澳门新浦京手机版Web Hacking 101 中文版 十一、SQL 注入

1. 不要使用 mysql_ 函数

这一天终于来了,从此你不仅仅“不应该”使用mysql_函数。PHP 7
已经把它们从核心中全部移除了,也就是说你需要迁移到好得多的mysqli_函数,或者更灵活的
PDO 实现。

澳门新浦京手机版 1

十一、SQL 注入

作者:Peter Yaworski

译者:飞龙

协议:CC BY-NC-SA
4.0

2011年将被记住,因为这一年SQL将死;这一年,关系数据库从一线退下;这一年开发人员发现他们没必要为了持久化数据,而将每个对象转化为表格结构。

2. 不要编写垃圾代码

这一条可能易于理解,但是会变得越来越重要,因为 PHP 7
的速度提升可能会隐藏你的一些问题。不要仅仅满足于你的站点速度,因为迁移到
PHP 7 才让它变快。

为了理解速度有多重要,以及如何把事情做得更好,请看一看我们的文章速度优化入门指南。

作为一名开发者,你应该总是确保按需加载脚本,尽可能连接它们,编写高效的数据库查询,尽可能使用缓存,以及其它。

描述

SQL 注入,或者 SQLi 允许黑客将 SQL
语句注入到目标中并访问它们的数据库。它的潜力是无穷的,通常使其成为高回报的漏洞,例如,攻击者能够执行所有或一些
CURD
操作(创建、读取、更新、删除)来获取数据库信息。攻击者甚至能够完成远程命令执行。

SQLi
攻击通常是未转义输入的结果,输入被传给站点,并用作数据库查询的一部分。它的一个例子是:

$name = $_GET['name']; 
$query = "SELECT * FROM users WHERE name = $name";

这里,来自用户输入的传入值直接被插入到了数据库查询中。如果用户输入了test' or 1=1,查询就会返回第一条记录,其中name = test or 1=1,所以为第一行。现在在其他情况下,你可能会得到:

$query = "SELECT * FROM users WHERE (name = $name AND password = 12345");

这里,如果你使用了相同的载荷,你的语句最后会变成:

$query = "SELECT * FROM users WHERE (name = 'test' OR 1=1 AND password = 12345");

所以这里,查询会表现得有些不同(至少是
MySQL)。我们会获取所有记录,其中名称是test,或者密码是12345。很显然我们没有完成搜索数据库第一条记录的目标。因此,我们需要忽略密码参数,并能够使用注释来实现,test' or 1=1;--。这里,我们所做的事情,就是添加一个分号来合理结束
SQL
语句,并且立即添加两个短横线(和一个空格)来把后面的所有东西标记为注释。因此不会被求职。它的结果会和我们初始的例子一样。

2011年是文档数据库的一年,尽管一直在稳步发展势头,通过过去八年多的发展,现在有各种稳定的文档数据库—-从基于亚马逊和谷歌的云,到各种开放源码工具,尤其是MongoDB。

3. 不要在文件末尾使用 PHP 闭合标签

你可以看一看,当一个文件以 PHP 代码结尾时,WordPress
多数核心代码都把末尾的 PHP 标签去掉了。实际上,Zend
框架特别禁止了它。PHP
并不需要文件末尾的闭合标签,并且我们可以通过去掉它来保证不会在后面添加任何的空白字符。

示例

那么,MongoDB是什么?这里的五件事是每个开发人员应该知道的:

4. 不要做不必要的引用传递

我个人不喜欢引用传递。我知道有时候它很实用,但是其它情况下它使代码变得难懂,并且更难预测结果。

据说一些人认为它使代码运行更快,但是根据一些 PHP 高级程序员所说,这并不正确。

说明引用为什么不好的一个例子是,PHP
内建了shuffle()和sort()。它们修改原始数组,而不是返回处理后的数组,这很不合逻辑。

1. Drupal SQL 注入

难度:中

URL:任意版本小于 7.32 的 Drupal 站点

报告链接;https://hackerone.com/reports/31756

报告日期:2014.10.17

奖金:$3000

描述:

Drupal 是一个流行的内容管理系统,用于构建网站,非常相思雨 WordPress 和
Joomla。它以 PHP
编写,并且基于模块,意思是新的功能可以通过安装模块来添加到 Drupal
站点中。Drupal
社区已经编写了上千个,并且使他们可免费获取。其中的例子包括电子商务,三方继承,内容产品,以及其他。但是,每个
Drupal
的安装都包含想用的核心模块系列,用于运行平台,并且需要数据库的链接。这些通常都以
Drupal 核心来指代。

在 2014 年,Drupal 安全小组为 Drupal
核心发布了一个紧急安全更新,表明所有 Drupal 站点都存在 SQL
注入漏洞,它能够由匿名用户来完成。这一漏洞允许攻击者控制任意没有更新的
Drupal 站点。

对于漏洞来说, Stefan Horst 发现了 Drupal
开发者不当实现了数据库查询的包装功能,它能够被攻击者滥用。更具体来说,Drupal
使用 PHP 数据对象(PDO)作为结构用于访问数据库。Drupal
核心的开发者编写了代码来调用这些 PDO 函数,并且在其他开发者编写代码来和
Drupal
数据库交互的任何时候,这些代码都可以使用。这在软件开发中是个最佳时间。它的原因是为了让
Drupal
能够用于不同类型的数据库(MySQL、Postgres,一起其它),移除复杂性并提供标准化。

现在结果是,Stefan 发现了 Drupal 包装器代码对传给 SQL
查询的数组数据做了一个错误的假设。这里是原始代码:

foreach ($data as $i => $value) { 
    [...] 
    $new_keys[$key . '_' . $i] = $value; 
}

你能够之处错误(我都不能)嘛?开发者的假设为,数组数据始终含有数字键,例如0, 1, 2以及其他($i的值)。并且所以它们将$key变量连接到$i,并且使其等于value。这里是来自
Drupal 的db_query函数,通常的查询的样子。

db_query("SELECT * FROM {users} WHERE name IN (:name)", array(':name'=>array('user1','user2')));

这里,db_query函数接受数据库查询SELECT * FROM {users} WHERE name IN (:name),以及值的数组来替换查询中的占位符。在
PHP
中,当你将数组声明为array('value','value2',value3'),它实际上创建了[0 =>'value',1=>'value2',2=>'value3'],其中每个值都可以通过数字键来访问。所以这里,:name变量被数组中的值替换。你从中获取到的东西是:

SELECT * FROM users WHERE name IN (:name_0, :name_1)

到目前为止很好。当你获取不含有数字键的数组时,问题就来了,像这样:

db_query("SELECT * FROM {users} where name IN (:name)", 
array(':name'=>array('test) -- ' => 'user1','test' => 'user2')));

这里,:name是个数组,它的键是'test) –', 'test'。你可以看到为什么嘛?当
Drupal 收到它并且处理数组来创建查询时,我们会得到:

SELECT * FROM users WHERE name IN (:name_test) -- , :name_test)

看出这是为什么可能需要一些技巧,所以让我们过一遍它。基于上面描述的foreach,Drupal
会遍历数组中的每个元素。所以,对于第一个迭代$i = test) –以及$value = user1。现在,$key是查询中的(:name),并且和$i组合之后,我们得到了name_test) –。对于第二个迭代,$i = test并且$value = user2,所以组合$key$i之后,我们得到了name_test,结果是个:name_test的占位符,它等于user2

现在,知道这些之后,Drupal 包装 PHP PDO 对象的事实就登场了,因为 PDO
允许多重查询。所以,攻击者能够传递恶意输入,例如实际的 SQL
查询来为任何的数组键创建管理员用户,它作为多重查询解释和执行。

重要结论

SQLi
似乎更难于发现,至少基于为了这本书搜索的报告。这个例子很有意思,因为它并不是提交单引号和截断查询。反之,它全部关于
Drupal
的代码如何处理传给内部函数的数组。这并不易于通过黑盒测试发现(其中你并不接触任何代码)。这里的重要结论是,寻找机会来修改传给站点的输入格式,所以在
URL
接受?name作为参数的地方,尝试传入类似?name[]的数组,来观察站点如何处理。它也可能不会造成
SQLi,但是可能会导致其他有趣的行为。

1) MongoDB是一个独立的服务器;

5. 不要在循环中执行查询

在循环中执行查询非常浪费。它给你的系统施加不必要的压力,并且可能能够在循环外部更快获得相同结果。当我遇到需要这样的情况时,我通常会使用两个分离的查询来解决问题,我会使用它们来构建数据数组。之后我会遍历数组,并不需要在这个过程中执行查询。

由于 WordPress 适用于这里,它可能有一些例外。虽然get_post_meta()
会从数据库获取大量数据,如果你正在遍历某个特殊博文的元数据你可以在循环中使用它。这是因为当你第一次调用它的时候,WordPress实际上会获取所有元数据并缓存它们。后续的调用使用这些缓存数据,没有数据库的调用。

弄懂这些的最佳方式是阅读函数文档,以及使用类似 Query Monitor 的工具。

总结

SQLi 对站点来说十分重要和危险。寻找这一类型的漏洞可能导致站点的完整的
CURD 权限。在其他情况下,它可能扩展为远程代码执行。Drupal
的例子实际上是这些例子之一,它们证明了攻击者可以通过漏洞来执行代码。在寻找它们的时候,不要仅仅留意向查询传递未转义单引号和双引号的可能性,也要注意以非预期方式提供数据的可能性,例如在
POST 数据中提交数组参数。

如MySQL或PostreSQL
一样,MongoDB提供侦听端口以便接入。它提供了用于查询,创建,更新和删除的工具。从理论上讲,你使用它的工作方式相同:连接,执行任务并关闭连接。

6. 不要在 SQL 查询中使用 *

当然,这个更像 MySQL 的问题,但是我们习惯在 PHP 中编写 SQL
代码,所以都差不多。无论如何,如果可以避免的话,不要在 SQL
查询里使用通配符,尤其是数据库有很多列的时候。

你应该明确指定需要哪些行,并且仅仅获取它们。这有助于减少所用资源,保护数据,以及让事情变得尽可能清晰。

对于
SQL,你需要了解所有可用的函数,并且尽可能测试其速度。在计算均值、求和或计算类似数值时,要使用
SQL 函数而不是 PHP
函数。如果你不确定某个查询的速度,测试它并且尝试一些其它的编译 —
之后使用最好的那个。

2)它是基于文档的,而不是基于表格的;

7. 不要信任用户输入

信任用户输入是不明智的。始终校验、过滤、转义、检查并留好退路。用户数据存在三个问题:我们开发者并没有考虑每种可能性,它通常不正确,以及它可能是蓄意破坏。

经过周密考虑的系统可以防护这些威胁。要确保使用类似filter_var()的内建函数检查适当的值,以及在处理数据库时转义(或预编译)。

WordPress 拥有一些函数来解决问题。详见文章校验、转义和过滤用户数据。

MongoDB
没有结构化语言。如果你想创建一个新的文档类型,你不用做任何事来告诉数据库关于这些数据的结构,而仅仅是存到数据库中即可。

8. 不要故作聪明

你的目标应该是编写优雅的代码,来更清晰地表达你的意图。你可能能够通过将任何东西缩短为一个单词的变量,使用多层的三元逻辑,以及其它手段,从每个页面中优化
0.01 秒。但这只会给你和你周围的人产生大麻烦。

合理命名变量,为代码编写文档,优先选择清晰而不是简洁。甚至还可以更好,使用标准的面向对象代码,它本身或多或少就是文档,不需要一大堆内联数值。

简单的说,MongoDB使用类似JavaScript或PHP
的类型处理方式。也就是说,数据库是灵活的弱类型。

9. 不要重新发明轮子

PHP
到现在为止有很长时间了,网站被造出来的时间更长。很可能无论你需要造出什么,一些人之前早就造出来了。不要害怕向他人寻求支持,Github是你的好朋友,Composer也是,Packagist也是。

从日志工具到调色工具,从性能分析器到单元测试框架,从 Mailchimp API 到
Twitter Bootstrap,每个东西都可以通过按下按键(或者敲下命令)来获取,使用它们吧!

虽然有一些数据是有限制条件的,但在大多数情况下,你可以像写PHP代码一样编写你的MongoDB代码。

10. 不要忽略其它语言

如果你是个 PHP 程序员,现在有个好机会去至少了解 HTML、CSS、JavaScript 和
MySQL。当你能够更好地处理这些语言时,就是重新学习 JavaScript
的时机了。JavaScript 并不是 jQuery,你应该合理地学习 JavaScript
来更高效地使用它。

我也打算向你推荐学习面向对象的
PHP,它可以节省时间,并且在代码规模更大时会变得更好。对于类似 C# 和
Java 的语言,在你了解 OOP 之后,它们也更易于理解。

通过了解包管理器、构建脚本、CoffeeScript、LESS、SASS、YAML
、脚本引擎和其它强大的工具来扩展你的知识面。我强烈向你推荐看一看其它框架,尤其是
Laravel。

当你使用它们出色完成任务时,学习 Ruby、RoR、Android、iPhone 和 Windows
Phone
应用开发如何?你可能会认为这毫无意义,因为它们在你的舒适区和工作所需范围之外,但是这就是它们的意义。每种语言都有一些要学习的实用的东西,以及从没碰到的新知识。所有
PHP 顶级开发者都懂得很多其它编程语言,这并非偶然。

3)它是非结构化的;

还记得这些你写的数据库抽象层吗?还记得那些你处理过的ORM层吗?现在,你可以将它们全部丢弃。在MongoDB中你不需要他们。MongoDB没有很多查询语句。在大多数情况下,只需给它一个数组指定你想要的信息,然后它会给你返回文档的数组。如果你想运行一些非常复杂的查询(如Map-Reduce操作),可以向MongoDB传递JavaScript,其内部的JavaScript引擎可以解析这个脚本。

4)不必去学习另一种查询语言;

开发时间也短,因为没有结构需要管理和很少的数据映射。

学习曲线很平滑,因为没有新的查询语言学习。代码是简洁的。毕竟,无须任何其他ORM,封装可以非常简单。你的代码是未来的保证。向你的对象增加更多的字段是很轻松的。因此,需求变化了,你可以很快修改代码以便适应。

MongoDB足以让我意识到它有改变游戏规则的潜力。这也是让大家主张使用新一代的文档数据库代替基于SQL的关系数据库的原因。将关系数据库留在尘土里,更可能的是让它们做它们能做好的事情:存储属于行和表的数据。

MongoDB
是用C++开发的面向文档的数据库,也就是反传统的数据库范式来设计的,把相关的对象都记录到一个文档里,每个文档内是schema-free的,也就是列名可以自由定义,比较灵活,特别是面对业务逻辑多变的应用场景十分给力。数据以BSON的格式二进制存储。不好的地方就是可能带来一定的数据冗余和存储开销。

另外,MongoDB的索引机制和MySQL等数据库是一样的,可以利用传统的关系数据库的经验来使用MongoDB的索引。

不像其他很多NoSQL产品由个别工程师根据应用场景开发出来的,MongoDB是有一个专门的公司
10gen
来维护。有一点要注意的是,MongoDB自己是不管理内存的,无法指定内存大小,完全交给操作系统来管理,因此有时候是不可控的,在生产环境使用必须在OS层面监控内存使用情况。

5)它具有强大的主流开发语言支持,如C#、C++、Java、PHP、Perl、Python、Ruby。

发表评论

电子邮件地址不会被公开。 必填项已用*标注