XML Billion Laughs attack, huidige stand van zaken

Saturday 30 January 2010, 16:07:00 | web dev

Ik was jaren geleden al eens aan het kijken naar de zogenaamde billion laughs xml attack, waarbij een doodsimpel xml bestandje van een paar honderd bytes zo'n beetje alle XML parsers (en web browsers!) op hun knieen bracht (enorm geheugengebruik en 100% cpu usage). Dit vanwege een exponentiele entity reference expansie. Meer info op Wikipedia: Billion Laughs.

Ik was nieuwsgierig of er tegenwoordig al wat vangnetten zijn geplaatst voor dit soort attacks, dus ik ging mijn million laughs testbestandje eens openen in een aantal web browsers. De resultaten zijn hoopvol! Waar een paar jaar geleden alle web browsers finaal onderuit gingen, zijn de resultaten nu ongeveer als volgt:

Voor meer info, zie de read more.

Java: JAXP (Xerces2)

Ik heb met de standaard (ingebouwde) Xerces2 XML parser van Java6 -wordt gebruikt als implementatie voor de JAXP API- geprobeerd de millionlaughs.xml te parsen. Die faalde netjes met de volgende foutmelding:

[Fatal Error] :1:1: The parser has encountered more than "64.000" entity expansi ons in this document; this is the limit imposed by the application.

(maakte niet uit of ik een DOM parser of een SAX parser gebruikte).

Dit verraste me, de laaste keer dat ik met Java deze grap uithaalde crashte hij met een out of memory error geloof ik. Het blijkt dat er tegenwoordig een SecurityManager class in Xerces zit die je onder andere kunt vertellen hoeveel entity expansions er maximaal mogen optreden. Standaard staat dat kennelijk op 64000, netjes.

Jammer genoeg blijkt er bij de XMLReader API geen SecurityManager ingesteld te zijn: deze ging vrolijk 100% CPU gebruiken om het document te parsen! Wel opvallend dat het geheugengebruik nooit boven de 16Mb uitkwam (met een dummy content handler) maar ja, de billion laughs zou toch de parser volledig blokkeren denk ik. Helaas lijkt het ook niet mogelijk een eigen SecurityManager property in te stellen op de parser, omdat deze class in de restricted API zit (com.sun.*). Je moet dus de officiele third party Xerces jars installeren om dit te kunnen gebruikern. Pogingen om de standaard JAXP feature XMLConstants.FEATURE_SECURE_PROCESSING te activeren faalden met een feature is unsupported error (wat ik niet begrijp, want in de Javadocs staat dat alle implementaties deze feature moeten ondersteunen!)

Met de officiele Xerces jars kreeg ik het voor elkaar om ook bij de XMLReader een SecurityManager property te activeren op de parser zodat je het aantal entity references kunt limiteren, maar het vreemde was dat nu de default instelling van de SAX en DOM parsers weer zonder security manager was... deze moest ik dus expliciet activeren om het standaard gedrag van de parser uit de JDK zelf te krijgen. En de JAXP feature secure-processing deed het nog steeds niet. Wat daar nou het verhaal van is?

Bij de STAX streaming interface (XMLStreamReader) kreeg ik het helemaal niet voor elkaar om een beveiliging te activeren. Noch de secure-processing property werd herkend, noch bestaat er een mogelijkheid om een SecurityManager te kopppelen. Heel spijtig.

Java scoort dus 2 uit 4 (DOM en SAX gaan goed, XMLReader en STAX XMLStreamReader falen, out of the box). Hopelijk gebruiken Java web services dus een SecurityManager met ingestelde beperkingen. Hoewel de mogelijkheden er zijn, weet ik vrij zeker dat oudere implementaties deze nog niet hadden. Er zullen dus best nog wel oudere web services bestaan die kwetsbaar zijn, maar met een beetje mazzel heb je er geen last meer van als je een recent genoege Xerces / JDK gebruikt. Tenzij je STAX gebruikt want blijkbaar zit daar helemaal geen beveiliging op... Zie ook hier bij IBM.

.NET 3.5

System.Xml.XmlReader: gaat direct de mist in, vrolijk 100% CPU gebruiken om het te parsen. Een tiental seconden en 200Mb geheugen verder is hij klaar. Maar dat was slechts millionlaughs.xml.... Score: 0/0 voor .NET :-( Waarschijnlijk zijn de meeste, zo niet alle, .NET SOAP web services dus kwetsbaar voor de billion laughs attack o_o !

Python

lxml (losse download, interface naar libxml2): geeft netjes een error, "lxml.etree.XMLSyntaxError: Detected an entity reference loop, line 1, column 9"

ElementTree (xml.etree.cElementTree): parsed millionlaughs.xml verbazingwekkend snel (slechts enkele seconden) maar billionlaughs.xml is game over (lockup 100% cpu en memory)

xml.dom.minidom: game over (lockup) xml.parsers.expat: geeft vreemd genoeg een invalid token error op de millionlaughs.xml. Maar de billionlaughs.xml brengt ook deze parser op z'n knieen. Zo erg zelfs dat het Python proces er helemaal mee kapt (crash).

Score voor Python is een schamele 1/4... :-( Conclusie lijkt: gebruik lxml als je xml web services maakt en niet vatbaar wilt zijn voor de billionlaughs attack. Helaas gebruikt de xmlrpclib uit de standard library expat dus die is kwetsbaar zonder aanpassingen...

De testbestanden

De testbestanden heb ik online staan. Wees gewaarschuwd als je er op klikt: downloadmillionlaughs.xml downloadbillionlaughs.xml

Als je alleen maar wilt weten hoe mijn billionlaughs.xml eruit ziet, dit is de inhoud:

<?xml version="1.0"?>
<!DOCTYPE billion [
<!ELEMENT billion (#PCDATA)>
<!ENTITY laugh0 "ha">
<!ENTITY laugh1 "&laugh0;&laugh0;">
<!ENTITY laugh2 "&laugh1;&laugh1;">
<!ENTITY laugh3 "&laugh2;&laugh2;">
<!ENTITY laugh4 "&laugh3;&laugh3;">
<!ENTITY laugh5 "&laugh4;&laugh4;">
<!ENTITY laugh6 "&laugh5;&laugh5;">
<!ENTITY laugh7 "&laugh6;&laugh6;">
<!ENTITY laugh8 "&laugh7;&laugh7;">
<!ENTITY laugh9 "&laugh8;&laugh8;">
<!ENTITY laugh10 "&laugh9;&laugh9;">
<!ENTITY laugh11 "&laugh10;&laugh10;">
<!ENTITY laugh12 "&laugh11;&laugh11;">
<!ENTITY laugh13 "&laugh12;&laugh12;">
<!ENTITY laugh14 "&laugh13;&laugh13;">
<!ENTITY laugh15 "&laugh14;&laugh14;">
<!ENTITY laugh16 "&laugh15;&laugh15;">
<!ENTITY laugh17 "&laugh16;&laugh16;">
<!ENTITY laugh18 "&laugh17;&laugh17;">
<!ENTITY laugh19 "&laugh18;&laugh18;">
<!ENTITY laugh20 "&laugh19;&laugh19;">
<!ENTITY laugh21 "&laugh20;&laugh20;">
<!ENTITY laugh22 "&laugh21;&laugh21;">
<!ENTITY laugh23 "&laugh22;&laugh22;">
<!ENTITY laugh24 "&laugh23;&laugh23;">
<!ENTITY laugh25 "&laugh24;&laugh24;">
<!ENTITY laugh26 "&laugh25;&laugh25;">
<!ENTITY laugh27 "&laugh26;&laugh26;">
<!ENTITY laugh28 "&laugh27;&laugh27;">
<!ENTITY laugh29 "&laugh28;&laugh28;">
<!ENTITY laugh30 "&laugh29;&laugh29;">
]>
<billion>&laugh30;</billion>
People have replied:

Irmen de Jong

2011-02-11 00:21:00

updates op dit moment (februari 2011)!

.NET 3.5 en 4.0: Er zijn een aantal instellingen die je kunt doen waardoor de .NET XmlReader netjes een exceptie geeft als je xml document te groot wordt of te veel entity expansions heeft. Dat wist ik niet. Zolang je dus maar de XmlReaderSettings instelt gaat het goed in .NET! Goed om te weten, maar overal waar een XmlDocument of XmlReader zonder speciale settings wordt gebruikt is de code nog wel kwetsbaar. Score 1/1 voor .NET ! :-) (.net 3.5 geeft een beetje een cryptische exception maar .net 4.0 geeft netjes aan dat het inlezen gefaald is wegens een bepaalde limiet ingesteld zus en zo).

XmlReaderSettings settings = new XmlReaderSettings();
settings.ProhibitDtd=false;
// settings.DtdProcessing = DtdProcessing.Parse;   // .net 4.0
settings.MaxCharactersFromEntities = 1024;
settings.MaxCharactersInDocument = 10000;
XmlReader reader = XmlReader.Create("billionlaughs.xml", settings);
XmlDocument doc=new XmlDocument();
doc.Load(reader);