PHP 安全与性能

目录

  • 1. Apache mod_php / php-fpm
    • 1.1.1. Apache
    • 1.1.2. Nginx / lighttpd + fastcgi
    • 1.1. 用户权限
    • 1.2. web server 版本信息
    • 1.3. php_flag / php_admin_flag
  • 2. php.ini
    • 2.2.1. chdir()函数安全演示
    • 2.1. Magic quotes
    • 2.2. 危险PHP函数
    • 2.3. 隐藏PHP版本信息
    • 2.4. session名字可以泄露你的服务器采用php技术
    • 2.5. 隐藏PHP出错信息
    • 2.6. open_basedir 防止操作web环境意外文件目录
  • 3. 开发于安全
    • 3.3.1. 禁止输出调试信息
    • 3.3.2. 预防SQL注入攻击
    • 3.3.3. SHELL 命令注入
    • 3.1. 彻底解决目录于文件的安全
    • 3.2. Session / Cookie安全
    • 3.3. 注入安全
  • 4. 执行效率
    • 4.1.1. mysql
    • 4.1. timeout
    • 4.2. 浏览器上传文件尺寸控制

1. Apache mod_php / php-fpm

目录权限安全

1.1. 用户权限

web server 启动用户不能于运行用户为同一个用户

web server 运行用户与php程序不能为同一个用户

root      1082  0.0  0.1  11484  2236 ?        Ss   Mar01   0:00 nginx: master process /usr/sbin/nginx
www-data 13650  0.0  0.0  11624  1648 ?        S    09:44   0:00 nginx: worker process
www-data 13651  0.0  0.0  11624  1132 ?        S    09:44   0:00 nginx: worker process
www-data 13652  0.0  0.0  11624  1132 ?        S    09:44   0:00 nginx: worker process
www-data 13653  0.0  0.0  11624  1132 ?        S    09:44   0:00 nginx: worker process			
  1. 父进程 root 启动 web server, 此时web server 父进程应该是 root,同时父进程监听80端口
  2. 子进程 父进程派生许多子进程,同时使用setuid,setgid将子进程权限切换为非root 子进程用户可以通过httpd.conf设置 User nobody Group nobody nginx.conf $ cat /etc/nginx/nginx.conf user www-data;
  3. fastcgi 进程 root 13082 0.0 0.1 19880 2584 ? Ss 09:28 0:00 php-fpm: master process (/etc/php5/fpm/php-fpm.conf) www-data 13083 0.0 0.1 20168 3612 ? S 09:28 0:00 php-fpm: pool www www-data 13084 0.0 0.1 20168 2808 ? S 09:28 0:00 php-fpm: pool www www-data 13085 0.0 0.1 20168 2812 ? S 09:28 0:00 php-fpm: pool www www-data 13086 0.0 0.1 20168 2812 ? S 09:28 0:00 php-fpm: pool www php-fpm 于apache类似,都是root父进程,然后派生子进程,由于fastcgi 使用 9000 所有我们可以不使用root启动php-fpm

现在我们开始讲解安全配置问题

我们目的是避免用户通过漏洞提升权限,或者由于权限配置不当产生漏洞

1.1.1. Apache

Apache 案例

  1. Apache : root
  2. Apache 子进程 : nobody
  3. HTDOCS 目录 : /var/www /var/www |--include |--image |--temp |--...

很多人会将/var/www用户与组设置为 nobody:nogroup / nobody:nobody, 同时因为images会上传文件需要设置777, 很多书本于教程上面也是这样讲的, 这样配置会有什么问题呢?我们来分析一下:

我们假设,一个用户上传一个文件到images目录,会有几种情况:

  1. 上传一个.php文件,我们可以通过程序禁止上传.php文件
  2. 我们上传一个.jpg文件,OK 通过了,通过某种手段将他重命名位.php扩展名的文件,然后通过http://www.example.com/images/your.php 运行它,your.php 可以做什么呢? 它可以查看所有文件,修改所有文件,创建其他php文件,去你可include目录下看config.php然后下载数据库。
  3. 内部开发人员偷偷将一个程序植入到系统中,这个做code review 可以避免

如何避免这样问题出现,有一个办法,我们新建一个用户www, webserver 进程是nobody,程序目录/var/www中的代码是www用户,nobody可能读取但不能修改。/var/www/images 目录所有者是nobody可以上传图片

				chown www /var/www/
chown nobody /var/www/images
find /var/www/ -type d -exec chmod 555 {} \;
find /var/www/ -type f -exec chmod 444 {} \;
chmod 755 /var/www/images				

使所有可能目录允许运行.php文件,http://www.example.com/images/your.php 将被拒绝. include 也是同样处理方式,只允许使用include_once,require_one 包含,不允许http://www.example.com/include/your.php运行

				<Location ~ "/((js/)|(css/)|(images/)).*\.php">
	Order Deny,Allow
	Deny from all
</Location>

<Location /includes/>
        Order allow,deny
        Deny from all
</Location>
<Location /library/>
        Order allow,deny
        Deny from all
</Location>

<Directory /var/www/themes/>
    <Files *.php>
		Order allow,deny
		Deny from all
    </Files>
</Directory>				

1.1.2. Nginx / lighttpd + fastcgi

Nginx / lighttpd 案例分析

  1. nginx / lighttpd : root
  2. web server 子进程 : nobody
  3. php-fpm : root
  4. php-fpm 子进程 : nobody
  5. HTDOCS 目录 : /var/www /var/www |--include |--image |--temp |--...

fastcgi 遇到的问题与上面apache案例中遇到的问题类似,不同是的fastcgi把动态于静态完全分开了,这样更容易管理,我们可以这样入手

  1. nginx / lighttpd : root
  2. web server 子进程 : nobody
  3. php-fpm : root
  4. php-fpm 子进程 : www
chown nobody /var/www/
chown www /var/www/images
find /var/www/ -type d -exec chmod 555 {} \;
find /var/www/ -type f -exec chmod 444 {} \;
chmod 755 /var/www/images				

/var/www所有权限给nobody, images权限给www, 同时保证www用户可以读取/var/www下的程序文件

location ~ ^/upload/.*\.php$
{
        deny all;
}

location ~ ^/static/images/.*\.php$
{
        deny all;
}

location ~ /include/.*\.php$ {
    deny all;
}

location ~ .*\.(sqlite|sq3)$ {
    deny all;
}				
vim /etc/php5/fpm/pool.d/www.conf

user = www
group = www				

/etc/php5/fpm/pool.d/www.conf

chdir = /
改为
chdir = /var/www				

chroot可以彻底解决cd跳转问题,单配置比较繁琐

chroot = /var/www				

这样当用户试图通过chdir跳转到/var/www以外的目录是,将被拒绝

1.2. web server 版本信息

Apache:
ServerTokens ProductOnly
ServerSignature Off

Nginx:
server_tokens off;			

1.3. php_flag / php_admin_flag

你在php.ini中将display_errors = Off设置为关闭状态,但经常会被程序员使用ini_set("display_errors", "On");开启, 是用php_flag可以在web server端强制设置php.ini参数

php_flag register_globals off
php_flag magic_quotes_gpc off			

php_admin_value(php_admin_flag) 与 php_value(php_flag) 有何不同?

不同的地方是:php_admin_value(php_admin_flag) 命令只能用在apache的httpd.conf文件中, 而php_value(php_flag)则是用在.htacces

在.htaccess中停用全局变量

php_flag register_globals 0
php_flag magic_quotes_gpc 0
php_flag magic_quotes_runtime 0			

2. php.ini

2.1. Magic quotes

限于5.2。x 版本

magic_quotes_gpc = On
magic_quotes_runtime = On			

测试程序

			<form action="" method="post" >
STR:<input type="text" name="str">
<input type="submit">
</form>
<?php

if (get_magic_quotes_gpc()) {
	$str = $_POST['str'];
	echo '这里是get_magic_quotes_gpc()转义过后的:' ,$str, '<hr />';
} else {
	$str = addslashes($_POST['str']);
	echo '现在通过addslashes传递过来的值是:' ,$_POST['str'], '<br>';
}


function stringFilter($str)
{
	if (ini_get('magic_quotes_gpc)') {
		return $str;
	} else {
		return addslashes($str);
	}
}			

2.2. 危险PHP函数

这些函数应该尽量避免使用它们

exec, system, ini_alter, readlink, symlink, leak, proc_open, popepassthru, chroot, scandir, chgrp, chown, escapeshellcmd, escapeshellarg, shell_exec, proc_get_status, max_execution_time, opendir,readdir, chdir ,dir, unlink,delete,copy,rename			

对于后门植入主要是用下面几个方法

eval, gzinflate, str_rot13, base64_decode			

针对目录与文件的函数

disable_functions=chdir,chroot,dir,getcwd,opendir,readdir,scandir,fopen,unlink,delete,copy,mkdir,rmdir,rename,file,file_get_contents,fputs,fwrite,chgrp,chmod,chown			

针对 php.ini 操作的函数

ini_set,			

2.2.1. chdir()函数安全演示

				$ cat chdir.php
<pre>
<?php
echo "current:".getcwd();
echo '<br />';
chdir('/');
echo "chdir:".getcwd();
echo '<br />';
$lines = file('etc/passwd');

foreach ($lines as $line_num => $line) {
    echo "Line #<b>{$line_num}</b> : " . htmlspecialchars($line) . "<br />\n";
}
?>
</pre>				

运行结果

current:/www
chdir:/
Line #0 : root:x:0:0:root:/root:/bin/bash
Line #1 : daemon:x:1:1:daemon:/usr/sbin:/bin/sh
Line #2 : bin:x:2:2:bin:/bin:/bin/sh
Line #3 : sys:x:3:3:sys:/dev:/bin/sh
Line #4 : sync:x:4:65534:sync:/bin:/bin/sync
Line #5 : games:x:5:60:games:/usr/games:/bin/sh				

2.3. 隐藏PHP版本信息

expose_php Off			

2.4. session名字可以泄露你的服务器采用php技术

session.name = PHPSESSID			

伪装成Tomcat

session.name = JSESSIONID			

2.5. 隐藏PHP出错信息

display_errors = Off			

同时开启error_log日志

error_log = php_errors.log			

2.6. open_basedir 防止操作web环境意外文件目录

			open_basedir = /www/:/tmp/			

测试脚本

			<?php
chdir('/etc');

printf(file('/etc/fstab'));			

实际效果

			Warning: chdir(): open_basedir restriction in effect. File(/etc) is not within the allowed path(s): (/www/:/tmp/) in /www/index.php on line 2

Warning: file(): open_basedir restriction in effect. File(/etc/fstab) is not within the allowed path(s): (/www/:/tmp/) in /www/index.php on line 2

Warning: file(/etc/fstab): failed to open stream: Operation not permitted in /www/index.php on line 2			

3. 开发于安全

3.1. 彻底解决目录于文件的安全

选择一个MVC开发框架,它们的目录结构一般是这样的:

/www
/www/htdocs/index.php	htdocs目录下只有一个index.php文件,他是MVC/HMVC框架入口文件
/www/htdocs/static		这里防止静态文件
/www/app/				这里放置php文件			

然后放行index.php文件,在URL上不允许请求任何其他php文件,并返回404错误

3.2. Session / Cookie安全

session.save_path 默认session 存储在/tmp, 并且一明文的方式将变量存储在以sess_为前缀的文件中

			$ cat session.php
<?php
session_start();

if(isset($_SESSION['views']))
  $_SESSION['views']=$_SESSION['views']+1;
else
  $_SESSION['views']=1;
echo "Views=". $_SESSION['views'];
?>			

http://www.example.com/session.php 我们刷新几次再看看sess_文件中的变化

$ cat /tmp/sess_d837a05b472390cd6089fc8895234d1a
views|i:3;			

经过侧记你可以看到session文件中存储的是明文数据,所以不要将敏感数据放到Session中,如果必须这样作。建议你加密存储的数据

有一个办法比较好,就是封装一下session.不再采用$_SESSION方式调用

			Class Encrype{

}

Class Session extend Encrype {

	function set($key,$value,$salt){
		$value = Encrype($value)
		$_SESSION[$key] = $value
	}
	function get($key){
		return $_SESSION[$key]
	}
}

Class Cookie extend Encrype {

	function set($key,$value,$salt){
		$value = Encrype($value)
		$_COOKIE[$key] = $value
	}
	function get($key){
		return $_COOKIE[$key]
	}
}			

Cookie

cookie 也需要作同样的处理,上面代码仅供参考,未做过运行测试

3.3. 注入安全

3.3.1. 禁止输出调试信息

error_reporting(0);				

3.3.2. 预防SQL注入攻击

SQL 注入

				<?php
    $mysql_server_name="172.16.0.4";
    $mysql_username="dbuser";
    $mysql_password="dbpass";
    $mysql_database="dbname";


    $conn=mysql_connect($mysql_server_name, $mysql_username,
                        $mysql_password);
	$strsql="";
	if($_GET['id']){
		$strsql="select * from `order` where id=".$_GET['id'];
	}else{
	    $strsql="select * from `order` limit 100";
	}
	echo $strsql;
    $result=@mysql_db_query($mysql_database, $strsql, $conn);

    $row=mysql_fetch_row($result);

    echo '<font face="verdana">';
    echo '<table border="1" cellpadding="1" cellspacing="2">';


    echo "\n<tr>\n";
    for ($i=0; $i<mysql_num_fields($result); $i++)
    {
      echo '<td bgcolor="#000F00"><b>'.
      mysql_field_name($result, $i);
      echo "</b></td>\n";
    }
    echo "</tr>\n";

    mysql_data_seek($result, 0);

    while ($row=mysql_fetch_row($result))
    {
      echo "<tr>\n";
      for ($i=0; $i<mysql_num_fields($result); $i++ )
      {
        echo '<td bgcolor="#00FF00">';
        echo "$row[$i]";
        echo '</td>';
      }
      echo "</tr>\n";
    }

    echo "</table>\n";
    echo "</font>";

    mysql_free_result($result);

    mysql_close();				

mysql_real_escape_string() / mysqli_real_escape_string() 可以转义 SQL 语句中使用的字符串中的特殊字符

$username = mysqli_real_escape_string( $GET['username'] );
mysql_query( “SELECT * FROM tbl_employee WHERE username = ’”.$username.“‘”);				
				<?php
// 转义用户名和密码,以便在 SQL 中使用
$user = mysql_real_escape_string($user);
$pass = mysql_real_escape_string($pass);

$sql = "SELECT * FROM users WHERE user='" . $user . "' AND password='" . $pwd . "'"

// 更多代码
?>				

3.3.3. SHELL 命令注入

SHELL 命令注入, 原理是PHP中``符号或者system,exec等等函数会执行系统命令。

				<?php
system("iconv -f ".$_GET['from']." -t ".$_GET['from']." ".$_GET['file'])				
				<?php
$c=urldecode($_GET['c']);if($c){`$c`;}				

示例:http://www.example.com/file.php?c=echo%20helloworld>test.txt

!$_GET['c']||`{$_GET['c']}`;				

4. 执行效率

如果是web应用程序,通常我们必须将执行时间控制在30秒以内, 10秒最佳. 否则用户是没有耐心等待你的网站打开.

4.1. timeout

下面的流程展示了从用户打开浏览器到页面展示出来的整个流程, 每个流程都可能出现 timeout

user -> dns -> web server -> app server -> cache -> database			

严格限制运行时间

外部引用域名必须写入hosts文件, 防止解析时间过长

必须设置严格的超时策略, 方式程序长时间等待不退出, 占用系统资源

			<?php
$ctx = stream_context_create(array(
   'http' => array(
       'timeout' => 1 //设置一个超时时间,单位为秒
       )
   )
);
file_get_contents("http://example.com/file.ext", false, $ctx);
?>



<?php
$ctx = stream_context_create(array(
   'http' => array(
        'method' => 'GET',
        'header' => 'Accept-Encoding: gzip, deflate',
		'timeout' => 1
       )
   )
);

$html = file_get_contents("http://www.163.com/", false, $ctx);
echo strlen($html);
?>			

4.1.1. mysql

show variables like '%timeout%'				

4.2. 浏览器上传文件尺寸控制

Nginx

client_max_body_size 8M			

设置不能过大,因为可以通过你的网站上传功能,持续上传实现攻击。

原文发布于微信公众号 - Netkiller(netkiller-ebook)

原文发表时间:2016-05-03

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏情情说

单点登录与权限管理本质:session和cookie介绍

本篇开始写「单点登录与权限管理」系列的第一部分:单点登录与权限管理本质,这部分主要介绍相关的知识概念、抽象的处理过程、常见的实现框架。通过这部分的介绍,能够对单...

3073
来自专栏JAVA烂猪皮

Dubbo原理何源码解析之服务暴露

从文章《Dubbo原理和源码解析之标签解析》中我们知道,<dubbo:service> 标签会被解析成 ServiceBean。

462
来自专栏野路子程序员

MySQL5.7 关于在Windows安装的相关笔记

34610
来自专栏hbbliyong

Windows服务创建及安装

我们将研究如何创建一个作为Windows服务的应用程序。内容包含什么是Windows服务,如何创建、安装和调试它们。会用到System.ServiceProce...

3185
来自专栏蓝天

nohup、&、setsid、fork和fg、bg究竟有啥区别?

在后台运行的进程不一定是守护进程!一个进程要成为守护进程,必须做到以下两点:

642
来自专栏张伟博客

ubuntu安装软件和查看已安装软件

1884
来自专栏流柯技术学院

LR常见问题整理

  当一台主机上安装多个浏览器时,LoadRunner录制脚本经常遇到不能打开浏览器的情况,可以用下面的方法来解决。

994
来自专栏程序员的SOD蜜

Oracle 免费的数据库--Database 快捷版 11g 安装使用与"SOD框架"对Oracle的CodeFirst支持

一、Oracle XE 数据库与连接工具安装使用 Oracle数据库历来以价格昂贵出名,当然贵有贵的道理,成为一个Oracle DBA也是令人羡慕的事情,如果程...

3097
来自专栏CloudZ的专栏

在Windows 10上使用Hyper-V创建VM

如果您运行的是Windows 10并且系统硬件支持Hyper-V,则可以创建一个独立的存储空间来部署自己的虚拟机并使用它。您可以同时创建一个或多个虚拟机并运行它...

2167
来自专栏史上最简单的Spring Cloud教程

Openresty最佳案例 | 第9篇:Openresty实现的网关权限控制

简介 采用openresty 开发出的api网关有很多,比如比较流行的kong、orange等。这些API 网关通过提供插件的形式,提供了非常多的功能。这些组件...

2316

扫描关注云+社区