sql注入漏洞
- 创业
- 2025-08-28 18:48:02

目录
一、SQL注入概述
例子背景
正常情况下的查询
SQL注入攻击
利用优先级进行攻击
二、解决SQL注入
使用PreparedStatement接口
步骤和方法
1. 创建PreparedStatement对象
2. 向占位符传入值
3. 执行SQL语句
示例
总结
SQL 注入是一种常见的网络攻击手段。通俗来讲,很多网站、应用程序等都需要和数据库交互,比如登录时要查询数据库验证用户名和密码是否正确,接下来就以最常见的登录问题入手。
一、SQL注入概述SQL注入问题本质上是由字符串拼接和And与or的优先级造成的(and优先级高于or)
让我们通过一个具体的例子来理解这句话:“SQL注入问题的本质是因为字符串拼接和AND的优先级高于OR”。
例子背景假设我们有一个简单的登录表单,后端使用如下的SQL查询来验证用户名和密码:
SELECT * FROM users WHERE username = '输入的用户名' AND password = '输入的密码';如果开发者直接将用户输入拼接到SQL查询中,而不进行任何处理,就可能导致SQL注入问题。
正常情况下的查询正常情况下,用户输入的用户名和密码如下:
用户名:admin
密码:password123
拼接后的SQL查询如下:
SELECT * FROM users WHERE username = 'admin' AND password = 'password123';这个查询会检查数据库中是否存在用户名为admin且密码为password123的用户。
SQL注入攻击攻击者发现可以输入特殊构造的输入来绕过验证。假设攻击者输入如下:
用户名:admin' --
密码:任意值(例如:123456)
拼接后的SQL查询如下:
SELECT * FROM users WHERE username = 'admin' --' AND password = '123456';由于SQL中--是注释符号,--后面的内容会被SQL解析器忽略,所以实际执行的查询变为:
SELECT * FROM users WHERE username = 'admin' AND password = '';这个查询会检查数据库中是否存在用户名为admin且密码为空的用户。
利用优先级进行攻击攻击者还可以利用AND的优先级高于OR的特性进行攻击。假设攻击者输入如下:
用户名:admin' OR '1'='1
密码:任意值(例如:123456)
拼接后的SQL查询如下:
SELECT * FROM users WHERE username = 'admin' OR '1'='1' AND password = '123456';由于AND的优先级高于OR,所以实际执行的查询如下:
SELECT * FROM users WHERE (username = 'admin') OR (('1'='1') AND password = '123456');这意味着:
首先计算 username = 'admin'。
然后计算 ('1'='1') AND password = '123456'。由于 '1'='1' 总是为真,所以这个表达式简化为 true AND password = '123456',即 password = '123456'。
最后,整个表达式变为 username = 'admin' OR password = '123456'。
那么这就意味着,在这种情况下,不管用户输什么进去,只要username能够输对,password不论输什么都能登陆成功。
二、解决SQL注入 使用PreparedStatement接口PreparedStatement是Statement的子接口,它提供了预编译SQL语句的功能,从而可以有效防止SQL注入漏洞。预编译SQL语句的关键在于使用占位符(?)来代替参数部分,这样可以先将SQL语句发送到数据库服务器进行编译,编译后的SQL语句格式是固定的,后续传入的任何值都会作为参数处理,而不会被解释为SQL代码。
步骤和方法 1. 创建PreparedStatement对象通过Connection接口提供的prepareStatement方法来创建PreparedStatement对象。这个方法需要传入一个SQL语句,其中参数部分用?占位符表示。
Connection conn = // 获取数据库连接 String sql = "SELECT * FROM users WHERE username = ? AND password = ?"; PreparedStatement pstmt = conn.prepareStatement(sql); 2. 向占位符传入值使用PreparedStatement提供的方法向占位符传入值。这些方法包括setInt、setString、setXXX等,具体取决于参数的类型。
String username = "admin"; String password = "password123"; pstmt.setString(1, username); // 将第一个占位符替换为username pstmt.setString(2, password); // 将第二个占位符替换为password 3. 执行SQL语句根据执行的SQL语句类型,使用executeQuery或executeUpdate方法来执行查询。
executeQuery:用于执行查询语句,返回ResultSet对象。
executeUpdate:用于执行增删改语句,返回受影响的行数。
ResultSet rs = pstmt.executeQuery(); while (rs.next()) { // 处理查询结果 } // 或者执行增删改操作 int affectedRows = pstmt.executeUpdate(); 示例假设我们有一个用户登录功能,需要查询数据库中是否存在匹配的用户名和密码。使用PreparedStatement可以安全地实现这一功能。
import java.sql.*; public class UserLogin { public static void main(String[] args) { String jdbcURL = "jdbc:mysql://localhost:3306/yourdatabase"; String username = "root"; String password = "yourpassword"; String sql = "SELECT * FROM users WHERE username = ? AND password = ?"; try (Connection conn = DriverManager.getConnection(jdbcURL, username, password); PreparedStatement pstmt = conn.prepareStatement(sql)) { String inputUsername = "admin"; String inputPassword = "password123"; pstmt.setString(1, inputUsername); pstmt.setString(2, inputPassword); ResultSet rs = pstmt.executeQuery(); if (rs.next()) { System.out.println("登录成功!"); } else { System.out.println("用户名或密码错误!"); } } catch (SQLException e) { e.printStackTrace(); } } } 总结通过使用PreparedStatement,我们可以有效地防止SQL注入漏洞。预编译SQL语句的关键在于使用占位符(?)来代替参数部分,这样可以先将SQL语句发送到数据库服务器进行编译,编译后的SQL语句格式是固定的,后续传入的任何值都会作为参数处理,而不会被解释为SQL代码。这样可以确保即使用户输入恶意代码,也不会影响数据库的安全性。