注入攻击
OWASP 网站将注入攻击和跨域脚本攻击(XSS),列入到排名前十的web应用程序最常见的攻击手段。然而,情况可能更加恶劣,并不局限使用某一种攻击手段,而是注入攻击和XSS结伴而行。
注入攻击是一套完备的攻击手段,通过向 Web 应用程序注入精心准备的、带有深深恶意的、意料之外的数据,让 Web 程序执行。它通常包括 XSS 攻击、SQL 注入、消息头注入(Header Injection)、日志注入(Log Injection) 和 全路径扫描(Full Path Disclosure)。
注入攻击是每个研发人员的梦靥。这是由于注入攻击的手段繁多、攻击范围广、防御手段复杂,因而它也是最常见、最成功的攻击手段。所有的应用程序都需要使用存储空间存放数据,提供程序执行。而 XSS 跨域脚本攻击和用户界面(UI redress) 如此常见,所以对它们我们将进行单独讲解。
OWASP 网站是这样定义注入攻击的:
Injection flaws, such as SQL, OS, and LDAP injection, occur when untrusted data is sent to an interpreter as part of a command or query. The attacker’s hostile data can trick the interpreter into executing unintended commands or accessing unauthorized data.
SQL 注入
到目前为止,最常见的注入攻击形式是臭名昭著的SQL注入。SQL注入不仅非常常见,而且攻击效果致命。我几乎无法用言语描述 SQL 注入攻击的危害程度,因为 SQL 入住的危害性取决于我们所做的防范程度。
SQL 注入是通过将意料之外的数据提交到(即注入)一个web应用程序中,然后在项目查询结果时,获取访问受限的结果达到攻击的目的。 数据通常来自不受信任的输入,比如web表单,不过也可能来自数据库本身的不安全。研发人员一般会忽视数据库本身的安全问题,数据库的安全在某一种或几种特定场景下是安全的这没问题,但并非意味着所有场景下数据库都是安全的,除非数据库的数据被校验过是安全的数据,比如数据已通过校验器的校验处理。
一个成功的 SQL 注入攻击,可以以研发人员未授权的方式通过 SQL 查询语句执行数据库操作。
$db = new mysqli('localhost', 'username', 'password', 'storedb');
$result = $db->query(
'SELECT * FROM transactions WHERE user_id = ' . $_POST['user_id']
);
这段代码有几个问题:
- 没有校验用户提交的 POST 数据 user_id
- 项目允许不可靠来源的数据使用哪个 user_id - 攻击者可以使用任意 user_id 达到攻击的目的,即使表单中的 user_id 字段来自隐藏表单,不过不要忘了攻击者可以提交任意数据
- 没有对输入的 user_id 进行转义,也没有进行数据绑定查询,要知道我们并没有进行第一步的数据校验处理
上面三点问题在 Web 应用中十分常见。
至于信任数据库中的数据,假设我们使用 user_name 字段搜索查询。但 user_name 的命名规则十分宽松,如果允许包含引号(')。可以想象,攻击者可以在 user_name 的值设置为一个SQL注入字符串。当我们在以后的查询中使用该字符串时,如果我们认为数据库是可信的数据来源,并不能正确地摆脱或绑定它,那么它就会操作查询字符串。
另一个SQL注入引起注意的因素是持久存储并不总是发生在服务器上。HTML5支持使用客户端数据库,在Javascript的帮助下可以使用SQL查询。有两个api可促进这一点:WebSQL和IndexedDB。WebSQL在2010年被W3C弃用,在后端使用SQLite支持WebKit浏览器。它在WebKit中的支持很可能会继续向后兼容的目的,即使它不再被推荐使用。顾名思义,它接受SQL查询,因此可能容易受到SQL注入攻击。IndexedDB是较新的替代方法,但它是NOSQL数据库(即不需要使用SQL查询)。
SQL 注入实例
SQL 注入的目标包括以下几个方面:
- 信息泄漏
- 获取存储数据
- 操作存储数据
- 绕过授权控制
客户端 SQL 注入
防御SQL注入攻击最重要额一个原则是在提交表单时,对提交数据进行数据校验处理,在 SQL 查询时对数据进行转移处理或绑定参数。
校验(validation)
在第 2 章我们已经了解了关于输入验证相关知识,要点就是假定所有请求 PHP 服务的数据都需要认定为不可信任的,都需要进行严格校验和拒绝所有不合法数据。永远不要试图修复数据,除非仅仅是稍微的格式化处理。
对数据校验处理的一个常见错误就是,仅考虑当前数据使用时的校验(如用于数据显示或计算),而忽略了数据最终存储到数据库表字段时的校验处理。
转义(Escaping)
PHP 研发过程中使用 mysqli 扩展时,可以使用 mysqlirealescape_string() 函数,在 SQL 查询时对数据进行转义。如果是 PostgresSQL 数据库,你可以使用 pgsql 扩展,该扩展提供了 pg_escape_bytea(), pg_escape_identifier(), pg_escape_literal() 和pg_escapestring() 函数。用于微软的 SQL Server 数据库扩展 mssql 则没有提供相关转义函数,但仅通过 addslashes() 函数转义处理还不够,你还需要一个通用转义函数库
让你头痛的还不止于此,因为你可能永远无法摆脱 SQL 注入的风险,一个微小的错误,都有可能引发 SQL 注入攻击。
基于以上原因,不推荐逃跑。如果您用于抽象的数据库库允许在不强制执行参数绑定的情况下设置裸SQL查询或查询部分,那么它将在必要时进行,并且可能是必要的。否则,你应该避免完全逃避。它是凌乱的、易出错的,而且与数据库扩展不同。
出于以上原因,对数据进行转义处理并非推荐防御 SQL 注入的解决方案。更好的方案是使用 PDO 扩展,它使用绑定参数的方法替代原生的 SQL 语句,使用参数绑定能够避免出现转义处理的问题,如易出错、杂乱和不同数据库实现差异等。
参数绑定(parameterised queries(prepared statements))
To be continue