安全
以CGI模式安装
CGI、FastCGI模式
CGI是通用网关(Common Gateway Interface)接口,简单说就是连接Web服务器中的执行程序和网页资源的一座桥,它把网页接收的请求交给服务器的执行程序,再把服务器执行程序的结果返还给网页。每个请求到来时,都会先创建一个CGI的子进程,处理请求,结束子进程。以执行php请求为例,CGI会重新解析php.ini、重新载入全部dll扩展并重初始化全部数据结构,解释执行php脚本,如果请求的数量大时,频繁的反复加载CGI子进程会大量挤占系统资源和内存,导致性能低下。这时候就有了CGI的升级版本:FastCGI模式,它像一个常驻型的CGI,只要启动后,就会有固定数量的CGI进程在那里等待接收请求并执行,执行完请求之后并不结束进程,而是把这个进程放回可用的进程池里面继续等待新的请求。CGI的进程有动态伸缩性质,它启动时自动fork一个进程池,会选出一个空闲的进程执行新的请求,当进程池没有空闲进程时,会fork出新的子进程执行,但是在php-fpm.conf里面有配置cgi进程数量的参数,设置了最大进程数和最小进程数,当请求数量超过最大进程数时,php就不能执行新的请求了。
CGI模式安装可能会受到的攻击
CGI模式安装通常会把php的可执行文件安装到web服务器的cgi-bin目录,这样在访问系统文件时可以把cgi-bin目录添加在url里面,栗子:http://my.host/cgi-bin/php?/etc/passwd ?后面的参数表示php的命令行参数,请求这个URL,web服务器会执行 php /etc/passwd 命令,这样一来,我想要运行服务器上的那个脚本,只需要知道脚本所在的位置并把它加在url里面,就可以任意执行了,这样是很不安全的。下面介绍几种方式可以减少这种攻击的伤害。
1、权限控制
在每个脚本所在的目录都设置一个检查权限的脚本,如果有权限访问,再执行脚本。栗子:访问http://my.host/cgi-bin/php/secret/doc.html 想要访问/secret/doc.html,创建http://my.host/cgi-bin/php/secret/check.php的重定向,先从这个脚本检查权限,通过验证再去执行脚本。
2、使用 --enable-force-cgi-redirect 选项
在编译PHP的configure脚本中指定参数: --enable-force-cgi-redirect,可以防止任何人通过http://my.host/cgi-bin/php/secretdir/script.php这样的URL直接调用php,HP 在此模式下只会解析已经通过了 web 服务器的重定向规则的 URL。
3、设置 doc_root 或 user_dir
配置文件的 doc_root选项 或设置环境变量 PHP_DOCUMENT_ROOT可以定义php 脚本执行目录,如果设置了该项,那么php就只会解释 doc_root 目录下的文并且目录外的脚本不会被php解释器执行,但是user_dir除外。另外一个user_dir选项,当这个选项没有被设置时,doc_root是唯一决定哪里的脚本能被执行的选项。设置了这个选项,就会执行用户主目录下的user_dir子目录下的文件,栗子:设置user_dir=public_php,那么访问http://my.host/~user/doc.php,就会访问用户主目录下的public_php子目录下的doc.php文件。如果用户的主目录是/home/user,那么将会执行文件/home/user/public_php/doc.php。
4、php解释器放在 web 目录以外
一个非常安全的做法就是把php解释器放在 web 目录外的地方,比如说 /usr/local/bin。这样做唯一不便的地方就是必须在每一个包含 PHP 代码的文件的第一行加上#!/usr/local/bin/php,还要将这些文件的属性改成可执行。
以apache模块安装
当 PHP 以 Apache 模块方式安装时,它将继承 Apache 用户(通常为“nobody”)的权限。就是说,如果用php操作数据库,而数据库刚好没有做访问控制,apache用户是nobody,那么php也是用nobody访问数据库,一些恶意的脚本即使不提供用户名和密码就可以操作数据库了。
文件系统安全
如果我们的web服务提供这样一个功能:用户可以删除服务器上某些文件,只需要在表单中提交要删除的文件即可。下面是代码实现:
<pre>
$username = $_POST['user_submitted_name’];//要删除的文件目录
$userfile = $_POST['user_submitted_filename’];//要删除的文件名
$homedir = "/home/$username";
unlink ("$homedir/$userfile");
echo "The file has been deleted!";
</pre>
如果用户提交的文件是/etc/passwd,而web服务器有权限可以删除这个文件,那么重要的文件就这么被删除了。可以用2个措施来防止这类问题:1、只给web用户很小的权限 2、检查所有提交上来的变量,下面是改进的脚本:
<pre>
$username = $_SERVER['REMOTE_USER']; // 使用认证机制
$userfile = basename($_POST['user_submitted_filename']);
$homedir = "/home/$username”;
$filepath = "$homedir/$userfile";
if (file_exists($filepath) && unlink($filepath)) {
//检查用户要删除的是不是他自己目录下的文件
$logstring = "Deleted $filepath\n";
} else {
$logstring = "Failed to delete $filepath\n";
}
$fp = fopen("/home/logging/filedelete.log", "a");
fwrite ($fp, $logstring);
fclose($fp);
echo htmlentities($logstring, ENT_QUOTES);
</pre>
但是,如果用户用../etc/作为自己的用户名呢?这个时候还需要对文件名进行检查,检查是否是字母、数字、下划线的组合。
数据库安全
数据库已经成为各个动态网站上的重要组成部分,各种用户隐私、敏感数据等都保存在数据库中,所以数据库安全尤为重要。下面介绍一下我们在使用数据库时应该注意哪些安全方面的问题。
设计数据库
第一步一般都是创建数据库,当创建一个数据库的时候,会指定一个所有者来执行新建数据库的sql语句,只有所有者或超级用户才有权任意操作数据库,如果想让其他用户使用,必须授权。应用程序永远不要使用数据库所有者或超级用户帐号来连接数据库,因为这些帐号可以执行任意的操作,比如说修改数据库结构(例如删除一个表)或者清空整个数据库的内容。应该为应用程序的不同功能创建不同的数据库账号,并且只赋予他们功能所需要的对数据库对象操作的权限,避免同一个用户可以完成另一个用户的事。最好不要把所有的事务逻辑都用脚本实现,最好用视图(view)、触发器(trigger)或者规则(rule)在数据库层面完成。
连接数据库
把连接建立在 SSL 加密技术上可以增加客户端和服务器端通信的安全性,或者 SSH 也可以用于加密客户端和数据库之间的连接。
加密存储模型
ssh/ssl只能加密客户端和服务端交换的数据,并不难保护数据库中已有的数据。所以创建自己的加密机制,在保存数据库之前,先把数据加密然后再存储,读数据时再解密。
SQL注入
最终执行的sql语句中一般都包含用户提交的数据,如果不对用户提交的数据进行验证的话会有很大的安全漏洞,栗子:一段实现数据分页显示的代码,也可以被用作创建一个超级用户(PostgreSQL系统)。
<pre>
$offset = $argv[0]; // 注意,没有输入验证!
$query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;";
$result = pg_query($conn, $query);
</pre>
一般的用户只会点击$offset被赋值的“上一页”、“下一页”的链接,这样$offset只是一个数值,但是如果有人把下面的语句先经过urlencode()处理,再加入url中的话,那么他就可以创建一个超级用户了,一开始的”0”只是为了提供一个正确的偏移量以便补充完整原来的查询,”—“是sql中的注释,表示告诉sql解释器忽略后面的语句。
<pre>
0;
insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd)
select 'crack', usesysid, 't','t','crack'
from pg_shadow where usename='postgres';
</pre>
预防sql注入的方法
1、不要使用超级用户或所有者账号去连接数据库,要用权限被严格控制的账号
2、检查输入的数据格式是否正确
3、用settype()函数来转换数据类型,也可以用sprintf()把它格式化为数字。
4、使用数据库特定的敏感字符转义函数(比如 mysql_escape_string() 和 sql_escape_string())把用户提交上来的非数字数据进行转义。如果数据库没有专门的敏感字符转义功能的话 addslashes() 和 str_replace() 可以代替完成这个工作。
5、也可以选择使用数据库的存储过程和预定义指针等特性来抽象数库访问,使用户不能直接访问数据表和视图。但这个办法又有别的影响。
6、保存数据库的查询日志,虽然不能防止任何攻击,但利用日志可以跟踪到哪个程序曾经被尝试攻击过,方便分析问题。
错误报告
通常 PHP 所返回的错误提示都能帮助开发者调试程序,它会提出哪个文件的哪些函数或代码出错,并指出错误发生的在文件的第几行,这些就是 PHP 本身所能给出的信息,但是攻击者故意输入错误的数据,然后查看错误提示的类型和上下文,这样就可以收集服务器的信息以便寻找弱点。解决办法是在代码中利用 error_reporting() 函数来定位问题,使代码更安全。在发布程序之前,先打开 E_ALL 测试代码,可以帮你很快找到变量使用不当的地方。一旦准备正式发布,就应该把 error_reporting() 的参数设为 0 来彻底关闭错误报告或者把 php.ini 中的 display_errors 设为 off 来关闭所有的错误显示使代码不能被探测。