注入攻击
OWASP将注入攻击和跨站脚本攻击(XSS)列入网络应用程序十大常见安全风险。实际上,它们会一起出现,因为 XSS 攻击依赖于注入攻击的成功。虽然这是最明显的组合关系,但是注入攻击带来的不仅仅是 XSS。
注入攻击代指一类攻击,它们通过注入数据到一个网络应用程序以期获得执行,亦或是通过非预期的一个方式来执行恶意数据。这种类别的攻击包括跨站脚本攻击(XSS)、SQL 注入攻击、头部注入攻击、日志注入攻击和全路径暴露。当然限于篇幅,这里只是一个简单的介绍。
这类攻击是每个程序员的梦魇。它们数量庞大、攻击范围广,并且有时候防御措施很复杂,因此是最常见、成功率最高的网络攻击。所有的应用程序都需要从某个地方获取数据来运行。跨站脚本攻击和界面伪装漏洞最为常见,并且它们本身就已经非常重要,通常与注入攻击分开归类。接下来的一章我将单独介绍它们。
OWASP 对注入攻击的定义如下:
类似SQL、OS、LDAP注入攻击等注入攻击会在不可信数据作为命令或请求的一部分被发送到解释程序时发生。攻击者的恶意数据会迷惑解释程序去执行非计划的命令,或访问非授权的数据。
SQL 注入攻击
目前最常见的注入攻击形式是臭名昭著的 SQL 注入攻击。SQL 注入攻击不仅常见,而且致命。我要特别强调,了解这种攻击、实现攻击的条件以及防御攻击需要采取的措施极为重要。
SQL 注入攻击通过将数据注入网络应用程序,然后被用于 SQL 请求来操作。数据通常来自类似网页表单的不可信来源。不过,数据也可能来自包括数据库本身在内的其他来源。程序员通常会信任来自自己数据库的数据,以为它们是非常安全的,却没有意识到,在一种用法中安全,不代表它在所有其他用法中都是安全的。来自数据库在经过证明(比如说,通过验证流程)之前,应该被视为不可信。
如果攻击成功,SQL 注入攻击能够操纵受攻击的 SQL 请求,从而进行非程序员意愿的数据库操作。
看一下这条请求:
git·$db = new mysqli('localhost', 'username', 'password', 'storedb'); $result = $db->query( 'SELECT * FROM transactions WHERE user_id = ' . $_POST['user_id'] ); ·git
上面的请求中存在多处问题。首先,我们还没有验证 POST 数据来确保这是个有效的 user_id。其次,我们允许一个不可信来源告诉我们要使用哪个 user_id——攻击者可以任意设置一个有效 user_id。也许 user_id 包含在一个隐藏的表单字段,因为网络表单不允许编辑,我们就以为安全了(却不知道攻击者可以提交任何信息)。第三,我们并没有 escape 该 user_id,或将其作为一个绑定参数传给请求,由于我们一开始没有验证 SQL 请求,这就让攻击者有机会注入任意字符串来操纵该请求。
上述三点问题在网络应用程序中极其常见。
至于信任来自数据库的数据,想象一下我们使用 user_name 字段来搜索交易。用户名的范围相当广阔,可能还包含引用。可以想见,攻击者可以在一个用户名内储存一个 SQL 注入字符串。如果我们将数据库视为可信的数据来源,没能合理地 escape 或约束它,当我们在后续请求中再次使用该字符串时,它就可以操纵请求字符串。
另一个需要注意的 SQL 注入攻击因素是永久存储不需要总是在服务器上进行。HTML5 支持使用客户端数据库,可以借助 Javascript 使用 SQL 来查询。有两个支持这项操作的接口:WebSQL 和 IndexedDB。WebSQL 于2010年被 W3C 弃用,受到后台使用 SQLite 的 WebKit 浏览器支持。虽然这个接口不被推荐使用,但是 WebKit 处于后台兼容考虑,很有可能会继续支持它。正如它的名字所示,它接收 SQL 请求,因此容易遭受 SQL 注入攻击。IndexedDB 是一个新的备选,不过它是一种 NOSQL 数据库(不需要使用 SQL 查询)。
SQL 注入攻击范例
尝试操纵 SQL 命令的目标包含以下几种:
信息泄露
披露存储数据
操纵存储数据
避开权限管理
客户端 SQL 注入攻击
信息泄露
披露存储数据
操纵存储数据
避开权限管理
防御 SQL 注入攻击
防御 SQL 注入攻击可采用深度防御原则。在将数据用于 SQL 命令之前,应该进行验证,以确保它是我们期望的正确格式,并且在将数据包含在请求或绑定参数前,应该将其 escape。
验证
第二章讲述输入验证,而且正如我在其中提到的,我们应该假设不是由当前请求的 PHP 源代码直接生成的所有数据都不可信。对其严格验证,并且拒绝所有未通过验证的数据。不要尝试“修复”数据,除非只是简单修正数据格式。
常见的验证错误包括只验证数据当下用途(例如,展示或计算),却不考虑数据最终存储位置的数据库表字段的验证需求。
Escaping
通过使用mysqli 扩展,你可以利用 mysqli_real_escape_string() 函数来 escape包含在 SQL 查询中的所有数据。PostgresSQL 的 pgsql 扩展提供 pg_escape_bytea()、 pg_escape_identifier()、 pg_escape_literal() 和 pg_escape_string() 函数。Mssql(微软 SQL 服务器)不提供 escaping 功能,而经常被推荐的 addslashes() 方法并不够用——你实际上需要一个定制功能http://stackoverflow.com/questions/574805/how-to-escape-strings-in-mssql-using-php。
再告诉你一件头疼的事,你绝对绝对不能在 escape 进入 SQL 查询数据上出错。一旦失手,可能就会引发 SQL 注入攻击。
基于以上原因,并不推荐使用 escaping。它可以用来救急,如果你用来抽象的数据库程序库不强制参数绑定就能进行 SQL 查询,可能就需要使用它。否则你应该避免使用 escape。它很混乱,容易出错,而且因数据库扩展不同而存在差异。
参数化查询(预处理语句)
参数化或参数绑定是构建 SQL 查询的推荐方法,而且所有优秀的数据库程序库都默认使用这种方法。以下是使用 PHP 的 PDO 扩展的一个实例。
if(ctype_digit($_POST['id']) && is_int($_POST['id']))
{
$validatedId = $_POST['id'];
$pdo = new PDO('mysql:store.db');
$stmt = $pdo->prepare('SELECT * FROM transactions WHERE user_id = :id');
$stmt->bindParam(':id', $validatedId, PDO::PARAM_INT);
$stmt->execute();
} else {
// reject id value and report error to user
}
PDO 语句可用的bindParam() 方法让你可以给预处理语句中出现的占位符绑定参数,并且接受基本的数据类型参数,例如 PDO::PARAM_INT、 PDO::PARAM_BOOL、 PDO::PARAM_LOB 和 PDO::PARAM_STR。这种方法默认使用 PDO::PARAM_STR,因此记得对其他数值做相应调整!
不同于手动 escape,这种方式下的参数绑定(或者你的数据库使用的方法)会自动正确地 escape 绑定的数据,因此你不需要回忆之前用了哪种 escape 函数。持续使用参数绑定要比记着手动 escape 所有东西要可靠得多。
强制实施最小特权原则
制止已经发生的 SQL 注入攻击跟从一开始就防御同样重要。一旦攻击者获得执行 SQL 查询的能力,他们就会以一个数据库用户的身份进行查询。可以通过确保所有数据库用户只得到完成各自任务必需的权限,来执行最小特权原则。
如果一个数据库用户拥有很大的权限,攻击者就可能删除数据表,操纵其他用户的权限,从而发起其他 SQL 注入攻击。你绝对不能以超级用户、其他权限较高或管理员层级的用户身份访问网络应用程序的数据库,从而杜绝这种情况发生。
最小特权原则的另外一个变体是区别数据库的读数据和写数据权限。你可以设置一个拥有写数据权限的用户,和另一个只有读数据权限的用户。这种角色区分可以确保在 SQL 注入攻击目标为只读用户时,攻击者无法写数据或操纵表数据。这种生物隔离区划可以延伸到进一步限制访问权限,这样就可以将 SQL 注入攻击的影响最小化。
很多网络应用程序,尤其是开源应用程序,特别被设计成只有一个数据库用户,而且几乎从来不会有人检查该用户是不是高度特免的。记住以上观点,忍住诱惑,不要在一个管理员用户下运行这样的网络应用程序。
代码注入攻击(也叫Remote File Inclusion)
代码注入攻击指的是任何允许攻击者在网络应用程序中注入源代码,从而得到解读和执行的方法。这并不适用于对应用程序客户端的代码注入攻击,例如 Javascript,那属于跨站脚本攻击(XSS)。
源代码可以通过不可信的输入直接注入,或者网络应用程序在通过本地文件系统或类似 URL 这样的外部来源加载代码时被操纵。包含远程文件导致代码注入攻击的情况通常被称为远程文件包含漏洞,虽然远程文件包含攻击本身的目的就是为了注入代码。
造成代码注入攻击的初始原因包括输入验证失败,包含可能被当做 PHP 代码、任何语境下的不可信输入,未能保障源代码库的安全,在下载第三方程序库时不够谨慎,服务器配置不当导致非 PHP 文件通过网络服务器被传送到 PHP 解释程序。最后一点尤其要加以注意,因为它意味着不可信用户上传到服务器的所有文件都可能带来极大的风险。
代码注入攻击范例
众所周知,PHP 涉及无数代码注入攻击目标,因此任何一位程序员都要高度关注代码注入攻击问题。
文件包含攻击
代码注入攻击最明显的目标就是 include()、 include_once()、 require() 和 require_once() 函数。如果不可信输入被允许来决定传输给这些函数的路径参数,它就可能影响被包含的本地文件。应该注意的是,被包含的文件不一定是一个真正的 PHP 文件,任何能够携带文字数据(例如,几乎所有的)的被包含文件都有可能。
路径参数也可能遭受目录遍历攻击或远程文件包含攻击。在路径中使用 ../ 或 ..(dot-dot-slash) 字符串会让攻击者能够触及 PHP 流程能够访问的所有文件。除非 XXX 被禁用,否则以上函数还能接受 PHP 默认设置中的 URL。
评估
PHP 的 eval() 函数接收 PHP 代码字符串并执行。
正则表示式注入攻击
PHP 中 PCRE 的 preg_replace() 函数允许“e”(PREG_REPLACE_EVAL)修饰符,这就意味着替换字符串在替换后将被当成 PHP。用于替换字符串的不可信输入可能会注入即将执行的 PHP 代码。
缺陷文件包含逻辑攻击
按照定义,网络应用程序会包含满足各种需求所需的各类文件。通过操纵请求路径或请求参数,它可以利用服务器路由的缺陷逻辑、依赖管理、自动加载或其他流程,导致服务器包含非预期的本地文件。
这些超出网络应用程序设计初衷的操纵可能带来无法预料的影响。比如说,一个应用程序可能无意中暴露了只用于命令行用法的路径。该应用程序可能还暴露了构造函数用来执行任务的其他类(虽然并不推荐这种设计类的方法,不过还是有人这么做)。两种场景都有可能干扰应用程序的后台运行,导致本来不应该被直接访问的资源密集型运行活动遭受数据操纵或拒绝服务攻击(DOS)。
服务器配置不当
代码注入攻击的目标
由于代码注入攻击允许攻击者选择任意 PHP 代码来执行代码注入攻击的目标极其广泛。
防御代码注入攻击
命令注入攻击
命令注入攻击范例
防御命令注入攻击
原文地址:Injection Attacks
本文系 OneAPM 工程师编译整理。OneAPM 是应用性能管理领域的新兴领军企业,能帮助企业用户和开发者轻松实现:缓慢的程序代码和 SQL 语句的实时抓取。想阅读更多技术文章,请访问 OneAPM 官方博客。
本文转自 OneAPM 官方博客