开发安全之:SQL Injection

Overview

调用通过不可信赖的数据源输入构建的 SQL 查询 mysql_query()。通过这种调用,攻击者能够修改语句的含义或执行任意 SQL 命令。

Details

SQL injection 错误在以下情况下发生:

  1. 数据从一个不可信赖的数据源进入程序。

  2. 数据用于动态地构造一个 SQL 查询。 这种情况下,数据被传递给代码中的 mysql_query()。

**例 1:**以下代码动态地构造并执行了一个 SQL 查询,该查询可以搜索与指定名称相匹配的项。该查询仅会显示条目所有者与被授予权限的当前用户一致的条目。

userName = _SESSION['userName']; itemName = _POST['itemName'];

query = "SELECT \* FROM items WHERE owner = 'userName' AND itemname = '$itemName';";

result = mysql_query(query);

查询计划执行以下代码:

SELECT * FROM items WHERE owner = <userName> AND itemname = <itemName>;

但是,由于这个查询是动态构造的,由一个不变的查询字符串和一个用户输入字符串连接而成,因此只有在 itemName 不包含单引号字符时,才会正确执行这一查询。如果一个用户名为 wiley 的攻击者为 itemName 输入字符串"name' OR 'a'='a",那么查询就会变成:

SELECT * FROM items WHERE owner = 'wiley' AND itemname = 'name' OR 'a'='a';

附加条件 OR 'a'='a' 会使 where 从句永远评估为 true,因此该查询在逻辑上将等同于一个更为简化的查询: SELECT * FROM items;

通常,查询必须仅返回已通过身份验证的用户所拥有的条目,而通过以这种方式简化查询,攻击者就可以规避这一要求。现在,查询会返回存储在 items 表中的所有条目,而不论其指定所有者是谁。

**示例 2:**此示例说明了将不同的恶意值传递给Example 1.中构造和执行的查询所带来的影响。如果一个用户名为 wiley 的攻击者为 itemName 输入字符串"name'; DELETE FROM items; --",则该查询就会变为以下两个查询:

SELECT * FROM items WHERE owner = 'wiley' AND itemname = 'name';

DELETE FROM items; --'

众多数据库服务器,其中包括 Microsoft(R) SQL Server 2000,都可以一次性执行多条用分号分隔的 SQL 指令。对于那些不允许运行用分号分隔的批量指令的数据库服务器,比如 Oracle 和其他数据库服务器,攻击者输入的这个字符串只会导致错误;但是在那些支持这种操作的数据库服务器上,攻击者可能会通过执行多条指令而在数据库上执行任意命令。 注意末尾的一对连字符 (--);这在大多数数据库服务器上都表示该语句剩余部分将视为注释,不会加以执行 [4]。在这种情况下,可通过注释字符删除修改后的查询遗留的末尾单引号。而在不允许通过这种方式使用注释的数据库上,攻击者通常仍可使用类似于Example 1.中所用的技巧进行攻击。

如果攻击者输入字符串"name'); DELETE FROM items; SELECT * FROM items WHERE 'a'='a",将创建以下三个有效语句:

SELECT * FROM items WHERE owner = 'wiley' AND itemname = 'name';

DELETE FROM items;

SELECT * FROM items WHERE 'a'='a';

避免 SQL injection 攻击的传统方法之一是,作为一个输入验证问题来处理,只接受列在安全值允许列表中的字符,或者识别并避免列在潜在恶意值列表(拒绝列表)中的字符。检验允许列表是一种非常有效的方法,它可以强制执行严格的输入验证规则,但是参数化的 SQL 语句所需的维护工作更少,而且能提供更好的安全保障。而对于通常采用的执行拒绝列表方式,由于总是存在一些小漏洞,所以并不能有效地防止 SQL Injection 攻击。

例如,攻击者可以: --- 把没有被黑名单引用的值作为目标 - 寻找方法以绕过某些需要转义的元字符 - 使用存储过程隐藏注入的元字符 手动去除 SQL 查询中的元字符有一定的帮助,但是并不能完全保护您的应用程序免受 SQL injection 攻击。 防范 SQL injection 攻击的另外一种常用方式是使用存储过程。虽然存储过程可以阻止某些类型的 SQL injection 攻击,但是对于绝大多数攻击仍无能为力。存储过程有助于避免 SQL injection 的常用方式是限制可作为参数传入的指令类型。但是,有许多方法都可以绕过这一限制,许多危险的表达式仍可以传入存储过程。所以再次强调,存储过程在某些情况下可以避免这种攻击,但是并不能完全保护您的应用系统抵御 SQL injection 的攻击。

Recommendations

造成 SQL injection 攻击的根本原因在于攻击者可以改变 SQL 查询的上下文,使程序员原本要作为数据解析的数值,被篡改为命令了。当构造一个 SQL 查询时,程序员应当清楚,哪些输入的数据将会成为命令的一部分,而哪些仅仅是作为数据。参数化 SQL 指令可以防止直接窜改上下文,避免几乎所有的 SQL injection 攻击。参数化 SQL 指令是用常规的 SQL 字符串构造的,但是当需要加入用户输入的数据时,它们就需要使用捆绑参数,这些捆绑参数是一些占位符,用来存放随后插入的数据。换言之,捆绑参数可以使程序员清楚地分辨数据库中的数据,即其中有哪些输入可以看作命令的一部分,哪些输入可以看作数据。这样,当程序准备执行某个指令时,它可以详细地告知数据库,每一个捆绑参数所使用的运行时的值,而不会被解析成对该命令的修改。 当连接到 MySQL 时,可以将前面的例子改写为使用参数化 SQL 指令(而不是联结用户输入的字符串),如下所示:

mysqli = new mysqli(host,dbuser, dbpass, $db);

userName = _SESSION['userName'];

itemName = _POST['itemName'];

$query = "SELECT * FROM items WHERE owner = ? AND itemname = ?";

stmt = mysqli->prepare($query);

stmt-\>bind_param('ss',username,$itemName);

$stmt->execute();

MySQL Improved 扩展 (mysqli) 适用于 MySQL 中的 PHP5 用户。依赖于不同数据库的代码应该检查相似的扩展名。 更加复杂的情况常常出现在报表生成代码中,因为这时需要通过用户输入来改变 SQL 指令的命令结构,比如在 WHERE 条件子句中加入动态的约束条件。不要因为这一需求,就无条件地接受连续的用户输入,从而创建查询语句字符串。当必须要根据用户输入来改变命令结构时,可以使用间接的方法来防止 SQL injection 攻击:创建一个合法的字符串集合,使其对应于可能要加入到 SQL 指令中的不同元素。在构造一个 SQL 指令时,可使用来自用户的输入,以便从应用程序控制的值集合中进行选择。

个人认为最简单的方法是直接过滤掉SQL关键字,以及=,单引号等。

相关推荐
恰薯条的屑海鸥4 小时前
零基础在实践中学习网络安全-皮卡丘靶场(第十四期-XXE模块)
网络·学习·安全·web安全·渗透测试
20242817李臻4 小时前
20242817李臻-安全文件传输系统-项目验收
数据库·安全
DevSecOps选型指南13 小时前
2025软件供应链安全最佳实践︱证券DevSecOps下供应链与开源治理实践
网络·安全·web安全·开源·代码审计·软件供应链安全
ABB自动化13 小时前
for AC500 PLCs 3ADR025003M9903的安全说明
服务器·安全·机器人
恰薯条的屑海鸥13 小时前
零基础在实践中学习网络安全-皮卡丘靶场(第十六期-SSRF模块)
数据库·学习·安全·web安全·渗透测试·网络安全学习
阿部多瑞 ABU14 小时前
主流大语言模型安全性测试(三):阿拉伯语越狱提示词下的表现与分析
人工智能·安全·ai·语言模型·安全性测试
moongoblin17 小时前
行业赋能篇-2-能源行业安全运维升级
运维·安全·协作
Fortinet_CHINA17 小时前
引领AI安全新时代 Accelerate 2025北亚巡展·北京站成功举办
网络·安全
这儿有一堆花18 小时前
安全访问家中 Linux 服务器的远程方案 —— 专为单用户场景设计
linux·服务器·安全
饮长安千年月21 小时前
JavaSec-SpringBoot框架
java·spring boot·后端·计算机网络·安全·web安全·网络安全