PHP头条
热点:

编纂安全PHP应用程序的七个习惯


编写安全 PHP 应用程序的七个习惯

提高 Web 应用程序的安全性

PHP 应用程序中的安全性包括远程安全性和本地安全性。本文将揭示 PHP 开发人员在实现具有这两种安全性的 Web 应用程序时应该养成的习惯。

?

在提及安全性问题时,需要注意,除了实际的平台和操作系统安全性问题之外,您还需要确保编写安全的应用程序。在编写 PHP 应用程序时,请应用下面的七个习惯以确保应用程序具有最好的安全性:

  • 验证输入
  • 保护文件系统
  • 保护数据库
  • 保护会话数据
  • 保护跨站点脚本(Cross-site scripting,XSS)漏洞
  • 检验表单 post
  • 针对跨站点请求伪造(Cross-Site Request Forgeries,CSRF)进行保护

?

验证输入

在 提及安全性问题时,验证数据是您可能采用的最重要的习惯。而在提及输入时,十分简单:不要相信用户。您的用户可能十分优秀,并且大多数用户可能完全按照期 望来使用应用程序。但是,只要提供了输入的机会,也就极有可能存在非常糟糕的输入。作为一名应用程序开发人员,您必须阻止应用程序接受错误的输入。仔细考 虑用户输入的位置及正确值将使您可以构建一个健壮、安全的应用程序。

?

虽然后文将介绍文件系统与数据库交互,但是下面列出了适用于各种验证的一般验证提示:

  • 使用白名单中的值
  • 始终重新验证有限的选项
  • 使用内置转义函数
  • 验证正确的数据类型(如数字)

白名单中的值(White-listed value)是正确的值,与无效的黑名单值(Black-listed value)相对。两者之间的区别是,通常在进行验证时,可能值的列表或范围小于无效值的列表或范围,其中许多值可能是未知值或意外值。

?

在进行验证时,记住设计并验证应用程序允许使用的值通常比防止所有未知值更容易。例如,要把字段值限定为所有数字,需要编写一个确保输入全都是数字的例程。不要编写用于搜索非数字值并在找到非数字值时标记为无效的例程。

?

保护文件系统

2000 年 7 月,一个 Web 站点泄露了保存在 Web 服务器的文件中的客户数据。该 Web 站点的一个访问者使用 URL 查看了包含数据的文件。虽然文件被放错了位置,但是这个例子强调了针对攻击者保护文件系统的重要性。

如果 PHP 应用程序对文件进行了任意处理并且含有用户可以输入的变量数据,请仔细检查用户输入以确保用户无法对文件系统执行任何不恰当的操作。清单 1 显示了下载具有指定名的图像的 PHP 站点示例。

?
清单 1. 下载文件
<");
        echo("title>Guard your filesystem");
    echo("
"); echo(""); echo(""); echo("
"); }

?

正如您所见,清单 1 中比较危险的脚本将处理 Web 服务器拥有读取权限的所有文件,包括会话目录中的文件(请参阅 “保护会话数据”),甚至还包括一些系统文件(例如 /etc/passwd)。为了进行演示,这个示例使用了一个可供用户键入文件名的文本框,但是可以在查询字符串中轻松地提供文件名。

?

同时配置用户输入和文件系统访问权十分危险,因此最好把应用程序设计为使用数据库和隐藏生成的文件名来避免同时配置。但是,这样做并不总是有效。清单 2 提供了验证文件名的示例例程。它将使用正则表达式以确保文件名中仅使用有效字符,并且特别检查圆点字符:..

?
清单 2. 检查有效的文件名字符
function isValidFileName($file) {
    /* don't allow .. and allow any "word" character \ / */
    return preg_match('/^(((?:\.)(?!\.))|\w)+$/', $file);
}

?

保护数据库

2008 年 4 月,美国某个州的狱政局在查询字符串中使用了 SQL 列名,因此泄露了保密数据。这次泄露允许恶意用户选择需要显示的列、提交页面并获得数据。这次泄露显示了用户如何能够以应用程序开发人员无法预料的方法执 行输入,并表明了防御 SQL 注入攻击的必要性。

?

清单 3 显示了运行 SQL 语句的示例脚本。在本例中,SQL 语句是允许相同攻击的动态语句。此表单的所有者可能认为表单是安全的,因为他们已经把列名限定为选择列表。但是,代码疏忽了关于表单欺骗的最后一个习惯 — 代码将选项限定为下拉框并不意味着其他人不能够发布含有所需内容的表单(包括星号 [*])。

?
清单 3. 执行 SQL 语句


SQL Injection Example


' . $select . '

'; $result = mysql_query($select) or die('

' . mysql_error() . '

'); echo ''; while ($row = mysql_fetch_assoc($result)) { echo ''; echo ''; echo ''; } echo '
' . $row[$col] . '
'; mysql_close($link); } ?>

?

因此,要形成保护数据库的习惯,请尽可能避免使用动态 SQL 代码。如果无法避免动态 SQL 代码,请不要对列直接使用输入。清单 4 显示了除使用静态列外,还可以向帐户编号字段添加简单验证例程以确保输入值不是非数字值。

?
清单 4. 通过验证和 mysql_real_escape_string() 提供保护


SQL Injection Example


' . $select . '

'; $result = mysql_query($select) or die('

' . mysql_error() . '

'); echo ''; while ($row = mysql_fetch_assoc($result)) { echo ''; echo ''; echo ''; echo ''; echo ''; } echo '
' . $row['account_number'] . '' . $row['name'] . '' . $row['address'] . '
'; mysql_close($link); } else { echo "" . "Please supply a valid account number!"; } } ?>

?

本例还展示了 mysql_real_escape_string() 函数的用法。此函数将正确地过滤您的输入,因此它不包括无效字符。如果您一直依赖于 magic_quotes_gpc,那么需要注意它已被弃用并且将在 PHP V6 中删除。从现在开始应避免使用它并在此情况下编写安全的 PHP 应用程序。此外,如果使用的是 ISP,则有可能您的 ISP 没有启用 magic_quotes_gpc

?

最 后,在改进的示例中,您可以看到该 SQL 语句和输出没有包括动态列选项。使用这种方法,如果把列添加到稍后含有不同信息的表中,则可以输出这些列。如果要使用框架以与数据库结合使用,则您的框架 可能已经为您执行了 SQL 验证。确保查阅文档以保证框架的安全性;如果仍然不确定,请进行验证以确保稳妥。即使使用框架进行数据库交互,仍然需要执行其他验证。

?

保护会话

默认情况下,PHP 中的会话信息将被写入临时目录。考虑清单 5 中的表单,该表单将显示如何存储会话中的用户 ID 和帐户编号。

?
清单 5. 存储会话中的数据



Storing session information





?

清单 6 显示了 /tmp 目录的内容。

?
清单 6. /tmp 目录中的会话文件
-rw-------  1 _www    wheel       97 Aug 18 20:00 sess_9e4233f2cd7cae35866cd8b61d9fa42b

?

正如您所见,在输出时(参见清单 7),会话文件以非常易读的格式包含信息。由于该文件必须可由 Web 服务器用户读写,因此会话文件可能为共享服务器中的所有用户带来严重的问题。除您之外的某个人可以编写脚本来读取这些文件,因此可以尝试从会话中取出值。

?
清单 7. 会话文件的内容
userName|s:5:"ngood";accountNumber|s:9:"123456789";

?

存储密码

不 管是在数据库、会话、文件系统中,还是在任何其他表单中,无论如何密码都决不能存储为纯文本。处理密码的最佳方法是将其加密存储并相互比较加密的密码。虽 然如此,在实践中人们仍然把密码存储到纯文本中。只要使用可以发送密码而非重置密码的 Web 站点,就意味着密码是存储在纯文本中或者可以获得用于解密的代码(如果加密的话)。即使是后者,也可以找到并使用解密代码。

?

您 可以采取两项操作来保护会话数据。第一是把您放入会话中的所有内容加密。但是正因为加密数据并不意味着绝对安全,因此请慎重采用这种方法作为保护会话的惟 一方式。备选方法是把会话数据存储在其他位置中,比方说数据库。您仍然必须确保锁定数据库,但是这种方法将解决两个问题:第一,它将把数据放到比共享文件 系统更加安全的位置;第二,它将使您的应用程序可以更轻松地跨越多个 Web 服务器,同时共享会话可以跨越多个主机。

要实现自己的会话持久性,请参阅 PHP 中的 session_set_save_handler() 函数。使用它,您可以将会话信息存储在数据库中,也可以实现一个用于加密和解密所有数据的处理程序。清单 8 提供了实现的函数用法和函数骨架示例。您还可以在 参考资料 小节中查看如何使用数据库。

?
清单 8. session_set_save_handler() 函数示例
function open($save_path, $session_name)
{
    /* custom code */
    return (true);
}

function close()
{
    /* custom code */
    return (true);
}

function read($id)
{
    /* custom code */
    return (true);
}

function write($id, $sess_data)
{
    /* custom code */
    return (true);
}

function destroy($id)
{
    /* custom code */
    return (true);
}

function gc($maxlifetime)
{
    /* custom code */
    return (true);
}

session_set_save_handler("open", "close", "read", "write", "destroy", "gc");

?

针对 XSS 漏洞进行保护

XSS 漏洞代表 2007 年所有归档的 Web 站点的大部分漏洞(请参阅 参考资料)。 当用户能够把 HTML 代码注入到您的 Web 页面中时,就是出现了 XSS 漏洞。HTML 代码可以在脚本标记中携带 JavaScript 代码,因而只要提取页面就允许运行 JavaScript。清单 9 中的表单可以表示论坛、维基、社会网络或任何可以输入文本的其他站点。

?
清单 9. 输入文本的表单


Your chance to input XSS



?

清单 10 演示了允许 XSS 攻击的表单如何输出结果。

?
清单 10. showResults.php


Results demonstrating XSS


You typed this:

"); echo("

"); echo($_POST['myText']); echo("

"); ?>

?

清单 11 提供了一个基本示例,在该示例中将弹出一个新窗口并打开 Google 的主页。如果您的 Web 应用程序不针对 XSS 攻击进行保护,则会造成严重的破坏。例如,某个人可以添加模仿站点样式的链接以达到欺骗(phishing)目的(请参阅 参考资料)。

?
清单 11. 恶意输入文本样例

相关文章

相关频道:

PHP之友评论

今天推荐