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

80酷酷网    80kuku.com

  >

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. 下载文件
    

以下为引用的内容:
<?php
if ($_POST['submit'] == 'Download') {
    $file = $_POST['fileName'];
    header("Content-Type: application/x-octet-stream");
    header("Content-Transfer-Encoding: binary");
    header("Content-Disposition: attachment; filename=\"" . $file . "\";" );
    $fh = fopen($file, 'r');
    while (! feof($fh))
    {
        echo(fread($fh, 1024));
    }
    fclose($fh);
} else {
    echo("<html><head><");
        echo("title>Guard your filesystem</title></head>");
    echo("<body><form id=\"myFrom\" action=\"" . $_SERVER['PHP_SELF'] .
        "\" method=\"post\">");
    echo("<div><input type=\"text\" name=\"fileName\" value=\"");
    echo(isset($_REQUEST['fileName']) ? $_REQUEST['fileName'] : '');
    echo("\" />");
    echo("<input type=\"submit\" value=\"Download\" name=\"submit\" /></div>");
    echo("</form></body></html>");
}

正如您所见,清单 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 语句
    

以下为引用的内容:
<html>
<head>
<title>SQL Injection Example</title>
</head>
<body>
<form id="myFrom" action="<?php echo $_SERVER['PHP_SELF']; ?>"
    method="post">
<div><input type="text" name="account_number"
    value="<?php echo(isset($_POST['account_number']) ?
        $_POST['account_number'] : ''); ?>" />
<select name="col">
<option value="account_number">Account Number</option>
<option value="name">Name</option>
<option value="address">Address</option>
</select>
<input type="submit" value="Save" name="submit" /></div>
</form>
<?php
if ($_POST['submit'] == 'Save') {
    /* do the form processing */
    $link = mysql_connect('hostname', 'user', 'password') or
        die ('Could not connect' . mysql_error());
    mysql_select_db('test', $link);
  
  $col = $_POST['col'];

 

    $select = "SELECT " . $col . " FROM account_data WHERE account_number = "
        . $_POST['account_number'] . ";" ;
    echo '<p>' . $select . '</p>';

    $result = mysql_query($select) or die('<p>' . mysql_error() . '</p>');

    echo '<table>';
    while ($row = mysql_fetch_assoc($result)) {
        echo '<tr>';
        echo '<td>' . $row[$col] . '</td>';
        echo '</tr>';
    }
    echo '</table>';

    mysql_close($link);
}
?>
</body>
</html>

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


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

以下为引用的内容:

<html>
<head>
<title>SQL Injection Example</title>
</head>
<body>
<form id="myFrom" action="<?php echo $_SERVER['PHP_SELF']; ?>"
    method="post">
<div><input type="text" name="account_number"
    value="<?php echo(isset($_POST['account_number']) ?
        $_POST['account_number'] : ''); ?>" /> <input type="submit"
    value="Save" name="submit" /></div>
</form>
<?php
function isValidAccountNumber($number)
{
    return is_numeric($number);
}

if ($_POST['submit'] == 'Save') {

    /* Remember habit #1--validate your data! */
    if (isset($_POST['account_number']) &&
    isValidAccountNumber($_POST['account_number'])) {

        /* do the form processing */
        $link = mysql_connect('hostname', 'user', 'password') or
        die ('Could not connect' . mysql_error());
        mysql_select_db('test', $link);

        $select = sprintf("SELECT account_number, name, address " .
  " FROM account_data WHERE account_number = %s;",
        mysql_real_escape_string($_POST['account_number']));
        echo '<p>' . $select . '</p>';

        $result = mysql_query($select) or die('<p>' . mysql_error() . '</p>');

        echo '<table>';
        while ($row = mysql_fetch_assoc($result)) {
            echo '<tr>';
            echo '<td>' . $row['account_number'] . '</td>';
            echo '<td>' . $row['name'] . '</td>';
            echo '<td>' . $row['address'] . '</td>';
            echo '</tr>';
        }
        echo '</table>';

        mysql_close($link);
    } else {
        echo "<span .
    "Please supply a valid account number!</span>";

    }
}
?>
</body>
</html>

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

分享到
  • 微信分享
  • 新浪微博
  • QQ好友
  • QQ空间
点击: