杼氖 发表于 2025-6-5 19:01:09

Java安全_SQL注入

[!NOTE]
本次学习使用开源项目:
https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/SQLI.java
使用工具:
浏览器
IDEA

目录

[*]什么是 SQL 注入
[*]JDBC 模式下的 SQL 注入

[*]1、最原始的拼接注入

[*]修复方式


[*]Mybatis模式下的SQL注入

[*]Mybatis下SQL的写法
[*]关于Mybatis下SQL的两种拼接方法

[*]那么体现在SQL语句上,这两种方法有什么区别呢?

[*]

[*]

[*]能否使用#{}的总结表格:






什么是 SQL 注入

SQL 注入(SQL Injection) 是 JavaWeb 应用中最常见、最严重的安全漏洞之一。它指的是攻击者将恶意 SQL 语句作为用户输入传入系统,并被后台拼接执行,从而操控数据库。
在 JavaWeb 项目中,最容易出现 SQL 注入的地方是:

[*]登录验证
[*]搜索查询
[*]数据过滤接口
[*]URL 参数拼接数据库语句
如下面的代码
            Statement statement = con.createStatement();
            String sql = "select * from users where username = '" + username + "'";
            logger.info(sql);
            ResultSet rs = statement.executeQuery(sql);如果攻击者输入:

[*]用户名:username = ' OR '1'='1
那么 SQL 就变成:
select * from users where username = '' OR '1'='1'那么条件判断永远为真,就能达到绕过的目的
JDBC 模式下的 SQL 注入

1、最原始的拼接注入

关键 Code
public String jdbc_sqli_vul( String username) {      StringBuilder result = new StringBuilder();      try {            Class.forName(driver);            Connection con = DriverManager.getConnection(url, user, password);            if (!con.isClosed())                System.out.println("Connect to database successfully.");            // sqli vuln code            Statement statement = con.createStatement();
            String sql = "select * from users where username = '" + username + "'";
            logger.info(sql);
            ResultSet rs = statement.executeQuery(sql);            while (rs.next()) {                String res_name = rs.getString("username");                String res_pwd = rs.getString("password");                String info = String.format("%s: %s\n", res_name, res_pwd);                result.append(info);                logger.info(info);            }            rs.close();            con.close();      } catch (ClassNotFoundException e) {            logger.error("Sorry,can`t find the Driver!");      } catch (SQLException e) {            logger.error(e.toString());      }      return result.toString();    }可以看到接受 Get 参数之后,直接进行了 SQL 语句拼接处理
下面为漏洞关键代码
            Statement statement = con.createStatement();
            String sql = "select * from users where username = '" + username + "'";
            logger.info(sql);
            ResultSet rs = statement.executeQuery(sql);构造 Payload
http://127.0.0.1:8081/sqli/jdbc/vuln?username=joychou' or '1'='1通过打断点查看,发现最终执行的 SQL 语句变成了 select * from users where username = 'joychou' or '1'='1',从而达到绕过的目的

修复方式

通过参数化查询+预编译,可以达到避免 SQL 注入的目的,代码示例如下:
String sql = "select * from users where username = ?";
PreparedStatement st = con.prepareStatement(sql);
st.setString(1, username);
ResultSet rs = st.executeQuery();PreparedStatement 是 Java JDBC 提供的预编译 SQL 语句接口,它允许开发者在 SQL 语句中使用占位符 ?,然后通过绑定参数的方式传入用户数据。数据库在执行时会先对 SQL 语句结构进行编译,之后再将参数作为数据绑定进去,保证了输入内容不会被当作 SQL 代码执行。
这种机制有效防止了 SQL 注入攻击,因为即便用户输入中包含恶意的 SQL 片段,也不会被当作语句解析执行,而只会作为普通字符串处理,从而提高了系统的安全性和稳定性。此外,PreparedStatement 还可以提升执行效率,尤其是对相同语句多次执行时,可以复用预编译结果,减少数据库负担。
在使用参数化查询+预编译后,在对代码进行调试,查看最终执行的 SQL 语句
可以发现,最终执行的语句变成了 select * from users where username = 'joychou\' or \'1\'=\'1'
在这里,预编译机制会将用户输入作为 纯字符串值 处理,自动对特殊字符(如单引号 ')进行转义,因此即使输入中包含类似 ' or '1'='1 的恶意内容,也不会改变 SQL 语句的逻辑结构。从而达到了防止 SQL 注入的目的

Mybatis模式下的SQL注入

Mybatis下SQL的写法

​        Mybatis有两种SQL语句写法:

[*]使用注解
    @Select("select * from users where username = #{username}")
    User findByUserName(@Param("username") String username);
[*]使用XML文件映射
    <select id="findByUserNameVsec02" parameterType="String" resultMap="User">
      select * from users where username like concat('%',#{_parameter}, '%')
    </select>

关于Mybatis下SQL的两种拼接方法

​        Mybatis中存在两种SQL语句拼接方法:

[*]${xxxxxx} 直接拼接,可以理解为上面的JDBC最原始的拼接 ,是一种不安全的方法
[*]#{xxxxxx} 预处理后拼接,是一种安全的方法
​        在 MyBatis 中,${} 和 #{} 的区别非常关键,它们直接影响到 SQL 是否存在注入风险:

[*]${} 是字符串替换,会在 SQL 拼接阶段将参数原样插入 SQL 中,不经过任何预处理或转义。如果用户输入中包含恶意 SQL 片段,就可能被拼接进最终 SQL 并执行,存在严重的 SQL 注入风险。
例如:
<select id="findUser" resultType="User">
    select * from users where username = '${username}'
</select>
​        如果用户输入是 joychou' or '1'='1,最终 SQL 将变为:
select * from users where username = 'joychou' or '1'='1'那么体现在SQL语句上,这两种方法有什么区别呢?

​                #{}会给传入的值自动加上单引号
​                如select * from #{param} ,如给param传入值为xxxx时,SQL会被拼接成 select * from 'xxxx' ,这是因为#{}自动给拼接上的参数加上了单引号
​                ${}就是什么都不变的直接拼接了
​                如 select * from ${param} ,如给param传入值为xxxx时,SQL会被拼接成 select * from xxxx,可以注意这里并没有单引号,因为${}是直接拼接
​                这就给Mybatis下的SQL注入带来了可乘之机
​                在SQL语句中存在很多不能包含单引号的情况,例如:被拼接的参数是表名、数据库名、字段名等 , 这一情况在 order by 中尤为常见
能否使用#{}的总结表格:

场景能否使用 #{}是否建议用 ${}风险说明表名❌ 否✅ 可用(需白名单)否则无法拼接 SQL列名 / 字段名❌ 否✅ 可用(需白名单)常用于排序、动态列选择排序方向(ASC/DESC)❌ 否✅ 可用(需白名单)否则 SQL 报错SQL 关键字❌ 否✅ 可用(需白名单)如运算符、动态语法结构LIMIT / OFFSET一般 ✅ 可用✅ 可用(需数值校验)数据库版本不同兼容性需注意数据库函数名❌ 否✅ 可用(需白名单)如 count, sum 可拼接但需控制​
​                因此,在这种不得不使用${}的情况下,需要格外注意对参数的过滤,如下面的代码
//使用了过滤器的order by执行语句
@GetMapping("/mybatis/orderby/sec04")
    public List<User> mybatisOrderBySec04(@RequestParam("sort") String sort) {
      String filter_order = SecurityUtil.sqlFilter(sort);
      return userMapper.findByUserNameVuln03(filter_order);
    }//过滤器
private static final Pattern FILTER_PATTERN = Pattern.compile("^+$");
public static String sqlFilter(String sql) {
      if (!FILTER_PATTERN.matcher(sql).matches()) {
            return null;
      }
      return sql;
    }
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: Java安全_SQL注入