简介
Pebble是一款Java 模板引擎,开发他的灵感来自于Twig。它具有很强的模板延续性和易于阅读的语法;出于安全性考虑,它内置自动转义功能,并包括对国际化的集成支持。它支持模板引擎中最常见的语法,其中变量替换使用完成。通常,在模板引擎中,可以包含任意的 Java 表达式。假设想将名为 name
的变量放在模板中并大写,这时可以使用 {{name.toUpperCase()}}
。
这个模板引擎Github才800多star,比较小众。
基本使用
当然,项目需要引入jar包:
1
2
3
4
5
| <dependency>
<groupId>io.pebbletemplates</groupId>
<artifactId>pebble-spring-boot-starter</artifactId>
<version>3.1.5</version>
</dependency>
|
首先准备一个模板文件: templates\home.html
,如下所示:
1
2
3
4
| <!DOCTYPE html>
<p>{{content}}</p>
<h1>{{user.name}}</h1>
</html>
|
然后使用如下代码进行模板渲染:
1
2
3
4
5
6
7
8
9
10
11
| PebbleEngine engine = new PebbleEngine.Builder().build();
PebbleTemplate compiledTemplate = engine.getTemplate("templates/home.html");
Writer writer = new StringWriter();
Map<String, Object> context = new HashMap<>();
User user = new User();
user.setName("p1n93r");
context.put("user", user);
context.put("content", "This is a basic test");
compiledTemplate.evaluate(writer, context);
String output = writer.toString();
System.out.println(output);
|
运行后,输出如下所示:
1
2
3
4
| <!DOCTYPE html>
<p>This is a basic test</p>
<h1>p1n93r</h1>
</html>
|
我们跟进 com.mitchellbosecke.pebble.template.PebbleTemplateImpl#evaluate()
,通过debug可以看到context中可以被访问的各种对象:
我们尝试使用模板的表达式访问一下这些对象:
1
2
3
4
5
6
7
| <!DOCTYPE html>
<p>{{content}}</p>
<h1>{{user.name}}</h1>
<h1>{{template}}}</h1>
<h1>{{_context}}}</h1>
<h1>{{locale}}</h1>
</html>
|
结果如下:
1
2
3
4
5
6
7
| <!DOCTYPE html>
<p>This is a basic test</p>
<h1>p1n93r</h1>
<h1>com.mitchellbosecke.pebble.template.PebbleTemplateImpl@5e025e70}</h1>
<h1>com.mitchellbosecke.pebble.template.GlobalContext@1fbc7afb}</h1>
<h1>zh_CN</h1>
</html>
|
其中, GlobalContext
就是一个 HashMap
的子类,只能调用 get()
方法, Map
里面的值就是前面分析的context中存在的可被表达式调用的几个对象。
PebbleTemplateImpl
类存在很多公开方法,但是目前暂时未找到可以利用的。 Locale
类也是,没找到可被利用的方法。
小节一下:
非springboot-start集成下,Pebble默认情况下存在三个对外暴露的对象:GlobalContext、PebbleTemplate和Locale ,但是暂时没发现可以被利用的点。
历史漏洞
我们知道,对于SSTI常用的沙箱逃逸手法有如下方式:
1
| {{ variable.getClass().forName('java.lang.Runtime').getRuntime().exec('ls -la') }}
|
在Pebble v3.0.9之前,会对 getClass
方法进行黑名单验证:
1
2
3
4
5
6
7
8
| /**
* com.mitchellbosecke.pebble.attributes.MemberCacheUtils
*/
private Method findMethod(Class<?> clazz, String name, Class<?>[] requiredTypes, String filename,
int lineNumber, EvaluationOptions evaluationOptions) {
if (!evaluationOptions.isAllowGetClass() && name.equals("getClass")) {
throw new ClassAccessException(lineNumber, filename);
}
|
所以,我们可以使用大小写绕过:
Pebble官方在v3.0.9进行了修复:
后续发现(v3.1.1之前),可以不使用 getClass()
即可获取 Class
对象。发现 java.lang.Integer
类存在静态变量:TYPE,这个变量的类型就是 Class
类型的。所以可以通过如下方式获取 Class
对象,并加载 java.lang.Runtime
类:
1
| {{ (1).TYPE.forName('java.lang.Runtime')... }}
|
这个漏洞在v3.1.1进行了修复,修复手法和 FreeMarker
一样。在 unsafeMethods.properties
中添加了大量黑名单。
安全机制
最新版(v3.1.5)中,发现是在 com.mitchellbosecke.pebble.attributes.methodaccess.BlacklistMethodAccessValidator#isMethodAccessAllowed()
存在黑名单,改变了以往的黑名单机制:
1
2
3
4
5
6
7
8
9
10
11
12
| // 类黑名单
@Override
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;
}
|
方法黑名单如下( com.mitchellbosecke.pebble.attributes.methodaccess.BlacklistMethodAccessValidator
):
1
2
3
4
| private static final String[] FORBIDDEN_METHODS = {"getClass",
"wait",
"notify",
"notifyAll"};
|
此外,需要注意的是,Pebble可以配置成不进行黑名单验证:
1
2
| // 配置NoOpMethodAccessValidator,不进行黑名单验证(包括类黑名单)
PebbleEngine engine = new PebbleEngine.Builder().methodAccessValidator(new NoOpMethodAccessValidator()).build();
|
如果配置这样,最新版本也可以直接getshell了:
参考
https://research.securitum.com/server-side-template-injection-on-the-example-of-pebble/