2008年9月25日星期四

Top 7 PHP Security Blunders的翻译 - 喜悦国际村

Top 7 PHP Security Blunders

PHP的7大安全错误

By Pax Dickinson
December 21st 2005
Reader Rating: 8.6



PHP is a terrific language for the rapid development of dynamic Websites. It also has many features that are friendly to beginning programmers, such as the fact that it doesn\'t require variable declarations. However, many of these features can lead a programmer inadvertently to allow security holes to creep into a Web application. The popular security mailing lists teem with notes of flaws identified in PHP applications, but PHP can be as secure as any other language once you understand the basic types of flaws PHP applications tend to exhibit.

为了适应动态网站的迅速发展,PHP是一个极好的语言选择。对初学者它有很多友好的特性,例如不需要声明变量类型。同时,这些特性也会使开发者无意之中在程序里留下安全漏洞。一些流行的安全相关的邮件列表描述了很多PHP应用程序的漏洞,但是只要明白了容易犯的基本安全错误,PHP可以像其他任何语言一样安全。

In this article, I\'ll detail many of the common PHP programming mistakes that can result in security holes. By showing you what not to do, and how each particular flaw can be exploited, I hope that you\'ll understand not just how to avoid these particular mistakes, but also why they result in security vulnerabilities. Understanding each possible flaw will help you avoid making the same mistakes in your PHP applications.

再这篇文章里,我会详细的阐述一些经常导致安全漏洞的PHP编程错误。在告诉你不该做什么和每一个漏洞是怎么产生的时候,我希望你不仅能理解怎样去避免这些错误,而且也能明白为什么会产生这些安全漏洞。明白了每一个潜在的安全漏洞会帮助你避免再犯同样的错误。

Security is a process, not a product, and adopting a sound approach to security during the process of application development will allow you to produce tighter, more robust code.

安全措施是一个过程,不是一个产物。在开发过程中采用一个好的安全处理方法会让你写出更严谨和健壮的代码。

Unvalidated Input Errors

未经验证的输入

One of -- if not the -- most common PHP security flaws is the unvalidated input error. User-provided data simply cannot be trusted. You should assume every one of your Web application users is malicious, since it\'s certain that some of them will be. Unvalidated or improperly validated input is the root cause of many of the exploits we\'ll discuss later in this article.

PHP中最常见的安全隐患之一是没有经过验证的输入错误。用户提供的数据通常是不能信任的,所以应当假设每一个web访问者怀有恶意—事实上他们当中确实有一些是这样的。没有经过验证或者验证不当的输入是其他许多溢出错误的根源。

As an example, you might write the following code to allow a user to view a calendar that displays a specified month by calling the UNIX cal command.

举一个例子,你可能会用下面的代码调用UNIX的cal命令实现月历功能

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

exec(\"cal $month $year\", $result);
print \"
\";
foreach ($result as $r) { print \"$r
\"; }
print \"
\";

This code has a gaping security hole, since the $_GET[month] and $_GET[year] variables are not validated in any way. The application works perfectly, as long as the specified month is a number between 1 and 12, and the year is provided as a proper four-digit year. However, a malicious user might append \";ls -la\" to the year value and thereby see a listing of your Website\'s html directory. An extremely malicious user could append \";rm -rf *\" to the year value and delete your entire Website!

这段代码由一个很大的漏洞,$_GET[month]和$_GET[year]变量没有经过任何验证。只要输入的月份是介于1和12之间的数字并且年份是一个4位数字,这段代码运行良好。但是,恶意的用户只要在年份的后面加上”;ls -la”就会列出整个网站的web目录。同样另一个相当危险的漏洞,”;rm -rf”的后缀会删除所有的网页。

The proper way to correct this is to ensure that the input you receive from the user is what you expect it to be. Do not use JavaScript validation for this; such validation methods are easily worked around by an exploiter who creates their own form or disables javascript. You need to add PHP code to ensure that the month and year inputs are digits and only digits, as shown below.

正确的处理方法是确保你得到的数据是你预期的。不要用Javascript来验证,恶意用户会创建自己的表单来禁用javascript,因此会很容易的绕过验证。正如下面列出的,你必须用PHP代码确保月份和年份的输入是数字,并且只能是数字。

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

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

exec(\"cal $month $year\", $result);
print \"
\";
foreach ($result as $r) { print \"$r
\"; }
print \"
\";

This code can safely be used without concern that a user could provide input that would compromise your application, or the server running it. Regular expressions are a great tool for input validation. They can be difficult to grasp, but are extremely useful in this type of situation.

这些代码可以安全的使用,并且不用担心用户的输入是否会危及到应用程序和服务器的安全。正则表达式是检验输入的很好的工具,虽然他们不容易掌握,但在这方面确实非常有用。

You should always validate your user-provided data by rejecting anything other than the expected data. Never use the approach that you\'ll accept anything except data you know to be harmful -- this is a common source of security flaws. Sometimes, malicious users can get around this methodology, for example, by including bad input but obscuring it with null characters. Such input would pass your checks, but could still have a harmful effect.

你应当在验证用户数据时把所有非预期的数据剔除掉,而不是仅仅剔除有危害的数据—这是经常见的安全漏洞原因。有时,恶意用户会绕过这些常用的验证方法,例如输入一些带有null的非法数据。这些做法通常会绕过检验,但仍然会对安全产生威胁。

You should be as restrictive as possible when you validate any input. If some characters don\'t need to be included, you should probably either strip them out, or reject the input completely.

你应当尽可能严格的检验输入的数据。如果有些字符不会被用到,那么就应该剔除掉或者拒绝此次的全部输入。

Access Control Flaws

权限控制漏洞

Another type of flaw that\'s not necessarily restricted to PHP applications, but is important nonetheless, is the access control type of vulnerability. This flaw rears its head when you have certain sections of your application that must be restricted to certain users, such as an administration page that allows configuration settings to be changed, or displays sensitive information.

另一种易受安全威胁的就是权限控制,虽然并非只针对PHP,但仍然是不容忽视的。这种隐患通常存在于针对特定用户的应用程序,例如后台管理这样可以修改配置或者显示敏感数据的地方。

You should check the user\'s access privileges upon every load of a restricted page of your PHP application. If you check the user\'s credentials on the index page only, a malicious user could directly enter a URL to a \"deeper\" page, which would bypass this credential checking process.

你应当在每一个针对特定用户的页面检查用户的权限级别。如果只在首页检查,那么一个恶意用户就可以直接在地址栏里输入通过检查之后调用的页面,这样就可以跳过身份验证。

It\'s also advisable to layer your security, for example, by restricting user access on the basis of the user\'s IP address as well as their user name, if you have the luxury of writing an application for users that will have predictable or fixed IPs. Placing your restricted pages in a separate directory that\'s protected by an apache .htaccess file is also good practice.

对安全分级是非常明智的,如果你的用户IP是固定的或者在特定范围之内,那么就可以根据用户的IP和用户名对权限做出控制。把特定的页面放在特定的目录,并用apache的.htaccess保护起来,是非常好的做法。

Place configuration files outside your Web-accessible directory. A configuration file can contain database passwords and other information that could be used by malicious users to penetrate or deface your site; never allow these files to be accessed by remote users. Use the PHP include function to include these files from a directory that\'s not Web-accessible, possibly including an .htaccess file containing \"deny from all\" just in case the directory is ever made Web-accessible by adiminstrator error. Though this is redundant, layering security is a positive thing.

把保存配置的文件放在web目录之外。一个配置文件可以保存数据库密码或者其他可以让恶意用户入侵或修改网站的重要信息;绝对不要让这些文件可以被远程用户访问到。用PHP的include函数包含web目录之外的文件,这些目录里也要放一个含有”deny from all”的.htaccess文件,防止管理员的疏忽而让这些目录称为web目录。虽然这显得有些多余,但对于安全仍然是一个积极的做法。

For my PHP applications, I prefer a directory structure based on the sample below. All function libraries, classes and configuration files are stored in the includes directory. Always name these include files with a .php extension, so that even if all your protection is bypassed, the Web server will parse the PHP code, and will not display it to the user. The www and admin directories are the only directories whose files can be accessed directly by a URL; the admin directory is protected by an .htaccess file that allows users entry only if they know a user name and password that\'s stored in the .htpasswd file in the root directory of the site.

在我做的PHP程序当中,我比较喜欢下面列出的目录结构。所有的函数库,类文件和配置文件都放在include目录里。这些文件都要以.php结尾,目的就是在保护措施失效的情况下,Web服务器会对这些文件解析,而不是直接显示出内容。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

You should set your Apache directory indexes to \'index.php\', and keep an index.php file in every directory. Set it to redirect to your main page if the directory should not be browsable, such as an images directory or similar.

你应当设置Apache的索引文件为index.php,并且在每一个目录里都放置一个index.php文件。那些不可以浏览目录里的index.php文件都应当指向你的主页,例如放置图片的目录的index.php等。

Never, ever, make a backup of a php file in your Web-exposed directory by adding .bak or another extension to the filename. Depending on the Web server you use (Apache thankfully appears to have safeguards for this), the PHP code in the file will not be parsed by the Web server, and may be output as source to a user who stumbles upon a URL to the backup file. If that file contained passwords or other sensitive information, that information would be readable -- it could even end up being indexed by Google if the spider stumbled upon it! Renaming files to have a .bak.php extension is safer than tacking a .bak onto the .php extension, but the best solution is to use a source code version control system like CVS. CVS can be complicated to learn, but the time you spend will pay off in many ways. The system saves every version of each file in your project, which can be invaluable when changes are made that cause problems later.

绝对不要在web目录里存放.bak结尾的备份文件或以其他扩展名结尾的文件。根据不同的web服务器,上述文件类型中所包含的PHP代码不会被服务器解析,很可能会直接向用户输出源代码。如果这些文件包含密码或者其他敏感信息,那么这些信息将是可读的—如果被Google的机器人捕捉到,这些信息很可能会被列入搜索引擎的索引中。将.php后缀到.bak文件比相反的做法更安全,但最好的解决办法是用一个源码版本控制系统如CVS。学习CVS可能会复杂一些,但这是值得的,这个系统可以保护每一个版本的每一个文件。

Session ID Protection

会话ID的保护

Session ID hijacking can be a problem with PHP Websites. The PHP session tracking component uses a unique ID for each user\'s session, but if this ID is known to another user, that person can hijack the user\'s session and see information that should be confidential. Session ID hijacking cannot completely be prevented; you should know the risks so you can mitigate them.

截获会话Id可以说是PHP网站所面临的一个问题。PHP的会话跟踪系统使用一个唯一ID,如果这个ID被其他用户得到,那么这个用户就可以截获这个会话进而看到一些私密信息。截获会话ID通常很难完全避免;你必须明白它的危险来尽可能的减少这种隐患。

For instance, even after a user has been validated and assigned a session ID, you should revalidate that user when he or she performs any highly sensitive actions, such as resetting passwords. Never allow a session-validated user to enter a new password without also entering their old password, for example. You should also avoid displaying truly sensitive data, such as credit card numbers, to a user who has only been validated by session ID.

例如,即使在一个用户经过身份验证并分配了一个会话ID之后,在它执行一个高度敏感的动作例如修改密码时,仍然要重新验证身份。绝对不要让一个仅通过会话验证的用户在不输入旧密码的情况下去修改密码。你也应当避免直接向一个仅通过会话ID验证的用户显示高度敏感的数据,例如信用卡号。

A user who creates a new session by logging in should be assigned a fresh session ID using the session_regenerate_id function. A hijacking user will try to set his session ID prior to login; this can be prevented if you regenerate the ID at login.

一个用户在登录网站之后应当通过session_regenerate_id分配一个新的会话ID。这样就可以阻止一个恶意用户会用以前的会话ID去尝试登录。

If your site is handling critical information such as credit card numbers, always use an SSL secured connection. This will help reduce session hijacking vulnerabilities since the session ID cannot be sniffed and easily hijacked.

如果你的网站会处理一些像信用卡密码这样的机密信息,那么一定要使用SSL连接。这样会话ID不会被探嗅到而且不容易被截获,就可以减少会话截获的危险。

If your site is run on a shared Web server, be aware that any session variables can easily be viewed by any other users on the same server. Mitigate this vulnerability by storing all sensitive data in a database record that\'s keyed to the session ID rather than as a session variable. If you must store a password in a session variable (and I stress again that it\'s best just to avoid this), do not store the password in clear text; use the sha1() (PHP 4.3+) or md5() function to store the hash of the password instead.

如果你的站点运行在一个共享主机上,需要注意会话变量可以很容易的被同一服务器上的其他用户浏览。为了减少此类风险,可以把敏感的数据以会话ID为主键保存在数据库中,这样比直接保存在会话变量中要好的多。如果必须要在会话变量中保存密码(我还是要强调尽量避免这样做),不要直接保存密码的明文,用sha1或者md5函数加密后保存在会话变量中。

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

The above code is not secure, since the password is stored in plain text in a session variable. Instead, use code more like this:

上面的代码把密码以平文保存在会话变量中,这样是不安全的。应该这样作:

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

The SHA-1 algorithm is not without its flaws, and further advances in computing power are making it possible to generate what are known as collisions (different strings with the same SHA-1 sum). Yet the above technique is still vastly superior to storing passwords in clear text. Use MD5 if you must -- since it\'s superior to a clear text-saved password -- but keep in mind that recent developments have made it possible to generate MD5 collisions in less than an hour on standard PC hardware. Ideally, one should use a function that implements SHA-256; such a function does not currently ship with PHP and must be found separately.

SHA-1算法并不是一点风险也没有,随着计算机计算能力的不断加强,使得用“碰撞”的暴力方法可以破解。但是这样的技术仍然要比直接保存密码的明文好的多。如果必须,可以用MD5算法,它比明文保存密码安全,但最近的研究表明MD5的“碰撞”可以在一台普通PC上不到一个小时就可以算出。理论上,应当使用SHA-256这样的安全算法,但是这个算法目前并不被PHP默认支持,需要另外的扩展支持。

For further reading on hash collisions, among other security related topics, Bruce Schneier\'s Website is a great resource.

如果要获取更多关于散列碰撞的安全相关文章,Bruce Schneier\'s Website 是一个不错的站点。

Cross Site Scripting (XSS) Flaws

跨站脚本攻击

Cross site scripting, or XSS, flaws are a subset of user validation where a malicious user embeds scripting commands -- usually JavaScript -- in data that is displayed and therefore executed by another user.

跨站脚本攻击或者XSS,是恶意用户利用验证上的漏洞将脚本命令嵌入到可以显示的数据中,使其在另一个用户浏览时可以执行这些脚本命令。

For example, if your application included a forum in which people could post messages to be read by other users, a malicious user could embed a

To prevent this type of attack, you need to be careful about displaying user-submitted content verbatim on a Web page. The easiest way to protect against this is simply to escape the characters that make up HTML syntax (in particular, < and >) to HTML character entities (< and >), so that the submitted data is treated as plain text for display purposes. Just pass the data through PHP\'s htmlspecialchars function as you are producing the output.

要阻止这样的攻击,首先要特别注意怎样显示用户提交的数据。最简单的方法就是将HTML语法的字符(特别注意<和>)转化为HTML实体,这样就可以将用户提交的数据转化为作为显示的文本。因此,只要在显示的时候将数据用htmlspecialchars函数过滤一下即可。

If your application requires that your users be able to submit HTML content and have it treated as such, you will instead need to filter out potentially harmful tags like