PHP安全编程

转自:http://www.nowamagic.net/librarys/veda/detail/2076

1.关闭register_globals,以提高安全性

2.在部署环境,不要让不相关的人看到报错信息,可以如此设置:

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_handler('my_error_handler');
function my_error_handler($number, $string, $file, $line, $context)
{
  $error = "= == == == ==\nPHP ERROR\n= == == == ==\n";
  $error .= "Number: [$number]\n";
  $error .= "String: [$string]\n";
  $error .= "File: [$file]\n";
  $error .= "Line: [$line]\n";
  $error .= "Context:\n" . print_r($context, TRUE) . "\n\n";
  error_log($error, 3, '/usr/local/apache/logs/error_log');
}

3.深度防范/最小权限/简单就是美/暴露最小化原则

4.平衡风险与可用性/跟踪数据

5.过滤用户输入

1)识别输入:_GET/_POST/_SERVER,session,数据库

2)过滤输入:防止非法数据进入你的应用,并且只做检查,不做纠正,提高安全性

3)区分已过滤及被污染数据:利用一个$clean数组来保存过滤后的数据,但是需要做两件事情:

a.经常初始化该数组为一个空数组

b.加入检查及阻止来自外部数据源的变量命名为clean

6.对输出要转义

1)识别输出:echo/print/printf/<?=

2)过滤输出:htmlentities/htmlspecialchars/mysql_real_escape_string/addslashes

3)区分已转义及未转义数据:利用一个$html数组来保存转义后的数据

7.利用session相关机制来避免URL语义攻击

8.防御文件上传攻击

1)限制上传大小:upload_max_filesize/post_max_size

2)对缓存区的数据进行确认:is_uploaded_file/move_uploaded_file/filesize

9.利用"过滤输入,转义输出"的原则来降低XSS攻击的风险

10.利用POST和Token来降低CSRF的风险

特别需要指出的是,习惯上GET与HEAD方式不应该用于引发一个操作,而只是用于获取信息。这些方式应该被认为是‘安全’的。客户浏览器应以特殊的方式,如POST,PUT或DELETE方式来使用户意识到正在请求进行的操作可能是不安全的

11.不要使用HTTP的头中的Referer,而是采用过滤输入的方式来避免欺骗表单的提交

12.还是采用过滤输入的方式来避免欺骗HTTP的接受

13.不要将配置文件放在根目录,也不要采用其他的特殊后缀名,防止暴露配置信息

14.利用"过滤输入,转义输出"的原则来降低SQL注入风险

1)利用参数化查询语句

2)配置magic_quotes_gpc自动对GET和POST数据进行addslashes处理

15.通过防止CSRF漏洞和修复暴露cookie的浏览器漏洞相结合来防止cookie盗窃

16.Session回话固定

SessionID是浏览器和后台交易的基础,浏览器可以通过固定或者捕获的方式来获得该数值,有以下几种防御方式:

1)可以在权限等级变化时,利用session_regenerate_id来每次重新生成

2)修改PHPSESSID,改成一些没有规律的名称

3)比较每次请求的$_SERVER['HTTP_USER_AGENT'],不同就有嫌疑

4)采用更为俺安全的SSL,防止信息被泄露

17.源代码的暴露

1)对包含文件使用非常用的扩展名,例如:inc等

2)包含文件保存在网站主目录下

3)Apache未设定.inc文件的类型

4)Apache的默认文件类型是text/plain

18.为了阻止文件名被操控,当然最有效的方法就是过滤输入,此时之外,还有以下几个方法

1)没有路径信息的文件名,用basename过滤

2)允许有路径信息但想要在检测前把它化简,用realpath过滤

3)还可以用pathinfo来获得路径信息

4)关闭allow_url_fopen,不能用include/require访问远程文件

19.通过限制登录次数或登录间隔,来限制暴力破解攻击

20.攻击者可以用抓包软件来嗅探用户密码,最好的方法是采用HTTPS

21.攻击者得到密码后,就可以进行重播攻击,可以采取的防御为

1)避免受保护资源永久访问权的的使用

2)避免受保护资源访问权的临时性

22.如果非要进行永久访问权的使用,可以采用的方法为

在username和password的基础上,加上identifier/token/timeout字段,加如二次验证

1)生成

$salt = 'SHIFLETT';
$identifier = md5($salt . md5($username . $salt));
$token = md5(uniqid(rand(), TRUE));
$timeout = time() + 60 * 60 * 24 * 7;
setcookie('auth', "$identifier:$token", $timeout);

2)验证

$clean = array();
$mysql = array();
$now = time();
$salt = 'SHIFLETT';
 
list($identifier, $token) = explode(':', $_COOKIE['auth']);
if (ctype_alnum($identifier) && ctype_alnum($token))
{
  $clean['identifier'] = $identifier;
  $clean['token'] = $token;
}
else
{
  /* ... */
}
 
$mysql['identifier'] = mysql_real_escape_string($clean['identifier']);
$sql = "SELECT username, token, timeout
        FROM users
        WHERE identifier = '{$mysql['identifier']}'";
if ($result = mysql_query($sql))
{
  if (mysql_num_rows($result))
  {
    $record = mysql_fetch_assoc($result);
    if ($clean['token'] != $record['token'])
    {
      /* Failed Login (wrong token) */
    }
    elseif ($now > $record['timeout'])
    {
      /* Failed Login (timeout) */
    }
    elseif ($clean['identifier'] != md5($salt . md5($record['username'] . $salt)))
    {
      /* Failed Login (invalid identifier) */
    }
    else
    {
      /* Successful Login */
    }
  }
  else
  {
    /* Failed Login (invalid identifier) */
  }
}
else
{
  /* Error */
}

3)退出

setcookie('auth', 'DELETED!', time());

23.尽量将Session数据保存在Mysql或Memcache中,减少因文件被操控后被猜中后泄露和操控的机会

24.PHP安全模式生效时,PHP会对正在执行的脚本所读取(或所操作)文件的属主进行检查,以保证与该脚本的属主是相同的,他是一种深度防范