作者:Thomas Myer 来源:IBM DeveloperWorks 2007-02-27 最后更新:2007-02-27 13:10:28
防止 SQL 注入攻击
在 SQL 注入攻击 中,用户通过操纵表单或 GET 查询字符串,将信息添加到数据库查询中。例如,假设有一个简单的登录数据库。这个数据库中的每个记录都有一个用户名字段和一个密码字段。构建一个登录表单,让用户能够登录。
清单 5. 简单的登录表单
<html>
<head>
<title>Login</title>
</head>
<body>
<form action="verify.php" method="post">
<p><label for='user'>Username</label>
<input type='text' name='user' id='user'/>
</p>
<p><label for='pw'>Password</label>
<input type='password' name='pw' id='pw'/>
</p>
<p><input type='submit' value='login'/></p>
</form>
</body>
</html>
这个表单接受用户输入的用户名和密码,并将用户输入提交给名为 verify.php 的文件。在这个文件中,PHP 处理来自登录表单的数据,如下所示:
清单 6. 不安全的 PHP 表单处理代码
<?php
$okay = 0;
$username = $_POST['user'];
$pw = $_POST['pw'];
$sql = "select count(*) as ctr from users where
username='".$username."' and password='". $pw."' limit 1";
$result = mysql_query($sql);
while ($data = mysql_fetch_object($result)){
if ($data->ctr == 1){
//they're okay to enter the application!
$okay = 1;
}
}
if ($okay){
$_SESSION['loginokay'] = true;
header("index.php");
}else{
header("login.php");
}
?>
这段代码看起来没问题,对吗?世 界各地成百(甚至成千)的 PHP/MySQL 站点都在使用这样的代码。它错在哪里?好,记住 “不能信任用户输入”。这里没有对来自用户的任何信息进行转义,因此使应用程序容易受到攻击。具体来说,可能会出现任何类型的 SQL 注入攻击。
例如,如果用户输入 foo 作为用户名,输入 ' or '1'='1 作为密码,那么实际上会将以下字符串传递给 PHP,然后将查询传递给 MySQL:
$sql = "select count(*) as ctr from users where
username='foo' and password='' or '1'='1' limit 1";
这个查询总是返回计数值 1,因此 PHP 会允许进行访问。通过在密码字符串的末尾注入某些恶意 SQL,黑客就能装扮成合法的用户。
解决这个问题的办法是,将 PHP 的内置 mysql_real_escape_string() 函数用作任何用户输入的包装器。这个函数对字符串中的字符进行转义,使字符串不可能传递撇号等特殊字符并让 MySQL 根据特殊字符进行操作。清单 7 展示了带转义处理的代码。
清单 7. 安全的 PHP 表单处理代码
<?php
$okay = 0;
$username = $_POST['user'];
$pw = $_POST['pw'];
$sql = "select count(*) as ctr from users where
username='".mysql_real_escape_string($username)."'
and password='". mysql_real_escape_string($pw)."' limit 1";
$result = mysql_query($sql);
while ($data = mysql_fetch_object($result)){
if ($data->ctr == 1){
//they're okay to enter the application!
$okay = 1;
}
}
if ($okay){
$_SESSION['loginokay'] = true;
header("index.php");
}else{
header("login.php");
}
?>
使用 mysql_real_escape_string() 作为用户输入的包装器,就可以避免用户输入中的任何恶意 SQL 注入。如果用户尝试通过 SQL 注入传递畸形的密码,那么会将以下查询传递给数据库:
select count(*) as ctr from users where \
username='foo' and password='\' or \'1\'=\'1' limit 1"
数据库中没有任何东西与这样的密码匹配。仅仅采用一个简单的步骤,就堵住了 Web 应用程序中的一个大漏洞。这里得出的经验是,总是应该对 SQL 查询的用户输入进行转义。
但是,还有几个安全漏洞需要堵住。下一项是操纵 GET 变量。
防止用户操纵 GET 变量
在前一节中,防止了用户使用畸形的密码进行登录。如果您很聪明,应该应用您学到的方法,确保对 SQL 语句的所有用户输入进行转义。
但是,用户现在已经安全地登录了。用户拥有有效的密码,并不意味着他将按照规则行事 —— 他有很多机会能够造成损害。例如,应用程序可能允许用户查看特殊的内容。所有链接指向 template.php?pid=33 或 template.php?pid=321 这样的位置。URL 中问号后面的部分称为查询字符串。因为查询字符串直接放在 URL 中,所以也称为 GET 查询字符串。
在 PHP 中,如果禁用了 register_globals,那么可以用 $_GET['pid'] 访问这个字符串。在 template.php 页面中,可能会执行与清单 8 相似的操作。
清单 8. 示例 template.php
<?php
$pid = $_GET['pid'];
//we create an object of a fictional class Page
$obj = new Page;
$content = $obj->fetchPage($pid);
//and now we have a bunch of PHP that displays the page
//......
//......
?>
这里有什么错吗?首先,这里隐含地相信来自浏览器的 GET 变量 pid 是安全的。这会怎么样呢?大多数用户没那么聪明,无法构造出语义攻击。但是,如果他们注意到浏览器的 URL 位置域中的 pid=33,就可能开始捣乱。如果他们输入另一个数字,那么可能没问题;但是如果输入别的东西,比如输入 SQL 命令或某个文件的名称(比如 /etc/passwd),或者搞别的恶作剧,比如输入长达 3,000 个字符的数值,那么会发生什么呢?
在这种情况下,要记住基本规则,不要信任用户输入。应用程序开发人员知道 template.php 接受的个人标识符(PID)应该是数字,所以可以使用 PHP 的 is_numeric() 函数确保不接受非数字的 PID,如下所示:
清单 9. 使用 is_numeric() 来限制 GET 变量
<?php
$pid = $_GET['pid'];
if (is_numeric($pid)){
//we create an object of a fictional class Page
$obj = new Page;
$content = $obj->fetchPage($pid);
//and now we have a bunch of PHP that displays the page
//......
//......
}else{
//didn't pass the is_numeric() test, do something else!
}?>
这个方法似乎是有效的,但是以下这些输入都能够轻松地通过 is_numeric() 的检查:
•100 (有效)
•100.1 (不应该有小数位)
•+0123.45e6 (科学计数法 —— 不好)
•0xff33669f (十六进制 —— 危险!危险!)
那么,有安全意识的 PHP 开发人员应该怎么做呢?多年的经验表明,最好的做法是使用正则表达式来确保整个 GET 变量由数字组成,如下所示:
清单 10. 使用正则表达式限制 GET 变量
<?php
$pid = $_GET['pid'];
<b>
if (strlen($pid)){
if (!ereg("^[0-9]+$",$pid)){
//do something appropriate, like maybe logging \
them out or sending them back to home page
}
}else{
//empty $pid, so send them back to the home page
}
</b>
//we create an object of a fictional class Page, which is now
//moderately protected from evil user input
$obj = new Page;
$content = $obj->fetchPage($pid);
//and now we have a bunch of PHP that displays the page
//......
//......
?>
需要做的只是使用 strlen() 检查变量的长度是否非零;如果是,就使用一个全数字正则表达式来确保数据元素是有效的。如果 PID 包含字母、斜线、点号或任何与十六进制相似的内容,那么这个例程捕获它并将页面从用户活动中屏蔽。如果看一下 Page 类幕后的情况,就会看到有安全意识的 PHP 开发人员已经对用户输入 $pid 进行了转义,从而保护了 fetchPage() 方法,如下所示:
清单 11. 对 fetchPage() 方法进行转义
<?php
class Page{
function fetchPage($pid){
$sql = "select pid,title,desc,kw,content,\
status from page where pid='
".mysql_real_escape_string($pid)."'";
//etc, etc....
}
}
?>
您可能会问,“既然已经确保 PID 是数字,那么为什么还要进行转义?” 因为不知道在多少不同的上下文和情况中会使用 fetchPage() 方法。必须在调用这个方法的所有地方进行保护,而方法中的转义体现了纵深防御的意义。
如果用户尝试输入非常长的数值,比如长达 1000 个字符,试图发起缓冲区溢出攻击,那么会发生什么呢?下一节更详细地讨论这个问题,但是目前可以添加另一个检查,确保输入的 PID 具有正确的长度。您知道数据库的 pid 字段的最大长度是 5 位,所以可以添加下面的检查。
清单 12. 使用正则表达式和长度检查来限制 GET 变量
<?php
$pid = $_GET['pid'];
if (strlen($pid)){
if (!ereg("^[0-9]+$",$pid) && strlen($pid) > 5){
//do something appropriate, like maybe logging \
them out or sending them back to home page
}
}else{
//empty $pid, so send them back to the home page
}
//we create an object of a fictional class Page, which is now
//even more protected from evil user input
$obj = new Page;
$content = $obj->fetchPage($pid);
//and now we have a bunch of PHP that displays the page
//......
//......
?>
现在,任何人都无法在数据库应用程序中塞进一个 5,000 位的数值 —— 至少在涉及 GET 字符串的地方不会有这种情况。想像一下黑客在试图突破您的应用程序而遭到挫折时咬牙切齿的样子吧!而且因为关闭了错误报告,黑客更难进行侦察。
分享到:
相关推荐
《PHP应用程序安全编程》是Tricia Ballad撰写的一本专著,主要针对PHP开发者,旨在帮助他们理解和实施安全的编程实践。PHP作为一种广泛使用的开源脚本语言,常用于Web开发,但同时也因为其灵活性和易用性,可能导致...
在PHP应用程序开发中,安全是至关重要的一个环节。由于PHP是一种广泛使用的服务器端脚本语言,许多网站和应用程序都是基于PHP构建的。然而,如果不注意安全编程实践,PHP应用可能会遭受各种攻击,如SQL注入、跨站...
本文将深入探讨在PHP应用程序安全编程中所涉及的关键知识点,旨在帮助开发者避免常见安全漏洞,构建更健壮、更安全的系统。 1. **输入验证**:在处理用户输入时,必须对数据进行严格的验证和过滤。这包括URL参数、...
PHP的设计目标是比Perl或C语言编写的CGI程序更安全,但正确配置编译选项和运行时设置以及采用安全编码实践是确保PHP应用程序安全的关键。 在使用PHP开发安全应用的过程中,有几个重要的注意事项: 1. **保持操作...
### PHP应用程序安全编程知识点 #### 一、引言与安全观念 - **目的与受众**:本书面向PHP初学者及具有一定经验的开发者,旨在提升PHP应用程序的安全性。 - **安全误区**:书中强调不应将混淆文件名或目录结构视为...
然而,随着Web安全成为关注焦点,PHP应用程序的安全性问题也日益凸显。许多企业和组织纷纷成立专门的安全团队或聘请安全专家来进行代码审计,以确保其应用程序的安全性。传统的漏洞审计技术已经难以发现新型的安全...
在深入探讨高级PHP应用程序漏洞审核技术...总结,高级PHP应用程序漏洞审核技术不仅涉及代码层面的安全,还涵盖了整体架构、依赖管理和安全意识。通过深入理解这些概念,开发者可以构建更安全、更可靠的PHP应用程序。
在PHP应用程序安全编程中,确保代码的安全性是至关重要的。PHP是一种广泛使用的开源脚本语言,常用于构建动态网站和Web应用程序。然而,由于其灵活性和动态性,如果不注意安全编程,很容易导致各种安全漏洞,如SQL...
确保PHP应用程序的安全性需要从多个层面入手。首先,操作系统应当保持最新状态并安装所有必要的安全补丁。在编译PHP之前,要特别注意以下几点: 1. 整合Apache、PHP和MySQL:通常推荐使用Apache Toolbox来简化这一...
如果多个Web应用程序共用一个应用程序池,则可能存在安全风险,因为一个应用程序的崩溃可能会导致整个应用程序池中的W3wp进程退出,从而影响到其他Web应用程序。因此,如果内存资源允许,通常建议为每个Web应用程序...
### 用PHP构建Android手机应用程序 #### 摘要与关键词解析 本文主要探讨了如何利用PHP语言在Android平台上开发应用程序。通常情况下,Android应用程序主要采用Java或Kotlin进行开发,但通过Android脚本层(SL4A)...
9. **响应式设计**:随着移动设备的普及,响应式设计成为必备技能,确保应用程序在不同设备和屏幕尺寸上都能正常显示。 10. **持续集成/持续部署(CI/CD)**:理解自动化构建和部署流程,如使用Git进行版本控制,...
在PHP的环境中,确保应用程序的安全性至关重要。PHP的旧版本存在一些已知的安全问题,因此推荐使用最新稳定版以减少漏洞的暴露。其中,SQL Injection是PHP应用中常见的安全威胁,它允许攻击者通过注入恶意SQL语句来...
应用程序可能会对Linux系统性能和安全性产生影响,反之亦然。如果影响过大,可能需要重新审视迁移的必要性和方案的正确性。此外,移植过程需考虑操作系统特性对应用程序代码的影响,有时候可能需要通过虚拟化技术来...
揭露 PHP 应用程序中出现的五个常见数据库问题 在 PHP 应用程序中,数据库问题是非常常见的。这篇文章将揭露五个常见的数据库问题,包括数据库模式设计、数据库访问和基于数据库的业务逻辑代码,并提供相应的解决...
测试阶段确保应用程序的性能、稳定性和安全性。这包括单元测试、集成测试、系统测试和用户体验测试。此外,还需进行兼容性测试,确保应用在不同浏览器和设备上的表现。 5. **发行**: 在测试成功后,准备发布应用...