共用方式為


本文章是由機器翻譯。

安全性簡報

XML 拒絕服務攻擊與防禦

Bryan Sullivan

拒絕服務 (DoS) 攻擊是攻擊對於網站的最舊的型別。 記載的 DoS 攻擊作為 predates SQL 資料隱碼攻擊 (在 1998年中發現) 的 1992年遠反面形式存在至少跨網站指令碼 (直到 1995 wasn’t 是 JavaScript,) 和跨網站要求偽造 (CSRF 攻擊通常需要工作階段 Cookie 和 Cookie weren’t 引入直到 1994年)。

從開始拒絕服務攻擊是高度受歡迎與駭客的社群,然後 ’s 容易了解原因。 單一 「 指令碼 kiddie 」 攻擊,以最少數量的技能與資源可能會產生足夠破壞服務中的站台 (如同步處理) 的 TCP SYN 要求的洪水。 對於 fledgling 電子商務] 世界這是寸草不生:如果使用者 couldn’t 取得到站台,couldn’t 非常好或是逛那里金錢。 DoS 攻擊是虛擬相當於 erecting razor 電線柵欄圍繞磚塊及灰漿存放區不同之處在於任何存放區可以在任何時間、 白天或晚上遭受攻擊。

這些年來 SYN 氾濫攻擊有被多半被降低 Web 伺服器軟體和網路硬體的改良。 不過,最近沒有安全性的群區內的拒絕服務攻擊的感興趣的 resurgence — 不是針對 「 舊學校 」 網路層級 DoS,但改為應用程式層級 DoS 及特別 XML 剖析器 DoS。

XML 的 DoS 攻擊是極為非對稱式:若要傳遞攻擊裝載,攻擊者必須花只有一小部份的處理能力或受害者需要花處理內容的頻寬。 更糟的是仍然,處理 XML 的程式碼中的 DoS 弱點也是非常普遍。 即使您使用一樣在 Microsoft.NET Framework System.Xml 類別中找到的徹底測試剖析器,您的程式碼可以仍然受到除非您採取明確的步驟來保護它。

本文將告訴您一些新的 XML DoS 攻擊。 它也會顯示方式可讓您偵測潛在的 DoS 弱點,以及如何減輕您的程式碼中。

XML 炸彈

一種特別棘手的 XML 服務阻斷攻擊是 XML 炸彈 (E-Mail Bomb) — XML 的是格式正確而且有效根據 XML 結構描述規則來,但其損毀,或該程式嘗試剖析它時停止回應程式區塊。 XML 炸彈 best-known 範例可能為指數實體展開攻擊。

內部的 XML 文件類型定義 (DTD),您可以定義您自己基本上是做為字串替代巨集的實體。 比方說您可以將這一行加入您的 DTD 取代所有的字串 &companyname; 與 「 Contoso Inc.」:

<!ENTITY companyname "Contoso Inc.">

您也可以使用巢狀像這樣的實體:

<!ENTITY companyname "Contoso Inc.">
<!ENTITY divisionname "&companyname; Web Products Division">

雖然大部分的開發人員都熟悉使用 DTD 的外部檔案,’s 也可能會包含內嵌 DTD 本身的 XML 資料一起。 您只需定義 DTD 直接在 <! DOCTYPE > 宣告,而非使用 <! DOCTYPE > 來參照外部 DTD 檔案:

<?xml version="1.0"?>
<!DOCTYPE employees [
  <!ELEMENT employees (employee)*>
  <!ELEMENT employee (#PCDATA)>
  <!ENTITY companyname "Contoso Inc.">
  <!ENTITY divisionname "&companyname; Web Products Division">
]>
<employees>
  <employee>Glenn P, &divisionname;</employee>
  <employee>Dave L, &divisionname;</employee>
</employees>

攻擊者現在可以利用 XML (替代實體、 巢狀實體) 和內嵌 DTD) 精心製作惡意的 XML 炸彈的這三個屬性。 攻擊者會寫入 XML 文件具有巢狀實體),就像先前的範例一樣,但而非巢狀處理只在一個層級深度,他鳥巢許多層級深度,如此處所示他實體:

<?xml version="1.0"?>
<!DOCTYPE lolz [
  <!ENTITY lol "lol">
  <!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
  <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
  <!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
  <!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
  <!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
  <!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
  <!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
  <!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
]>
<lolz>&lol9;</lolz>

請注意此 XML 是格式正確而且有效根據 DTD 規則來。 XML 剖析器載入此文件時, 看到它包含一個根元素 「 lolz 」,包含文字 「 &lol9; 」。 不過,「 &lol9; 」 是已定義的實體,展開以字串,包含十個 「 &lol8; 」 字串。 每個 「 &lol8; 」 字串會是展開以 10 的已定義的實體 「 &lol7; 」 字串,等等。 已處理所有實體擴充之後 XML 這個小 (< 1 KB) 區塊實際上會包含一個 20 億個 「 lol 」 s 佔用幾乎 3 GB 的記憶體!  您可以試試自己使用這個非常簡單程式碼區塊 (有時稱為 Billion Laughs 攻擊) 這類攻擊 — 只是準備刪除測試應用程式處理序從 「 工作管理員 」:

void processXml(string xml)
{
    System.Xml.XmlDocument document = new XmlDocument();
    document.LoadXml(xml);
}

一些更 devious 讀者可能會想要知道在此時是否 ’s 可能建立兩個彼此參考的實體所組成的無限地 recursing 實體擴充:

<?xml version="1.0"?>
<!DOCTYPE lolz [
  <!ENTITY lol1 "&lol2;">
  <!ENTITY lol2 "&lol1;">
]>
<lolz>&lol1;</lolz>

這會是非常有效的攻擊,但幸運的是它 isn’t 合法的 XML,而且不剖析。 但是的運作 [指數實體擴充 XML 炸彈 (E-Mail Bomb) 另一種變化是二次方 Blowup 攻擊 Amit Klein Trusteer 的發現。 定義多個小型的深層巢狀項目的攻擊者會定義一個非常大的實體,並參考許多次:

<?xml version="1.0"?>
<!DOCTYPE kaboom [
  <!ENTITY a "aaaaaaaaaaaaaaaaaa...">
]>
<kaboom>&a;&a;&a;&a;&a;&a;&a;&a;&a;...</kaboom>

如果攻擊者會定義實體 「 &a; 」 為 50,000 個字元長,並參考的實體 50,000 的時間內根 「 kaboom 」 項目,他最後與 XML 炸彈 (E-Mail Bomb) 攻擊承載量稍微超過 200 KB 的擴充為 2.5 GB 剖析時的大小。 此擴充比率不令人印象很一樣深刻做為與指數實體擴充] 攻擊,但它是採取關閉剖析的處理程序仍足夠。

另一個 Klein ’s XML 炸彈 (E-Mail Bomb) 網站尋找的是屬性 Blowup 攻擊。 許多較舊 XML 剖析器,包括在.NET Framework 1.0 和 1.1 版剖析極無效率的二次方 O (n 2 ) 中的 XML 屬性的版本執行階段中。 藉由建立 XML 文件有許多的單一元素的屬性 (假設 100,000 或多個),XML 剖析器將 monopolize 處理器很長的一段時間,並因此會造成拒絕服務的情況。 不過,這項弱點已固定在.NET Framework 2.0 和更新版本。

外部實體攻擊

代替以及常數定義實體取代字串,也可能是要定義它們,讓它們的值從外部 URI 提取:

<!ENTITY stockprice SYSTEM    "https://www.contoso.com/currentstockprice.ashx">

雖然確切行為取決於特定 XML 剖析器實作,這裡的用意是每次 XML 剖析器遇到實體 「 &stockprice; 」 剖析器會使 www.contoso.com/currentstockprice.ashx 的要求而替代從 stockprice 實體該要求收到回應。 這一定是 XML,一項很棒且實用的功能,但它也會啟用某些 devious DoS 攻擊。

濫用外部實體功能最簡單的方式是將 XML 剖析器傳送到資源,將永遠不會傳回,也就是要傳送它至一個無限等待迴圈。 比方說如果攻擊者有伺服器 adatum.com 控制項,他可以設定一般的 HTTP 處理常式檔在 http://adatum.com/dos.ashx 向上如下所示:

using System;
using System.Web;
using System.Threading;

public class DoS : IHttpHandler
{
    public void ProcessRequest(HttpContext context)
    {
        Thread.Sleep(Timeout.Infinite);
    }

    public bool IsReusable { get { return false; } }
}

他可能接著精心製作 http://adatum.com/dos.ashx,所指向的惡意實體,當 XML 剖析器會讀取 XML 檔案,剖析器會停止回應。 不過,這不是非常有效的攻擊。 DoS 攻擊的重點是要耗用資源,以使他們的應用程式的合法使用者無法使用。 我們較早的範例的指數實體展開] 及 [二次方 Blowup XML 炸彈造成伺服器使用大量的記憶體和 CPU 時間,但本範例不會。 這種攻擊真的會消耗的只是執行的單一執行緒。 let’s 改善這種攻擊 (從攻擊者 ’s 觀點來看) 強制消耗一些資源伺服器:

public void ProcessRequest(HttpContext context)
{
    context.Response.ContentType = "text/plain";
    byte[] data = new byte[1000000];
    for (int i = 0; i < data.Length; i++) { data[i] = (byte)’A’; }
    while (true)
    {
        context.Response.OutputStream.Write(data, 0, data.Length);
        context.Response.Flush();
    }
}

這個程式碼會寫入一個無限數目 ‘ A ’ 字元 (一百萬一次) 至回應資料流,且在極短的時間內 chew 向上大量記憶體。 如果攻擊者無法或願意設定他一頁擁有對這個用途 — 也許他 doesn’t 想留下一個軌跡,指到他的辨識項 — 他可以改指向外部實體非常大的資源在第三方廠商的網站上。 為此目的,影片或檔案的下載項目可以是特別有效 ; Visual Studio 2010 專業版 Beta 版下載是 2 GB 以上的例如。

還有另一個聰明的變化,這種攻擊的是在目標伺服器自己 ’s Intranet 資源指向外部實體。 此攻擊技術探索借方給徐維斌 Orrin Intel。 這項技巧並需要攻擊者,伺服器具有可存取的內部網路網站的內部知識但如果可執行的內部網路的資源攻擊,它可以特別有效因為伺服器花自己的資源 (處理器時間、 頻寬和記憶體) 攻擊本身或它同層級的伺服器在同一個網路上。

防禦 XML 炸彈

容易防禦所有類型的 XML 實體攻擊的方法是只需完全停用內嵌 DTD 結構描述,XML 剖析物件中使用。 這是縮減的簡單的應用程式攻擊面的原則:如果您不使用某項功能,關閉它,讓攻擊者 won’t 能夠濫用它。

在.NET Framework 3.5 及較早,DTD 的剖析行為是 System.Xml.XmlTextReader 和 System.Xml.XmlReaderSettings 類別中找到的布林 (Boolean) ProhibitDtd 屬性所控制。 完全設定此值來指定為 True,停用內嵌 DTD:

XmlTextReader reader = new XmlTextReader(stream);
reader.ProhibitDtd = true;

-或-

XmlReaderSettings settings = new XmlReaderSettings();
settings.ProhibitDtd = true;
XmlReader reader = XmlReader.Create(stream, settings);

ProhibitDtd XmlReaderSettings 中的預設值是 true,但 ProhibitDtd XmlTextReader 中的預設值是 false,這表示您必須明確地將它設為 True,則要停用內嵌 DTD。

在.NET Framework (以在撰寫本文時的 Beta 版) 4.0 版 DTD 剖析行為已經變更。 ProhibitDtd 屬性已被取代的新的 DtdProcessing 屬性改。 您可以將這個屬性設定為 [超過下列大小就禁止 (預設值) 會造成執行階段時擲回例外狀況為 <! DOCTYPE > 項目會出現在 XML 中:

XmlReaderSettings settings = new XmlReaderSettings();
settings.DtdProcessing = DtdProcessing.Prohibit;
XmlReader reader = XmlReader.Create(stream, settings);

或者,您可以在這裡將 DtdProcessing 屬性設定為略過不會擲回例外狀況上遇到一個 <! DOCTYPE > 項目,但將只是略過它並不處理。 最後,您可以設定 DtdProcessing 來分析,如果您想要允許和處理內嵌 DTD。

如果您真的想要剖析 DTD,應該採用一些額外的步驟,以保護您的程式碼。 第一個步驟是展開實體大小限制。 請記住的攻擊我討論運作藉由建立展開至巨大的字串,並強制剖析器會耗用大量記憶體的實體。 藉由設定 MaxCharactersFromEntities 屬性 XmlReaderSettings 物件的您可以覆蓋您可以建立透過實體擴充的字元數目。 決定合理的最大值,並依此設定了屬性。 以下為範例:

XmlReaderSettings settings = new XmlReaderSettings();
settings.ProhibitDtd = false;
settings.MaxCharactersFromEntities = 1024;
XmlReader reader = XmlReader.Create(stream, settings);

防禦外部實體攻擊

這個時候我們已加強這段程式碼,讓它就更不容易受到 XML 炸彈,但我們還 haven’t 解決惡意外部實體所造成的危險。 如果您藉由變更其 XmlResolver 來自訂 XmlReader 行為,您可以增進您對抗這些攻擊的恢復能力。 XmlResolver 物件用來解決包括外部實體的外部參考。 XmlTextReader 執行個體以及呼叫 XmlReader.Create,從傳回的 XmlReader 執行個體 prepopulated 與預設 XmlResolvers (實際上 XmlUrlResolvers) 中。 您可以防止 XmlReader 解析外部實體,同時仍讓它以解析內嵌實體 XmlReaderSettings] 的 [XmlResolver] 屬性設定為 null。 這是縮減攻擊面的上班一次 ; 如果您 don’t 需要功能,將它關閉:

XmlReaderSettings settings = new XmlReaderSettings();
settings.ProhibitDtd = false;
settings.MaxCharactersFromEntities = 1024;
settings.XmlResolver = null;
XmlReader reader = XmlReader.Create(stream, settings);

如果這種情況下 doesn’t 套用到您 — 如果您真的,真正需要解析外部實體 — 所有希望都不會遺失,但是您必須稍微更多工作来做。 若要進行拒絕服務攻擊更有彈性 XmlResolver,您需要變更它的行為三種方式。 首先,您必須設定要求逾時時間,以防止無限延遲的攻擊。 其次,您必須限制將擷取的資料量。 最後深度防禦量值,您需要限制 [XmlResolver 從擷取本機主機上的資源。 您可以建立自訂的 XmlResolver 類別來進行所有的這。

您想要修改的行為是由 XmlResolver 方法 GetEntity 所規範。 建立新的類別 XmlSafeResolver 衍生自 XmlUrlResolver 並覆寫 GetEntity 方法,如下所示:

class XmlSafeResolver : XmlUrlResolver
{
    public override object GetEntity(Uri absoluteUri, string role, 
        Type ofObjectToReturn)
    {

    }
}

預設行為的 XmlUrlResolver.GetEntity 方法看起來像下列程式碼即可使用做為起點為您的實作:

public override object GetEntity(Uri absoluteUri, string role, 
    Type ofObjectToReturn)
{
    System.Net.WebRequest request = WebRequest.Create(absoluteUri);
    System.Net.WebResponse response = request.GetResponse();
    return response.GetResponseStream();
}

第一次變更是套用逾時值,進行要求時,和讀取回應時。 System.Net.WebRequest 和 System.IO.Stream 類別提供的逾時固有的支援。 圖 1 所示的範例程式碼中我只是 hardcode 逾時] 值,但您可以輕易地公開公用逾時屬性 XmlSafeResolver 類別上的如果您想更 configurability。

圖 1 設定等候逾時數值

private const int TIMEOUT = 10000;  // 10 seconds

public override object GetEntity(Uri absoluteUri, string role, 
   Type ofObjectToReturn)
{
    System.Net.WebRequest request = WebRequest.Create(absoluteUri);
    request.Timeout = TIMEOUT;

    System.Net.WebResponse response = request.GetResponse();
    if (response == null)
        throw new XmlException("Could not resolve external entity");

    Stream responseStream = response.GetResponseStream();
    if (responseStream == null)
        throw new XmlException("Could not resolve external entity");
    responseStream.ReadTimeout = TIMEOUT;
    return responseStream;
}

下一個步驟是以覆蓋最大回應中擷取的資料量。 有 ’s 沒有 「 上限 」 屬性或資料流] 類別的對等用法所以您不必自行實作這項功能。 若要執行此動作,您可以讀取回應資料流一個區塊中的資料,一次,然後複製該到本機的資料流快取。 如果從回應資料流讀取的位元組總數超過預先定義的限制硬式 (一次編碼為簡單起見只),停止讀取自資料流,則擲回例外狀況 (請參閱 的 圖 2)。

圖 2 Capping 擷取資料的最大數量

private const int TIMEOUT = 10000;                   // 10 seconds
private const int BUFFER_SIZE = 1024;                // 1 KB 
private const int MAX_RESPONSE_SIZE = 1024 * 1024;   // 1 MB

public override object GetEntity(Uri absoluteUri, string role, 
   Type ofObjectToReturn)
{
    System.Net.WebRequest request = WebRequest.Create(absoluteUri);
    request.Timeout = TIMEOUT;

    System.Net.WebResponse response = request.GetResponse();
    if (response == null)
        throw new XmlException("Could not resolve external entity");

    Stream responseStream = response.GetResponseStream();
    if (responseStream == null)
        throw new XmlException("Could not resolve external entity");
    responseStream.ReadTimeout = TIMEOUT;

    MemoryStream copyStream = new MemoryStream();
    byte[] buffer = new byte[BUFFER_SIZE];
    int bytesRead = 0;
    int totalBytesRead = 0;
    do
    {
        bytesRead = responseStream.Read(buffer, 0, buffer.Length);
        totalBytesRead += bytesRead;
        if (totalBytesRead > MAX_RESPONSE_SIZE)
            throw new XmlException("Could not resolve external entity");
        copyStream.Write(buffer, 0, bytesRead);
    } while (bytesRead > 0);

    copyStream.Seek(0, SeekOrigin.Begin);
    return copyStream;
}

您可以另一個方法包裝資料流類別,並實作檢查直接在覆寫的讀取方法中的限制 (請參閱 的 圖 3)。 這是更有效率的實作,因為儲存額外的記憶體配置給快取 MemoryStream 在較早的範例。

圖 3 定義一個大小-受限制 」 資料流包裝函式類別

class LimitedStream : Stream
{
    private Stream stream = null;
    private int limit = 0;
    private int totalBytesRead = 0;

    public LimitedStream(Stream stream, int limit)
    {
        this.stream = stream;
        this.limit = limit;
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
        int bytesRead = this.stream.Read(buffer, offset, count);
        checked { this.totalBytesRead += bytesRead; }
        if (this.totalBytesRead > this.limit)
            throw new IOException("Limit exceeded");
        return bytesRead;
    }

    ...
}

現在,只是包裝在一個 LimitedStream WebResponse.GetResponseStream 從傳回資料流並傳回該 LimitedStream 從 GetEntity 方法 (請參閱 的 圖 4)。

圖 4 使用中 GetEntity LimitedStream

private const int TIMEOUT = 10000; // 10 seconds
private const int MAX_RESPONSE_SIZE = 1024 * 1024; // 1 MB

public override object GetEntity(Uri absoluteUri, string role, Type
ofObjectToReturn)
{
    System.Net.WebRequest request = WebRequest.Create(absoluteUri);
    request.Timeout = TIMEOUT;

    System.Net.WebResponse response = request.GetResponse();
    if (response == null)
        throw new XmlException("Could not resolve external entity");

    Stream responseStream = response.GetResponseStream();
    if (responseStream == null)
        throw new XmlException("Could not resolve external entity");
    responseStream.ReadTimeout = TIMEOUT;

    return new LimitedStream(responseStream, MAX_RESPONSE_SIZE);
}

最後,加入一個更多的深度防禦量值,藉由封鎖的 URI 解析為本機主機 (請參閱 的 圖 5) 因為實體解析包含啟動 http://localhost、 http://127.0.0.1,與 file:// URI 的 URI。 請注意這也可防止攻擊者可以建立指向的 file://resources 內容都是再 duly 擷取且由剖析器寫入至 XML 文件的實體的非常棘手的資訊洩漏弱點。

圖 5 封鎖本機主機實體解析

public override object GetEntity(Uri absoluteUri, string role,
    Type ofObjectToReturn)
{
    if (absoluteUri.IsLoopback)
        return null;
    ...
}

既然您定義更安全的 XmlResolver,就必須將它套用至 XmlReader。 外顯具現化 XmlReaderSettings 物件、 將 XmlResolver] 屬性設定為 XmlSafeResolver 的執行個體,然後使用 [XmlReaderSettings 時建立 XmlReader,如下所示:

XmlReaderSettings settings = new XmlReaderSettings();
settings.XmlResolver = new XmlSafeResolver();
settings.ProhibitDtd = false;   // comment out if .NET 4.0 or later
settings.DtdProcessing = DtdProcessing.Parse;  // comment out if 
                                               // .NET 3.5 or earlier
settings.MaxCharactersFromEntities = 1024;
XmlReader reader = XmlReader.Create(stream, settings);

其他考量事項

如果一個 XmlReader 未明確提供至物件或某個方法那麼其中一個隱含建立它的架構程式碼中,它 ’s 特別要注意的許多 System.Xml 的類別。 隱含建立 XmlReader 這不會有任何額外的防禦這篇文章中所指定的且會容易受到攻擊。 第一個程式碼片段本文中的是很好這種行為範例:

void processXml(string xml)
{
    System.Xml.XmlDocument document = new XmlDocument();
    document.LoadXml(xml);
}

這個程式碼就完全容易受到本文所述的所有攻擊。 為了增進此程式碼,明確地建立一個 XmlReader 具有適當的設定值 (或是停用內嵌 DTD 剖析指定更安全的解決器類別),並代替 XmlDocument.LoadXml 或任何其他 XmlDocument.Load 多載,使用 XmlDocument.Load(XmlReader) 多載,如 的 圖 6 所示。

圖 6 套用更安全實體剖析設定],以 XmlDocument

void processXml(string xml)
{
    MemoryStream stream =
        new MemoryStream(Encoding.Default.GetBytes(xml));
    XmlReaderSettings settings = new XmlReaderSettings();

    // allow entity parsing but do so more safely
    settings.ProhibitDtd = false;
    settings.MaxCharactersFromEntities = 1024;
    settings.XmlResolver = new XmlSafeResolver();

    XmlReader reader = XmlReader.Create(stream, settings);
    XmlDocument doc = new XmlDocument();
    doc.Load(reader);
}

XLinq 是有點更安全,在其預設設定 ; XmlReader System.Xml.Linq.XDocument 預設建立並允許 DTD 剖析,但自動將 MaxCharactersFromEntities 設定為 10,000,000,而且禁止外部實體解析。 如果您明確地提供一個 XmlReader 到 XDocument,請務必套用稍早所述的防禦性設定。

結論

XML 實體擴充是強大的功能,但它可以輕易地濫用由攻擊者拒絕您的應用程式的服務。 請務必遵循縮減攻擊面的的原則,並停用實體擴充,如果 don’t 需要使用它。 否則,套用適當的防禦,以限制在其上最大的時間和您的應用程式可以花費的記憶體量。

Bryan Sullivan 是安全性程式管理員為 Microsoft 安全性開發生命週期] 小組珍貴的 Web 應用程式和.NET 中的安全性問題。 他是 「 AJAXSecurity 」 (Addison-Wesley,2007年) 作者。