Skip to content

Latest commit

 

History

History

resonator

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 

This is the shortest of the web challenges, totaling only five lines in index.php:

<?php
$file = $_GET['file'] ?? '/tmp/file';
$data = $_GET['data'] ?? ':)';
file_put_contents($file, $data);
echo file_get_contents($file);

That is, we can write arbitrary data to a given file on the server (with file_put_contents()) and then read it back with file_get_contents(). The file permission are draconian, though: we are restricted to reading from and writing to /tmp only. The flag is somewhere else, and in order to read it, we are supposed to invoke the sgid /readflag binary the challenge authors put in the Docker container.

There's only so much we can do with files in /tmp (we certainly can't run arbitrary binaries this way), but the file_*_contents() functions in PHP support more than just regular files. For our evil purposes, the most important thing is they can fetch URLs:

<?php
    // prints
    // Contact: https://ctftime.org/feedback
    // Contact: mailto:info@ctftime.org
    echo file_get_contents('https://ctftime.org/.well-known/security.txt');
?>

Unlike in the other web challenges, the socket that php-fpm listens on is a regular TCP socket, not a UNIX one:

fastcgi_pass 127.0.0.1:9000;

This suggests we try to reuse our trick from heiko with a slight modification. As before, we first save a malicious PHP script that runs /readflag to /tmp/. After that, since we don't have a shell and can't talk to the PHP socket directly, we trick file_*_contents() into connecting to the socket and sending a FastCGI message that would execute the malicious script.

We can craft such a message in a few lines of Python. However, because the FastCGI protocol is binary, the hard part is figuring out how to deliver it over the socket. We decided to implement a fake FTP server (again, a small Python script) that redirects PHP to 127.0.0.1:9000 when file_put_contents() is called and PHP tries to open a data connection in passive mode.

Here's how it works:

This FTP is so fake

The only minor detail remaining is how to send the flag to us after spawning /readflag because file_put_contents() that talks to FTP obviously won't send anything back to the client. Our PHP payload saves the flag in /tmp/whatever and makes it readonly:

<?php shell_exec("/readflag > /tmp/{FLAG_TXT_ID}.txt && chmod 444 /tmp/{FLAG_TXT_ID}.txt"); ?>

This means that the flag can't be overwritten by file_put_contents() and we can retrieve it simply with GET /index.php?file=/tmp/whatever. Combining all the pieces, we run the exploit and finally get what we want:

PS> python .\exploit.py [REDACTED_IP]
hxp{I_hope_you_did_not_had_to_read_php-src_for_this____lolphp}