可扩展标记语言 (XML)

xml简介

xml是可扩展标记语言,和html(超文本标记语言)相似,区别是html主要用于展示数据,而xml是传输和存储数据。并且html有固定的标签<h1></h1>等,xml是自定义的标签

组成部分

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>

声明

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

这个是必须的,声明版本和使用的编码

元素

1
2
3
4
5
6
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>

note为根元素,只能有一个根元素,其中有四个子元素

属性

1
2
3
4
5
6
<note date="10/01/2008">
<to lang="en">Tove</to>
<from>Jani</from>
<heading level="1">Reminder</heading>
<body urgent="true">Don't forget me this weekend!</body>
</note>

属性是提供元素的额外信息,xml属性必须要有引号,属性写在元素的标签里

DTD(文档类型定义)

DTD(Document Type Definition,文档类型定义)是XML文档中用于定义文档结构、元素、属性和实体的规范。 它定义了XML文档的逻辑结构,规定了文档中所使用的元素、属性、实体以及它们之间的关系。 一个符合DTD验证的XML文档被称为合法的XML文档。这个也可以不写

1
2
3
4
<!DOCTYPE 根元素名 [DTD定义]>  //文档类型声明
<!ENTITY 实体名称 实体值> //实体声明
<!ELEMENT 元素名称 (子元素名称 | 子元素名称)*> //元素声明
<!ATTLIST 元素名称 属性名称 属性类型 默认值> //属性声明

内部DTD引用

内部DTD引用是将DTD定义直接嵌入XML文档中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" ?> 
<!DOCTYPE note [
<!ELEMENT note (to,from,heading,body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>
]>
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<message>Don't forget me this weekend!</message>
</note>

外部DTD引用

外部DTD引用是将DTD定义保存在独立的文件中,XML文档通过引用外部文件来使用DTD

1
<!DOCTYPE notebook SYSTEM "DTD文件路径">  //引用外部DTD

详细的DTD可以看看这篇文章https://edu.51cto.com/article/note/39216.html

预定义实体

xml预定义实体是文档中表示数据项的方式,如果使用字符”<”,解析器会把它当做新元素的开始,所以我们会有xml的实体替代

xml内部实体

1
<!DOCTYPE foo [ <!ENTITY myentity "my entity value" > ]>

在文档中的任何对实体引用&myentity;的使用都将被替换为所定义的值:“my entity value

xml外部实体

1
<!DOCTYPE foo [ <!ENTITY myentity SYSTEM "file:///etc/passwd" > ]>

需要有SYSTEM,可以利用file://协议从文件中加载外部实体,xee的主要原因

xxe(外部实体注入)

1
2
3
4
5
<?xml version="1.0"?>
<!DOCTYPE root [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root>&xxe;</root>

在DTD中定义外部实体,然后在元素中&引用就行了

XSLT(可扩展样式表语言转换)

可扩展样式表语言转换(XSLT)是一种基于xml的语言,和专门的处理软件一起使用,用于 XML 文档转换。

https://developer.mozilla.org/en-US/docs/Web/XML/XSLT/Reference/Element

Conversor

渗透靶机

nmap -sS -Pn 10.10.11.92 ,然后看了看3306和6379都是close,开了80和22端口

访问80服务,登入界面

注册登入

是一个转换器,可以上传xml文件和xslt文件,看到xml肯定想到xxe,然后还有一个about的路由

可以下载源码,拿到源码。app.py

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
@app.route('/convert', methods=['POST'])
def convert():
if 'user_id' not in session:
return redirect(url_for('login'))
xml_file = request.files['xml_file']
xslt_file = request.files['xslt_file']
from lxml import etree
xml_path = os.path.join(UPLOAD_FOLDER, xml_file.filename)
xslt_path = os.path.join(UPLOAD_FOLDER, xslt_file.filename)
xml_file.save(xml_path)
xslt_file.save(xslt_path)
try:
parser = etree.XMLParser(resolve_entities=False, no_network=True, dtd_validation=False, load_dtd=False)
xml_tree = etree.parse(xml_path, parser)
xslt_tree = etree.parse(xslt_path)
transform = etree.XSLT(xslt_tree)
result_tree = transform(xml_tree)
result_html = str(result_tree)
file_id = str(uuid.uuid4())
filename = f"{file_id}.html"
html_path = os.path.join(UPLOAD_FOLDER, filename)
with open(html_path, "w") as f:
f.write(result_html)
conn = get_db()
conn.execute("INSERT INTO files (id,user_id,filename) VALUES (?,?,?)", (file_id, session['user_id'], filename))
conn.commit()
conn.close()
return redirect(url_for('index'))
except Exception as e:
return f"Error: {e}"
1
parser = etree.XMLParser(resolve_entities=False, no_network=True, dtd_validation=False, load_dtd=False) 

resolve_entities=False禁用实体解析,防御了xxe。dtd_validation=False忽略DTD,load_dtd=False不加载DTD的内容

parser定义了解析器的策略,但是这里只给xml用了,xslt没有使用

xml_tree = etree.parse(xml_path, parser)  
xslt_tree = etree.parse(xslt_path)

xslt注入的payload,可以看看这篇文章传送门 ,继续看源码中的文件,有一个install.md

下面的命令,是定时任务,每分钟会以www-data的身份执行该目录下的python文件,所以我们可以利用xslt写入文件,然后用python脚本反弹shell,先尝试写入文件

然后写入反弹shell的payload,死活弹不出来,只能先看wp进入到ssh阶段,看看文件到底有没有写入

ssh fismathack@10.10.11.92 ,用户名和密码是在数据库找到的,密码是md5加密,用网站破解出来的,ssh连接后,先列出当前用户可以使用sudo执行的命令和权限。sudo -l

不需要密码,就能以任意身份执行needrestart,首先查看版本,/usr/sbin/needrestart -v

搜索找到相关漏洞的利用 传送门

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sub files {
my $self = shift;
my $pid = shift;
my $cache = shift;
my $ptable = nr_ptable_pid($pid);

my %e = nr_parse_env($pid);
local %ENV;
if (exists($e{PYTHONPATH})) {
$ENV{PYTHONPATH} = $e{PYTHONPATH};
}

my ($pyread, $pywrite) = nr_fork_pipe2($self->{debug}, $ptable->exec, '');
print $pywrite "import sys\nprint(sys.path)\n";
close($pywrite);

漏洞原理是在执行needrestart时,会获取python环境变量,然后用python解释器执行import sys;print(sys.path)

python环境变量可以控制导入包或者模块的优先级,比如说我让PYTHONPATH=”$PWD”,如果要import foo,我在当前目录下建一个同名foo的目录,就会使用新建的foo,而不是系统自带的/第三方的 foo

因为import 导入的时候会执行包里面的__init__.py,__init__.so等初始化文件,意味着我们可以使用恶意的.so文件,执行命令

但是源码里只import sys,但是sys不能利用,gpt plus的解释是

但是构建一个python解释器,也需要导入其他包,比如说importlib,所以我们可以把恶意的文件放在该目录下

更改环境变量->编译恶意的c文件为.so文件->在importlib目录下添加__init__.so->提权为root

参考链接https://idocdown.com/app/articles/blogs/detail/10972

然后是解决之前弹不到shell的问题,原因是首行缩进不对

xslt文件

nc -lvnp 1234 成功反弹shell

根据源码中数据库的位置,在/var/www/conversor.htb/instance/users.db,用sqlite3连接

然后就是md5解密

xslt写入python反弹shell->读取数据库md5解密->CVE-2024-48990提权