利用GPT进行代码审计-Beelt模板注入

admin 2024年4月14日02:31:19评论22 views字数 5836阅读19分27秒阅读模式

概述

Beetl 是一款高性能、开源的 Java 模板引擎,它提供了简洁而强大的模板语法,使开发者能够方便地生成动态的文本输出。Beetl 模板引擎具有灵活的配置选项和丰富的功能,可以广泛应用于 Web 开发、邮件生成、代码生成等领域。

安装和配置

  1. 下载 Beetl 模板引擎的 JAR 文件,可以从官方网站或 Maven 仓库获取。

  2. 将 JAR 文件添加到项目的依赖中。

  3. 根据项目的需求,配置 Beetl 模板引擎的相关参数,如模板目录、缓存配置、字符编码等。

模板语法

Beetl 模板引擎使用一种简洁而直观的模板语法,让开发者能够轻松地构建动态的文本输出。

输出变量

使用 ${} 语法可以在模板中输出变量的值。例如,${name} 表示输出变量 name 的值。

<p>欢迎您,${name}!</p>

条件判断

使用 ifelse 来进行条件判断。示例:

<if(user.isAdmin)>
  <p>您是管理员。</p>
<else>
  <p>您不是管理员。</p>
</if>

循环遍历

使用 for 循环语句来遍历集合或数组中的元素。示例:

<ul>
  <for(item in itemList)>
      <li>${item}</li>
  </for>
</ul>

定义变量

使用 #set 来定义变量并赋值。示例:

#set(username = "John")
<p>您好,${username}!</p>

调用方法

使用 ${} 语法调用对象的方法,并输出方法的返回值。示例:

<p>当前时间:${new java.util.Date().toString()}</p>

高级特性

Beetl 模板引擎还提供了一些高级特性,用于处理更复杂的场景。

模板继承

模板继承允许开发者定义一个基础模板,并在其基础上创建其他模板。子模板可以重写父模板中的区块内容,实现模板的复用和扩展。示例:

父模板 layout.btl

<!DOCTYPE html>
<html>
<head>
  <title>${title}</title>
</head>
<body>
  <div id="content">
      <!-- 子模板可重写的区块 -->
      #block("content")
          <p>这是默认内容。</p>
      #end
  </div>
</body>
</html>

子模板 index.btl

#extends("layout.btl")

#block("content")
  <h1>欢迎使用 Beetl 模板引擎!</h1>
#end

自定义函数

Beetl 允许开发者定义自己的函数,并在模板中调用。示例:

定义自定义函数:

public class MyFunctions {
  public static String reverse(String str) {
      return new StringBuilder(str).reverse().toString();
  }
}

在模板中调用自定义函数:

<p>逆序输出:${reverse("Hello")}</p>

使用 Beetl 模板引擎

使用 Beetl 模板引擎的基本步骤如下:

  1. 创建一个 GroupTemplate 对象,作为模板引擎的入口点。

  2. 根据需要,配置 GroupTemplate 对象的相关参数,如模板目录、缓存配置等。

  3. 使用 GroupTemplate 对象的 getTemplate() 方法获取模板对象。

  4. 将数据填充到模板中,可以通过设置模板对象的变量或传入数据模型。

  5. 调用模板对象的 render() 方法,将模板渲染为最终的文本输出。

以下是一个使用 Beetl 模板引擎的示例代码:

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

public class BeetlDemo {
  public static void main(String[] args) throws IOException {
      // 创建配置对象
      Configuration configuration = Configuration.defaultConfiguration();

      // 创建模板引擎
      GroupTemplate groupTemplate = new GroupTemplate(configuration);

      // 获取模板对象
      Template template = groupTemplate.getTemplate("/path/to/template.btl");

      // 设置模板变量
      template.binding("name", "John");

      // 渲染模板并输出结果
      String result = template.render();
      System.out.println(result);
  }
}

漏洞演示

利用GPT进行代码审计-Beelt模板注入

模板注入分析

自建一个demo,用来进行演示

利用GPT进行代码审计-Beelt模板注入

跟踪getTemplate

/** 获取指定模板。
* 注意,不能根据Template为空来判断模板是否存在,请调用ResourceLoader来判断
* @param key
* @return
*/
public Template getTemplate(String key)
{

return getTemplateByLoader(key, this.resourceLoader,true);
}

调用getTemplateByLoader

使用gpt进行解读

利用GPT进行代码审计-Beelt模板注入

  1. 将参数 key 转换为内部字符串常量,以便在后续使用中进行比较和同步。

  2. 从缓存中根据 key 获取 Program 对象。

  3. 如果 program 为空,则进入同步块。

  4. 在同步块内部重新检查 program 是否为空,以防止在获取锁之前其他线程已经加载了模板。

  5. 使用提供的 loader 对象获取资源 resource

  6. 调用 loadTemplate 方法加载模板,传递 resourceisTextTemplate 参数。

  7. 将加载后的 program 对象存储在 programCache 中,使用 key 作为键。

  8. 创建一个新的 Template 对象,传递当前对象、programconf 参数,并将其返回。

如果 program 不为空,则执行以下操作:

  1. 检查 resourceLoader 是否修改了 program.rs(可能是资源的时间戳或版本号等)。

  2. 如果 resourceLoader 修改了 program.rs,则再次进入同步块。

  3. 使用提供的 loader 对象获取资源 resource

  4. 调用 loadTemplate 方法加载模板,传递 resourceisTextTemplate 参数。

  5. 将加载后的 program 对象存储在 programCache 中,使用 key 作为键。

最后,无论是从缓存中获取的 program 还是重新加载的 program,都会创建一个新的 Template 对象,并将其返回。

总体来说,这段代码的目的是根据给定的 key 使用提供的 loader 加载模板资源,并将加载的模板缓存起来。如果缓存中已经存在相应的模板且未被修改,则直接使用缓存的模板,否则重新加载模板并更新缓存。最终返回一个包装了模板的 Template 对象。

if判断key是否为空,这里的key不为空,所以进入下面的代码

if (resourceLoader.isModified(program.rs))
{
  synchronized (key)
  {
    Resource resource = loader.getResource(key);
    program = this.loadTemplate(resource,isTextTemplate);
    this.programCache.set(key, program);
  }
}

利用GPT进行代码审计-Beelt模板注入

  1. 首先声明一个变量 sf,类型为 Transformator,并将其初始化为 null

  2. try 块中进行模板加载的逻辑处理。

  3. 调用 res.openReader() 方法打开模板资源的读取器,获取模板内容的 Reader 对象。

  4. 根据isTextTemplate参数的值,决定创建不同类型的Transformator对象。

    • 如果 isTextTemplatetrue,则创建一个 Transformator 对象,使用配置中的占位符和语句定界符。

    • 如果 isTextTemplatefalse,则根据配置中是否具有函数限制符来决定创建哪种类型的 Transformator 对象。

  5. 如果配置中启用了 HTML 标签支持,则调用 sf.enableHtmlTagSupport() 方法,传入配置中的 HTML 标签定界符和绑定属性信息。

  6. 声明一个变量 scriptReader,类型为 Reader,并将其初始化为 sf.transform(reader) 的结果。这将对模板内容进行转换,并返回转换后的脚本内容的 Reader 对象。

  7. 使用 engine.createProgram() 方法创建一个 Program 对象,传入资源 res、转换后的脚本内容的 scriptReadersf 的文本映射和行分隔符信息,以及当前对象。

  8. 返回创建的 Program 对象。

try 块中,如果发生以下异常,则会执行相应的异常处理逻辑并返回特定的 ErrorGrammarProgram 对象:

  • 如果发生 HTMLTagParserException 异常,则创建一个 ErrorGrammarProgram 对象,传入资源 res、当前对象和 sf 的行分隔符信息,并将异常设置为其属性。然后将异常关联到资源,并返回 ErrorGrammarProgram 对象。

  • 如果发生 IOException 异常,则创建一个 ErrorGrammarProgram 对象,传入资源 res、当前对象和 sf 的行分隔符信息。然后创建一个 BeetlException 对象,并将其关联到资源。将 BeetlException 设置为 ErrorGrammarProgram 的异常,并返回 ErrorGrammarProgram 对象。

  • 如果发生 BeetlException 异常,则创建一个 ErrorGrammarProgram 对象,传入资源 res、当前对象和 sf 的行分隔符信息。将异常关联到资源,并将异常设置为 ErrorGrammarProgram 的异常。最后返回 ErrorGrammarProgram 对象。

总体来说,这段代码的目的是根据给定的资源 resisTextTemplate 参数加载模板,并将加载的模板转换为脚本内容,创建一个 Program 对象并返回。如果加载或转换过程中发生异常,将会返回一个特定的 ErrorGrammarProgram 对象,其中包含异常信息。

查看programCache

Cache programCache = ProgramCacheFactory.defaulCache();

利用GPT进行代码审计-Beelt模板注入

跟踪defaulCache

利用GPT进行代码审计-Beelt模板注入

  1. 声明了一个公共静态常量 CACHE,类型为字符串,值为 "org.beetl.core.cache.LocalCache"。该常量表示缓存实现类的类名。

  2. 声明了一个公共静态方法 defaultCache(),返回类型为 Cache。该方法用于获取默认的缓存实例。

  3. 在方法内部,首先获取当前线程的上下文类加载器,并将其赋值给 loader 变量。

  4. 如果 loader 为空,即当前线程没有上下文类加载器,那么将 loader 设置为 ProgramCacheFactory 类的类加载器。

  5. 调用 ObjectUtil.instance(CACHE, loader) 方法,使用 loader 加载 CACHE 所指定的缓存实现类,并实例化为一个 Cache 对象。

  6. 返回实例化后的 Cache 对象。

总体来说,这段代码的目的是提供一个工厂方法 defaultCache(),用于获取默认的缓存实例。该工厂方法通过反射加载指定的缓存实现类,并返回实例化后的缓存对象。默认情况下,缓存实现类的类名为 "org.beetl.core.cache.LocalCache"

instance 万物皆可序列化

最后一步就是instance,通过反射加载代码

利用GPT进行代码审计-Beelt模板注入

在最新版源码中,存在黑名单

public boolean permit(Object resourceId, Class c, Object target, String method) {
if (c.isArray()) {
// 允许调用,但实际上会在在其后调用中报错。不归此处管理
return true;
}
String name = c.getName();
String className = null;
String pkgName = null;
int i = name.lastIndexOf('.');
if (i != -1) {
pkgName = name.substring(0, i);
className = name.substring(i + 1);

} else {
// 无包名,允许调用
return true;
}

if (pkgName.startsWith("java.lang")) {
return !className.equals("Runtime") && !className.equals("Process") && !className.equals(
"ProcessBuilder") && !className.equals("Thread")
// https://gitee.com/xiandafu/beetl/issues/I6RUIP
&& !className.equals("Class") //https://gitee.com/xiandafu/beetl/issues/I6RUIP#note_17223442
&& !className.equals("System");
}


if (pkgName.startsWith("javax.")) {
return false;
}

if (pkgName.startsWith("sun.")) {
return false;
}

return true;
}

但可以通过js进行绕过,从而实现rce。

${@Class.forName(%22javax.script.ScriptEngineManager%22).newInstance().getEngineByName(%22js%22).eval(%22s=%27open%20-a%20Calculator%27;java.lang.Runtime.getRuntime().exec(s);%22)}

原文始发于微信公众号(轩公子谈技术):利用GPT进行代码审计-Beelt模板注入

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年4月14日02:31:19
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   利用GPT进行代码审计-Beelt模板注入http://cn-sec.com/archives/2654770.html

发表评论

匿名网友 填写信息