本教程提供一种将安全性集成到 PHP Web 应用程序中的好方法。它讨论几个一般性安全主题,然后深入讨论主要的安全漏洞以及如何堵住它们。在学完本教程之后,您会对安全性有更好的理解。
主题包括: SQL 注入攻击 操纵 GET 字符串 缓冲区溢出攻击 跨站点脚本攻击(XSS) 浏览器内的数据操纵 远程表单提交
Web 安全性 101 在讨论实现安全性的细节之前,最好从比较高的角度讨论 Web 应用程序安全性。本节介绍安全哲学的一些基本信条,无论正在创建何种 Web 应用程序,都应该牢记这些信条。这些思想的一部分来自 Chris Shiflett(他关于 PHP 安全性的书是无价的宝库),一些来自 Simson Garfinkel(参见 参考资料),还有一些来自多年积累的知识。
规则 1:绝不要信任外部数据或输入
关于 Web 应用程序安全性,必须认识到的第一件事是不应该信任外部数据。外部数据(outside data) 包括不是由程序员在 PHP 代码中直接输入的任何数据。在采取措施确保安全之前,来自任何其他来源(比如 GET 变量、表单 POST、数据库、配置文件、会话变量或 COOKIE)的任何数据都是不可信任的。
已经知道了不能信任用户输入,还应该知道不应该信任机器上配置 PHP 的方式。例如,要确保禁用 register_globals。如果启用了 register_globals,就可能做一些粗心的事情,比如使用 $variable 替换同名的 GET 或 POST 字符串。通过禁用这个设置,PHP 强迫您在正确的名称空间中引用正确的变量。要使用来自表单 POST 的变量,应该引用 $_POST['variable']。这样就不会将这个特定变量误会成 COOKIE、会话或 GET 变量。
$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"); } ?>
//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 ?>
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! } ?>
那么,有安全意识的 PHP 开发人员应该怎么做呢?多年的经验表明,最好的做法是使用正则表达式来确保整个 GET 变量由数字组成,如下所示:
清单 10. 使用正则表达式限制 GET 变量
$pid = $_GET['pid'];
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 }
//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 ?>
class Page{ function fetchPage($pid){ $sql = "select pid,title,desc,kw,content,status from page where pid='".mysql_real_escape_string($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 字符串的地方不会有这种情况。想像一下黑客在试图突破您的应用程序而遭到挫折时咬牙切齿的样子吧!而且因为关闭了错误报告,黑客更难进行侦察。
正如您看到的,这种方式与前一节中使用 strlen() 检查 GET 变量 pid 的长度相似。在这个示例中,忽略长度超过 5 位的任何输入值,但是也可以很容易地将值截短到适当的长度,如下所示:
清单 14. 改变输入的 GET 变量的长度
$pid = $_GET['pid'];
if (strlen($pid)){ if (!ereg("^[0-9]+$",$pid)){ //if non numeric $pid, send them back to home page } }else{ //empty $pid, so send them back to the home page }
//we have a numeric pid, but it may be too long, so let's check if (strlen($pid)>5){ $pid = substr($pid,0,5); }
//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 ?>
以表单为例。任何人都能够访问一个 Web 站点,并使用浏览器上的 File > Save As 建立表单的本地副本。然后,他可以修改 action 参数来指向一个完全限定的 URL(不指向 formHandler.php,而是指向 http://www.yoursite.com/formHandler.php,因为表单在这个站点上),做他希望的任何修改,点击 Submit,服务器会把这个表单数据作为合法通信流接收。
获得产品和技术 Windows 用户可以下载 WAMPServer。 用 PHP 构建您的下一个开发项目。 使用 IBM 试用软件 改进您的下一个开放源码开发项目,这些软件可以下载或者通过 DVD 获得。
讨论 通过参与 developerWorks blog 加入 developerWorks 社区。
关于作者 Thomas Myer 是 Triple Dog Dare Media 的创始人和主要人物,这是一家位于德州 Austin 的 Web 咨询公司,特长在于信息体系结构、Web 应用程序开发和 XML 咨询。他是 No Nonsense XML Web Development with PHP(由 SitePoint 出版)的作者。