一、简介
1.PHP安全方面的基本功能
1.1全局变量注册
事实上,全局变量是无辜的,它不会产生安全漏洞。
一般我们会关闭全局变量的原因是:它会增加安全漏洞的数量;隐藏了数据的来源,与开发者需要随时跟踪数据的责任违背;
注:如果必须在一个开启了register_globals的环境部署应用程序时,重要的一点是,必须要初始化所有变量,并且把error_reporting设为E_ALL(或E_ALL | E_STRICT)以对未初始化变量进行警告。
当register_globals开始时,任何使用未初始化变量的行为就意味着安全漏洞。
1.2错误报告" title="错误报告" >错误报告
php的错误报告" title="错误报告" >错误报告方便了我们开发时对错误的确认和定位,但这些错误描述如果被恶意攻击者看到,就不妙了。所以开发结束后我们要关闭错误报告" title="错误报告" >错误报告。
ini_set('error_reporting', E_ALL | E_STRICT);
ini_set('display_errors', 'Off');
ini_set('log_errors', 'On');
ini_set('error_log', '/usr/local/apache/logs/error_log');
?>
注:也可以通过set_error_handle()函数制定自己的出错处理函数。
2.PHP安全方面的原则
深度防范:时刻有一个安全备份方案
最小权限:不必要的授权会加大风险
简单是美:没有必要的复杂是糟糕的
最少暴露:尽量降低数据源的被暴露
3.PHP安全方面的方法
3.1平衡风险和可用性
友好的用户操作与安全措施是一对矛盾,提高安全性的同时,通常会降低可用性。尽量使安全措施对用户透明,使他们感受不到它的存在。
3.2跟踪数据
区分可信和不可信数据的来源。
知道数据在哪进入程序,同时知道数据在哪离开程序。
审核PHP代码时候有安全漏洞时,主要检查代码中与外部系统交互的部分。
3.3过滤输入
步骤:识别输入,过滤输入,区分已过来率及被污染数据。
把ssion" title="session" >session保存位置与数据库" title="数据库" >数据库看成是输入更安全。
防止非法数据进入应用程序。
最好的数据过滤方法是把过滤看成是一个检查的过程,不要试图好心的去纠正非法数据!要让用户按照你的规划去做。任何试图纠正非法数据的举动都可能导致潜在错误并允许非法数据通过。只做检查更安全。
使用一个命名约定或其他可以帮助你正确和可靠区分已过滤和被污染数据的方法。
比如,把所有经过滤的数据放入$clean的数组中,
1.初始化$clean为一个空数组
2.加入检查及阻止来自外部数据源的变量命名为 clean
考虑下面的表单,允许用户选择三种颜色中的一种
选择一种颜色:
red
green
blue
在处理这个表单的编程逻辑中,非常容易犯的错误是认为只能提交三个选择中的一个。为了正确地过滤数据,你需要用一个 switch 语句" title="语句" >语句来进行:
$clean = array( );
switch($_POST['color'])
{
case 'red':
case 'green':
case 'blue':
$clean['color'] = $_POST['color'];
break;
}
?>
上面的方法对于过滤有一组已知的合法值的数据很有效,但是对于过滤有一组已知合法字符组成的数据时就没有什么帮助。例如,你可能需要一个用户名" title="用户名" >用户名只能由字母及数字组成:
$clean = array( );
if (ctype_alnum($_POST['username'])){//ctype_alnum():只允许字母和数字
$clean['username'] = $_POST['username'];
}
?>
3.4输出转义
对输出进行转义或对特殊字符进行编码,以保证原意不变。
例如, O'Sjolzy 在传送给 MySQL 数据库" title="数据库" >数据库前需要转义成 O\'Sjolzy 。
步骤:识别输出,输出转义,区分已转义与未转义数据。
对于一些常见的输出目标(包括客户端、数据库" title="数据库" >数据库和 URL )的转义, PHP中有内置函数可用 。
最常见的输出目标是客户机,使用 htmlentities( ) 在数据发出前进行转义。
使用 htmlentities( )函数的最佳方式是指定它的两个可选参数:引号的转义方式(第二参数)及字符集" title="字符集" >字符集(第三 参数)。
$html = array( );
$html['username'] = htmlent ities($clean['username'], ENT_QUOTES, 'UTF-8');
echo "
Welcome back, {$html['username']}.
";?>
注:htmlspecialchars( ) 函数与 htmlent ities( ) 函数基本相同,它们的参数定义完全相同,只不过是htmlent ities( ) 的转义更为彻底。
另外一个常见的输出目标是数据库" title="数据库" >数据库。如果可能的话,你需要对 SQL 语句" title="语句" >语句中的数据使用 PHP内建函数进行转义。对于 MySQL用户,最好的转义函数是 sql" title="mysql" >mysql_real_escape_string( ) 。
$sql" title="mysql" >mysql = array( );
$sql" title="mysql" >mysql['username'] = sql" title="mysql" >mysql_real_escape_string($clean['username']);
$sql = "SELECT *
FROM profile
WHERE username = '{$sql" title="mysql" >mysql['username']}'";
$result = sql" title="mysql" >mysql_query($sql);
?>
二、表单和URL
1 表单与数据
在典型的 PHP 应用开发中,大多数的逻辑涉及数据处理任务。
数据可能有无数的来源,简单可靠区分两类数据:已过滤数据、被污染数据。
自己设定的数据可信数据,可以认为是已过滤数据。而所有的输入数据都是被污染的,必须在要在使用前对其进行过滤。
注:数据是关键,而不是变量。变量只是数据的容器,它往往随着程序的执行而为 被污染数据所覆盖。如果你不希望数据进行变化,可以使用常量来代替define(define)。常量如果不小心被重新赋值,会引起一个级别为Notice的报错信息。
一个用户可以通过三种方式向应用程序传输数据:
·通过 URL( 如 GET 数据方式 )
·通过一个请求的内容(如 POST 数据方式
·通过 HTTP 头部信息(如 Cookie )
2.语义" title="语义" >语义 URL 攻击
此类攻击主要包括对 URL 进行编辑以期发现一些有趣的事情。
例如,用户 sjolzy 点击了你的软件中的一 个链接并到达了页面 http://sjolzy.cn/msg.php?user=sjolzy,很自然地他可能会试图改变 user的值,看看会发生什么。
如果使用 ssion" title="session" >session 跟踪,就可以很方便地避免上述情况的发生了。
下面是一个 Webmail 系统的例子:
ssion" title="session" >session_start();
$clean = array();
$email_pattern = '/^[^@\s]+@([-a-z0-9]+\.)+[a-z]{2,}$/i';
if (preg_match($email_pattern, $_POST['email'])){
$clean['email'] = $_POST['email'];
$user = $_SESSION['user'];
$new_password = md5(uniqid(rand(), TRUE));
if ($_SESSION['verified']){
/* Update Password */
mail($clean['email'], 'Your New Password', $new_password);
}
}
?>
上例示范了对用户提供的帐户不加以信任,同时更重要的是使用 ssion" title="session" >session 变量为保存用户是否正确回答了问题($_SESSION['verified']) ,以及正确回答问题的用户 ($_SESSION['user'])。
3.文件上传攻击
文件在表单中传送时与其它的表单数据不同,你必须指定一个特别的编码方式 multipart/form-data :
为了更好地演示文件上传机制,下面是一个允许用户上传附件的例子:
Please choose a file to upload:
隐藏的表单变量 MAX_FILE_SIZE 告诉了浏览器最大允许上传的文件大小。但这一限制很容易被攻击者绕开,在服务器上进行该限制才是可靠的。
PHP 的配置变量中, upload_max_f ilesize 控制最大允许上传的文件大小。同时 post_max_size( POST 表单的最大提交数据的大小)也能潜在地进行控制,因为文件是通过表单数据进行上传的。
接收程序 upload.php 显示了超级全局数组 $_FILES 的内容:
header('Content-Type: text/plain');
print_r($_FILES);
?>
PHP提供了两个方便的函数以减轻这些理论上的风险:is_uploaded_file( ) 和move_uploaded_file( )。如果你需要确保tmp_name 中的文件是一个上传的文件,你可以用is_uploaded_file( ):
$lename" title="filename" >filename = $_FILES['attachment']['tmp_name'];
if (is_uploaded_f ile($lename" title="filename" >filename)){
/* $_FILES['attachment']['tmp_name'] is an uploaded file. */
}
?>
如果你希望只把上传的文件移到一个固定位置,你可以使用 move_uploaded_file( ) :
$old_f ilename = $_FILES['attachment']['tmp_name'];
$new_lename" title="filename" >filename = '/path/to/attachment.txt';
if (move_uploaded_f ile($old_f ilename, $new_lename" title="filename" >filename)){
/* $old_lename" title="filename" >filename is an uploaded file, and the move was successful. */
}
?>
最后你可以用filesize( )来校验文件的大小:
$lename" title="filename" >filename = $_FILES['attachment']['tmp_name'];
if (is_uploaded_file($lename" title="filename" >filename)){
$size = filesize($lename" title="filename" >filename);
}
?>