正如您所见,在输出时(参见清单 7),会话文件以非常易读的格式包含信息。由于该文件必须可由 Web 服务器用户读写,因此会话文件可能为共享服务器中的所有用户带来严重的问题。除您之外的某个人可以编写脚本来读取这些文件,因此可以尝试从会话中取出值。 清单 7. 会话文件的内容 复制代码 代码如下: userName|s:5:"ngood";accountNumber|s:9:"123456789";
存储密码 不管是在数据库、会话、文件系统中,还是在任何其他表单中,无论如何密码都决不能存储为纯文本。处理密码的最佳方法是将其加密存储并相互比较加密的密码。虽然如此,在实践中人们仍然把密码存储到纯文本中。只要使用可以发送密码而非重置密码的 Web 站点,就意味着密码是存储在纯文本中或者可以获得用于解密的代码(如果加密的话)。即使是后者,也可以找到并使用解密代码。 您可以采取两项操作来保护会话数据。第一是把您放入会话中的所有内容加密。但是正因为加密数据并不意味着绝对安全,因此请慎重采用这种方法作为保护会话的惟一方式。备选方法是把会话数据存储在其他位置中,比方说数据库。您仍然必须确保锁定数据库,但是这种方法将解决两个问题:第一,它将把数据放到比共享文件系统更加安全的位置;第二,它将使您的应用程序可以更轻松地跨越多个 Web 服务器,同时共享会话可以跨越多个主机。
针对 XSS 漏洞进行保护 XSS 漏洞代表 2007 年所有归档的 Web 站点的大部分漏洞(请参阅 参考资料)。当用户能够把 HTML 代码注入到您的 Web 页面中时,就是出现了 XSS 漏洞。HTML 代码可以在脚本标记中携带 JavaScript 代码,因而只要提取页面就允许运行 JavaScript。清单 9 中的表单可以表示论坛、维基、社会网络或任何可以输入文本的其他站点。 清单 9. 输入文本的表单 复制代码 代码如下: html head title Your chance to input XSS /title /head body form id="myFrom" action="showResults.php" method="post" div textarea name="myText" rows="4" cols="30" /textarea br / input type="submit" value="Delete" name="submit" / /div /form /body /html
清单 10 演示了允许 XSS 攻击的表单如何输出结果。 清单 10. showResults.php 复制代码 代码如下: html head title Results demonstrating XSS /title /head body ?php echo(" p You typed this: /p echo(" p echo($_POST['myText']); echo(" /p ? /body /html
要防止受到 XSS 攻击,只要变量的值将被打印到输出中,就需要通过 htmlentities() 函数过滤输入。记住要遵循第一个习惯:在 Web 应用程序的名称、电子邮件地址、电话号码和帐单信息的输入中用白名单中的值验证输入数据。 下面显示了更安全的显示文本输入的页面。 清单 12. 更安全的表单 复制代码 代码如下: html head title Results demonstrating XSS /title /head body ?php echo(" p You typed this: /p echo(" p echo(htmlentities($_POST['myText'])); echo(" /p ? /body /html
针对无效 post 进行保护 表单欺骗 是指有人把 post 从某个不恰当的位置发到您的表单中。欺骗表单的最简单方法就是创建一个通过提交至表单来传递所有值的 Web 页面。由于 Web 应用程序是没有状态的,因此没有一种绝对可行的方法可以确保所发布数据来自指定位置。从 IP 地址到主机名,所有内容都是可以欺骗的。清单 13 显示了允许输入信息的典型表单。 清单 13. 处理文本的表单 复制代码 代码如下: html head title Form spoofing example /title /head body ?php if ($_POST['submit'] == 'Save') { echo(" p I am processing your text: "); echo($_POST['myText']); echo(" /p } ? /body /html
清单 14 显示了将发布到清单 13 所示表单中的表单。要尝试此操作,您可以把该表单放到 Web 站点中,然后把清单 14 中的代码另存为桌面上的 HTML 文档。在保存表单后,在浏览器中打开该表单。然后可以填写数据并提交表单,从而观察如何处理数据。 清单 14. 收集数据的表单 复制代码 代码如下: html head title Collecting your data /title /head body form action="processStuff.php" method="post" select name="answer" option value="Yes" Yes /option option value="No" No /option /select input type="submit" value="Save" name="submit" / /form /body /html
表单欺骗的潜在影响是,如果拥有含下拉框、单选按钮、复选框或其他限制输入的表单,则当表单被欺骗时这些限制没有任何意义。考虑清单 15 中的代码,其中包含带有无效数据的表单。 清单 15. 带有无效数据的表单 复制代码 代码如下: html head title Collecting your data /title /head body form action="http://path.example.com/processStuff.php" method="post" input type="text" name="answer" value="There is no way this is a valid response to a yes/no answer..." / input type="submit" value="Save" name="submit" / /form /body /html
思考一下:如果拥有限制用户输入量的下拉框或单选按钮,您可能会认为不用担心验证输入的问题。毕竟,输入表单将确保用户只能输入某些数据,对吧?要限制表单欺骗,需要进行验证以确保发布者的身份是真实的。您可以使用一种一次性使用标记,虽然这种技术仍然不能确保表单绝对安全,但是会使表单欺骗更加困难。由于在每次调用表单时都会更改标记,因此想要成为攻击者就必须获得发送表单的实例,去掉标记,并把它放到假表单中。使用这项技术可以阻止恶意用户构建持久的 Web 表单来向应用程序发布不适当的请求。清单 16 提供了一种表单标记示例。 清单 16. 使用一次性表单标记 复制代码 代码如下: ?php session_start(); ? html head title SQL Injection Test /title /head body ?php echo 'Session token=' . $_SESSION['token']; echo ' br / echo 'Token from form=' . $_POST['token']; echo ' br / if ($_SESSION['token'] == $_POST['token']) { /* cool, it's all good... create another one */ } else { echo ' h1 Go away! /h1 } $token = md5(uniqid(rand(), true)); $_SESSION['token'] = $token; ? form id="myFrom" action=" ?php echo $_SERVER['PHP_SELF']; ? " method="post" div input type="hidden" name="token" value=" ?php echo $token; ? " / input type="text" name="myText" value=" ?php echo(isset($_POST['myText']) ? $_POST['myText'] : ''); ? " / input type="submit" value="Save" name="submit" / /div /form /body /html
CSRF 攻击通常是以 img 标记的形式出现的,因为浏览器将在不知情的情况下调用该 URL 以获得图像。但是,图像来源可以是根据传入参数进行处理的同一个站点中的页面 URL。当此 img 标记与 XSS 攻击结合在一起时 — 在已归档的攻击中最常见 — 用户可以在不知情的情况下轻松地对其凭证执行一些操作 — 因此是伪造的。 为了保护您免受 CSRF 攻击,需要使用在检验表单 post 时使用的一次性标记方法。此外,使用显式的 $_POST 变量而非 $_REQUEST。清单 18 演示了处理相同 Web 页面的糟糕示例 — 无论是通过 GET 请求调用页面还是通过把表单发布到页面中。 清单 18. 从 $_REQUEST 中获得数据 复制代码 代码如下: html head title Processes both posts AND gets /title /head body ?php if ($_REQUEST['submit'] == 'Save') { echo(" p I am processing your text: "); echo(htmlentities($_REQUEST['text'])); echo(" /p } ? /body /html
清单 19 显示了只使用表单 POST 的干净页面。 清单 19. 仅从 $_POST 中获得数据 复制代码 代码如下: html head title Processes both posts AND gets /title /head body ?php if ($_POST['submit'] == 'Save') { echo(" p I am processing your text: "); echo(htmlentities($_POST['text'])); echo(" /p } ? /body /html