rss· 投稿· 设为首页· 加入收藏· 繁體版
当前位置: 火魔网 » 程序开发 » PHP

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

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

<?php
session_start();
?>
<html>
<head>
<title>Storing session information</title>
</head>
<body>
<?php
if ($_POST['submit'] == 'Save') {
$_SESSION['userName'] = $_POST['userName'];
$_SESSION['accountNumber'] = $_POST['accountNumber']; ?>
<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="userName"
value="<?php echo(isset($_POST['userName']) ? $_POST['userName'] : ''); ?>" />
<br />
<input type="text" name="accountNumber"
value="<?php echo(isset($_POST['accountNumber']) ?
$_POST['accountNumber'] : ''); ?>" />
<br />
<input type="submit" value="Save" name="submit" /></div>
</form>
</body>
</html>

清单 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)
{ return (true);
}

function close()
{ return (true);
}

function read($id)
{ return (true);
}

function write($id, $sess_data)
{ return (true);
}

function destroy($id)
{ return (true);
}

function gc($maxlifetime)
{ return (true);
}

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

针对 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>

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

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

<script type="text/javascript">myRef = window.open('http://www.google.com','mywin',
'left=20,top=20,width=500,height=500,toolbar=1,resizable=0');</script>

要防止受到 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']) {

} 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 进行保护

跨站点请求伪造(CSRF 攻击)是利用用户权限执行攻击的结果。在 CSRF 攻击中,您的用户可以轻易地成为预料不到的帮凶。清单 17 提供了执行特定操作的页面示例。此页面将从 cookie 中查找用户登录信息。只要 cookie 有效,Web 页面就会处理请求。

清单 17. CSRF 示例

<img src="http://www.example.com/processSomething?id=123456789" />

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>

顶一下
(0)
踩一下
(0)