【php精品源码栏目提醒】:网学会员鉴于大家对php精品源码十分关注,论文会员在此为大家搜集整理了“PHP中防止SQL注入 - 其它资料”一文,供大家参考学习
在本系列文章中,我们将全面探讨如何在
PHP 开发环境中全面阻止 SQL 注入式攻击,并给出一个具体的开发示例。
一、 引言
PHP 是一种力量强大但相当容易学习的服务器端脚本语言,即使是经验不多的程序员也能够使用它来创建复杂的动态的 web 站点。
然而,它在实现因特网服务的秘密和安全方面却常常存在许多困难。
在本系列文章中,我们将向读者介绍进行 web 开发所必需的安全背景以及
PHP 特定的知识和代码-你可以借以保护你自己的 web 应用程序的安全性和一致性。
首先,我羌虻サ鼗毓艘幌路 衿靼踩 侍展示你如何存取一个共享宿主环境下的私人信息,使开发者脱离开生产服务器,维持最新的软件,提供加密的频道,并且控制对你的系统的存取。
然后,我们讨论
PHP 脚本实现中的普遍存在的脆弱性。
我们将解释如何保护你的脚本免于 SQL 注入,防止跨站点脚本化和远程执行,并且阻止对临时文件及会话的quot劫持quot。
在最后一篇中,我们将实现一个安全的 Web 应用程序。
你将学习如何验证用户身份,授权并跟踪应用程序使用,避免数据损失,安全地执行高风险性的系统命令,并能够安全地使用 web 服务。
无论你是否有足够的
PHP 安全开发经验,本系列文章都会提供丰富的信息来帮助你构建更为安全的在线应用程序。
二、 什么是 SQL 注入 如果你打算永远不使用某些数据的话,那么把它们存储于一个数据库是毫无意义的; 因为数据库的设计目的是为了方便地存取和操作数据库中的数据。
但是,如果只是简单地这样做则有可能会导致潜在的灾难。
这种情况并不主要是因为你自己可能偶然删除数据库中的一切;而是因为,当你试图完成某项quot无辜quot的任务时,你有可能被某些人所quot劫持quot-使用他自己的破坏性数据来取代你自己的数据。
我们称这种取代为quot注入quot。
其实,每当你要求用户输入构造一个数据库查询,你是在允许该用户参与构建一个存取数据库服务器的命令。
一位友好的用户可能对实现这样的操作感觉很满意;然而,一位恶意的用户将会试图发现一种方法来扭曲该命令,从而导致该被的扭曲命令删除数据,甚至做出更为危险的事情。
作为一个程序员,你的任务是寻找一种方法来避免这样的恶意攻击。
三、 SQL 注入工作原理 构造一个数据库查询是一个非常直接的过程。
典型地,它会遵循如下思路来实现。
仅为说明问题,我们将假定你有一个葡萄酒数据库表格quotwinesquot,其中有一个字段为quotvarietyquot(即葡萄酒类型): 1. 提供一个表单-允许用户提交某些要搜索的内容。
让我们假定用户选择搜索类型为quotlagreinquot的葡萄酒。
2. 检索该用户的搜索术语,并且保存它-通过把它赋给一个如下所示的变量来实现: variety _POSTvariety 因此,变量variety 的值现在为: lagrein 3. 然后,使用该变量在 WHERE 子句中构造一个数据库查询: query quotSELECT FROM wines WHERE varietyvarietyquot 所以,变量query 的值现在如下所示: SELECT FROM wines WHERE varietylagrein 4. 把该查询提交给 MySQL 服务器。
5. MySQL 返回 wines 表格中的所有记录-其中,字段 variety 的值为quotlagreinquot。
到目前为止,这应该是一个你所熟悉的而且是非常轻松的过程。
遗憾的是,有时我们所熟悉并感到舒适的过程却容易导致我们产生自满情绪。
现在,让我们再重新分析一下刚才构建的查询。
1. 你创建的这个查询的固定部分以一个单引号结束,你将使用它来描述变量值的开始: query quot SELECT FROM wines WHERE variety quot 2. 使用原有的固定不变的部分与包含用户提交的变量的值: query . variety 3. 缓螅 闶褂昧硪桓龅ヒ 爬戳 哟私峁描述该变量值的结束: query . quotquot 于是,query 的值如下所示: SELECT FROM wines WHERE variety lagrein 这个构造的成功依赖用户的输入。
在本文示例中,你正在使用单个单词也可能是一组单词来指明一种葡萄酒类型。
因此,该查询的构建是无任何问题的,并且结果也会是你所期望的-一个葡萄酒类型为quotlagreinquot的葡萄酒列表。
现在,让我们想象,既然你的用户不是输入一个简单的类型为quotlagreinquot的葡萄酒类型,而是输入了下列内容注意包括其中的两个标点符号: lagrein or 11 现在,你继续使用前面固定的部分来构造你的查询在此,我们仅显示query变量的结果值: SELECT FROM wines WHERE variety 然后,你使用包含用户输入内容的变量的值与之进行连接在此,以粗体显示: SELECT FROM wines WHERE variety lagrein or 11最后,添加上下面的下引号: SELECT FROM wines WHERE variety lagrein or 11 于是,这个查询结果与你的期望会相当不同。
事实上,现在你的查询包含的不是一条而是两条指令,因为用户输入的最后的分号已经结束了第一条指令进行记录选择从而开始了一条新的指令。
在本例中,第二条指令,除了一个简单的单引号之外别无意义;但是,第一条指令也不是你所想实现的。
当用户把一个单引号放到他的输入内容的中间时,他结束了期望的变量的值,并且引入了另一个条件。
因此,不再是检索那些 variety 为quotlagreinquot的记录,而是在检索那些满足两个标准中任何一个(第一个是你的,而第二个是他的-variety 为quotlagreinquot或 1 等于 1)的记录。
既然 1 总是 1,因此,你会检索到所有的记录! 你可能反对:我不会使用双引号来代替单引号来描述用户提交的变量吗?不错,这至少可以减慢恶意用户的攻击。
在以前的文章中,我们提醒过你:应该禁止所有对用户的错误通知信息。
如果在此生成一条错误消息,那么,它有可能恰恰帮助了攻击者-提供一个关于他的攻击为什么失败的具体的解释。
在实践中,使你的用户能够看到所有的记录而不只是其中的一部分乍看起来似乎不太费事,但实际上,这的确费事不少;看到所有的记录能够很容易地向他提供有关于该表格的内部结构,从而也就向他提供了使其以后实现更为恶毒目的的一个重要参考。
如果你的数据库中不是包含显然无害的酒之类信息而是包含例如一个含有雇员年收入的列表,那么,刚才描述情形会是特别真实的。
而从理论角度分析,这种攻击也的确是一件很可怕的事情。
由于把意外的内容注入到你的查询中,所以,此用户能够实现把你的数据库存取转化为用于实现他自己的目的。
因此现在,你的数据库已经对他打开-正如对你敞开一样。
四、
PHP 和 MySQL 注入 如我们前面所描述的,
PHP,从本身设计来说,并没有做什么特别的事情-除了按照你的指示操作之外。
因此,如果为恶意用户所用,它也只是按照要求quot允许quot特别设计的攻击-例如我们前面所描述的那样。
我们将假定,你不会故意地或甚至是偶然地构造一个具有破坏性效果的数据库查询-于是,我们假定问题出在来自你的用户的输入方面。
现在,让我们来更为细致地分析一下用户可能向你的脚本提供信息的各种途径。
五、 用户输入的类型 如今,用户能够影响你的脚本的行为已变得越来越复杂。
用户输入最明显的来源当然是表单上的一个文本输入域。
使用这样的一个域,你简直是在故意教唆一个用户输入任意数据。
而且,你向用户提供了一个很大的输入范围;没有什么办法能够使你提前限制一个用户能够输入的数据类型尽管你能够选择限制它的长度。
这正是绝大多数的注入式攻击源主要来自于无防备的表单域的原因。
但是,还存在其它的攻击源,并且稍加思考你就会想到的一种潜于表单后台的技术-POST 方法!通过简单地分析显示在浏览器的导航工具栏中的 URI,一个善于观察的用户能够很容易地看出是什么信息传递到了一个脚本。
尽管典型情况下这样的 URI 是以编程方式生成的,但是,没有什么办法能够阻止一个恶意的用户简单地把一个带有一个不适当的变量值的 URI 输入到一个浏览器中-而这样潜在地打开一个可能会被其滥用的数据库。
限制用户输入内容的一个常用策略是在一个表单中提供一个选择框, 而不是一个输入框。
这种控件能够强制用户从一组预定义的值中进行选择,并且能够在一定程度上阻止用户输入期望不到的内容。
但是正如一个攻击者可能quot哄骗quot一个URI也即是,创建一个能够模仿一个可信任的却无效的 URI一样,他也可能模仿创建你的表单及其自己的版本,并因此在选项框中使用非法的而不是预定义的安全选择。
要实现这点是极其简单的;他仅需要观察
源码,然后剪切并且粘贴该表单的源代码-然后一切为他敞开大门。
在修改该选择之后,他就能够提交表单,并且他的无效的指令就会被接受,就象它们是原始的指令一样。
因此,该用户可以使用许多不同的方法试图把恶意的代码注入到一个脚本中。
上篇文章我们介绍了
PHP 中防止 SQL 注入式攻击一,本文我们来继续学习,
php 防 SQL 注入式攻击。
一、注入式攻击的类型 可能存在许多不同类型的攻击动机,但是乍看上去,似乎存在更多的类型。
这是非常真实的-如果恶意用户发现了一个能够执行多个查询的办法的话。
本文后面,我们会对此作详细讨论。
如果你的脚本正在执行一个 SELECT 指令,那么,攻击者可以强迫显示一个表格中的每一行记录-通过把一个例如quot11quot这样的条件注入到 WHERE 子句中,如下所示其中,注入部分以粗体显示:SELECT FROM wines WHERE variety lagrein OR 11 正如我们在前面所讨论的,这本身可能是很有用的信息,因为它揭示了该表格的一般结构这是一条普通的记录所不能实现的,以及潜在地显示包含机密信息的记录。
一条更新指令潜在地具有更直接的威胁。
通过把其它属性放到 SET 子句中,一名攻击者可以修改当前被更新的记录中的任何字段,例如下面的例子(其中,注入部分以粗体显示):UPDATE wines SET typered,vintage9999 WHERE variety lagrein 通过把一个例如 11 这样的恒真条件添加到一条更新指令的 WHERE 子句中,这种修改范围可以扩展到每一条记录,例如下面的例子(其中,注入部分以粗体显示):UPDATE wines SET typered,vintage9999 WHERE variety lagreinOR 11 最危险的指令可能是 DELETE-这是不难想像的。
其注入技术与我们已经看到的相同-通过修改 WHERE 子句来扩展受影响的记录的范围,例如下面的例子(其中,注入部分以粗体显示):DELETE FROM wines WHERE variety lagrein OR 11二、多个查询注入 多个查询注入将会加剧一个攻击者可能引起的潜在的损坏-通过允许多条破坏性指令包括在一个查询中。
在使用 MySQL 数据库时,攻击者通过把一个出乎意料之外的终止符插入到查询中即可很容易实现这一点-此时一个注入的引号单引号或双引号标记期望变量的结尾;然后使用一个分号终止该指令。
现在,一个另外的攻击指令可能被添加到现在终止的原始指令的结尾。
最终的破坏性查询可能看起来如下所示:SELECT FROM wines WHERE variety lagreinGRANT ALL ON . TO BadGuy IDENTIFIED BY gotcha 这个注入将创建一个新的用户 BadGuy 并赋予其网络特权(在所有的表格上具有所有的特权);其中,还有一个quot不祥quot的口令被加入到这个简单的 SELECT语句中。
如果你遵循我们在以前文章中的建议-严格限制该过程用户的特权,那么,这应该无法工作,因为 web 服务器守护程序不再拥有你撤回的 GRANT 特权。
但是从理论上讲,这样的一个攻击可能给予 BadGuy 自由权力来实现他对你的数据库的任何操作。
至于这样的一个多查询是否会被 MySQL 服务器处理,结论并不唯一。
这其中的一些原因可能是由于不同版本的 MySQL 所致,但是大多数情况却是由于多查询存在的方式所致。
MySQL 的监视程序完全允许这样的一个查询。
常用的 MySQLGUI-phpMyAdmin,在最终查询之前会复制出以前所有的内容,并且仅仅这样做。
但是,大多数的在一个注入上下文中的多查询都是由
PHP 的 mysql 扩展负责管理的。
幸好,默认情况下,它是不允许在一个查询中执行多个指令的;试图执行两个指令例如上面所示的注入将会简单地导致失败-不设置任何错误,并且没有生成任何输出信息。
在这种情况下,尽管
PHP 也只是quot规规矩矩quot地实现其缺省行为,但是确实能够保护你免于大多数简单的注入式攻击。
PHP5 中的新的 mysqli 扩展参考 http://
php.net/mysqli,就象 mysql 一样,内在地也不支持多个查询,不过却提供了一个 mysqli_multi_query函数以支持你实现多查询-如果你确实想这样做的话。
然而,对于 SQLite-与
PHP5 绑定到一起的可嵌入的 SQL 数据库引擎参考http://sqlite.org/和 http://
php.net/sqlite情况更为可怕,由于其易于使用而吸引了大量用户的关注。
在有些情况下,SQLite 缺省地允许这样的多指令查询,因为该数据库可以优化批查询,特别是非常有效的批 INSERT 语句处理。
然而,如果查询的结果为你的脚本所使用的话(例如在使用一个 SELECT 语句检索记录的情况下),sqlite_query函数却不会允许执行多个查询。
三、INVISION Power BOARD SQL 注入脆弱性 Invision Power Board 是一个著名的论坛系统。
2005 年五月 6 号,在登录代码中发现了一处 SQL 注入脆弱性。
其发现者为 GulfTech Security Research的 James Bercegay。
这个登录查询如下所示:DB-gtqueryquotSELECT FROM ibf_members WHERE idmid ANDpasswordpidquot 其中,成员 ID 变量mid 和口令 ID 变量pid 被使用下面两行代码从my_cookie函数中检索出:mid intvalstd-gtmy_getcookiemember_idpid std-gtmy_getcookiepass_hash 在此,my_cookie函数使用下列语句从 cookie 中检索要求的变量:return urldecode_COOKIEibforums-gtvarscookie_id.name 【注意】从该 cookie 返回的值根本没有被处理。
尽管mid 在使用于查询之前被强制转换成一个整数,但是pid 却保持不变。
因此,它很容易遭受我们前面所讨论的注入类型的攻击。
因此,通过以如下方式修改 my_cookie函数,这种脆弱性就会暴露出来:if in_array name,arraytopicsread, forum_read,collapseprefsreturn this-gtclean_valueurldecode_COOKIEibforums-gtvarscookie_id.nameelsereturn urldecode_COOKIEibforums-gtvarscookie_id.name 经过这样的改正之后,其中的关键变量在quot通过quot全局 clean_value函数后被返回,而其它变量却未进行检查。
现在,既然我们大致了解了什么是 SQL 注入,它的注入原理以及这种注入的脆弱程度,那么接下来,让我们探讨如何有效地预防它。
幸好,
PHP 为我们提供了丰富的资源,因此我们有充分的信心预言,一个经仔细地彻底地使用我们所推荐的技术构建的应用程序将会从你的脚本中根本上消除任何可能性的 SQL 注入-通过在它可能造成任何损坏之前quot清理quot你的用户的数据来实现。
四、界定你的查询中的每一个值 我们推荐,你确保界定了你的查询中的每一个值。
字符串值首当其冲,以及那些你通常期望应该使用quot单quot而不是quot双quot引号的内容。
一方面,如果你使用双引号来允许
PHP 在字符串内的变量替代,这样可以使得输入查询更为容易些;另一方面,这无可否认,只是极少量地也会减少以后
PHP 代码的分析工作。
下面,让我们使用我们一开始使用的那个非注入式查询来说明这个问题:SELECT FROM wines WHERE variety lagrein 或以
PHP 语句表达为:query quotSELECT FROM wines WHERE variety varietyquot 从技术上讲,引号对于数字值来说是不需要使用的。
但是,如果你并不介意用引号把例如葡萄酒这样的一个域相应的一个值括起来并且如果你的用户把一个空值输入到你的表单中的话,那么,你会看到一个类似下面的查询:SELECT FROM wines WHERE vintage 当然,这个查询从语法上讲是无效的;但是,下面的语法却是有效的:SELECT FROM wines WHERE vintage 第二个查询将大概不会返回任何果,但是至少它不会返回一个错误消息。
五、检查用户提交的值的类型 从前面的讨论中我们看到,迄今为止,SQL 注入的主要来源往往出在一个意料之外的表单入口上。
然而,当你经由一个表单向用户提供机会提交某些值时,你应该有相当的优势来确定你想取得什么样的输入内容-这可以使得我们比较容易地检查用户入口的有效性。
在以前的文章中,我们已经讨论过这样的校验问题;所以,在此,我们仅简单地总结当时我们讨论的要点。
如果你正在期望一个数字,那么你可以使用下面这些技术之一来确保你得到的真正. 使用 is_int函数或 is_integer或 is_long。
使用 gettype函数。
使用 intval函数。
使用 settype函数。
为了检查用户输入内容的长度,你可以使用 strlen函数。
为了检查一个期望的时间或日期是否有效,你可以使用 strtotime函数。
它几乎一定能够确保一位用户的入口中没有包含分号字符除非标点符号可以被合法地包括在内。
你可以借助于 strpos函数容易地实现这一点,如下所示:if strpos variety, exit quotvariety is an invalid value forvarietyquot 正如我们在前面所提到的,只要你仔细分析你的用户输入期望,那么,你应该能够很容易地检查出其中存在的许多问题。
六、从你的查询中滤去每一个可疑字符 尽管在以前的文章中,我们已经讨论过如何过滤掉危险字符的问题;但是在此,还是让我们再次简单地强调并归纳一下这个问题: 不要使用 magic_quotes_gpc 指令或它的quot幕后搭挡quot-addslashes函数,此函数在应用程序开发中是被限制使用的,并且此函数还要求使用额外的步骤-使用 stripslashes函数。
相比之下,mysql_real_escape_string函数更为常用,但是也有它自己的缺点。
--一、 建立一个安全抽象层 我们并不建议你手工地把前面介绍的技术应用于每一个用户输入的实例中,而是强烈推荐你为此创建一个抽象层。
一个简单的抽象是把你的校验方案加入到一个函数中,并且针对用户输入的每一项调用这个函数。
当然,我们还可以创建一种更复杂的更高一级的抽象-把一个安全的查询封装到一个类中,从而应用于整个应用程序。
在网上已经存在许多这种现成的免费的类;在本篇中,我们正要讨论其中的一些。
进行这种抽象至少存在三个优点(而且每一个都会改进安全级别): 1. 本地化代码。
2. 使查询的构造更快且更为可靠-因为这可以把部分工作交由抽象代码来实现。
3. 当基于安全特征进行构建并且恰当使用时,这将会有效地防止我们前面所讨论的各种各样的注入式攻击。
二、 改进现有的应用程序 如果你想改进一个现有的应用程序,则使用一个简单的抽象层是最适当的。
一个能够简单地quot清理quot你所收集的任何用户输入内容的函数可能看起来如下所示:function safe string return quotquot . mysql_real_escape_string string . quotquot 【注意】我们已经构建了相应于值要求的单引号以及mysql_real_escape_string函数。
接下来,就可以使用这个函数来构造一个query 变量,如下所示:variety safe _POSTvariety query quot SELECT FROM wines WHERE varietyquot . variety 现在,你的用户试图进行一个注入式攻击-通过输入下列内容作为变量variety 的值:lagrein or 11 注意,如果不进行上面的quot清理quot,则最后的查询将如下所示(这将导致无法预料的结果):SELECT FROM wines WHERE variety lagrein or 11 然而现在,既然用户的输入已经被清理,那么查询语句就成为下面这样一种无危害的形式:SELECT FROM wines WHERE variety lagrein/ or 11/既然数据库中不存在与指定的值相应的 variety 域这正是恶意用户所输入的内容-lagrein or 11,那么,这个查询将不能返回任何结果,并且注入将会失败。
三、 保护一个新的应用程序 如果你正在创建一个新的应用程序,那么,你可以从头开始创建一个安全抽象层。
如今,
PHP 5 新改进的对于 MySQL 的支持(这主要体现在新的 mysqli 扩展中)为这种安全特征提供了强有力的支持(既有过程性的,也有面向对象特征的)。
你可以从站点 http://
php.net/mysqli 上获取有关 mysqli 的信息。
注意,只有当你使用--with-mysqlipath/to/mysql_config 选项编译
PHP 时,这种mysqli 支持才可用。
下面是该代码的一个过程性版本,用于保护一个基于 mysqli的查询:<
php //检索用户的输入 animalName _POSTanimalName //连接到数据库 connect mysqli_connect localhost, username, password,database if connect exit connection failed: . mysqli_connect_error //创建一个查询语句源 stmt mysqli_prepare connec.
上一篇:
PHP5网游开发入门教程
下一篇:
高等数学课程复习资料