Loading... # [PHP 安全与性能](https://www.netkiller.cn/journal/security.php.html) 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 父进程 root 启动 web server, 此时web server 父进程应该是 root,同时父进程监听80端口 子进程 父进程派生许多子进程,同时使用setuid,setgid将子进程权限切换为非root 子进程用户可以通过httpd.conf设置 User nobody Group nobody nginx.conf $ cat /etc/nginx/nginx.conf user www-data; 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 案例 Apache : root Apache 子进程 : nobody HTDOCS 目录 : /var/www /var/www |--include |--image |--temp |--... 很多人会将/var/www用户与组设置为 nobody:nogroup / nobody:nobody, 同时因为images会上传文件需要设置777, 很多书本于教程上面也是这样讲的, 这样配置会有什么问题呢?我们来分析一下: 我们假设,一个用户上传一个文件到images目录,会有几种情况: 上传一个.php文件,我们可以通过程序禁止上传.php文件 我们上传一个.jpg文件,OK 通过了,通过某种手段将他重命名位.php扩展名的文件,然后通过http://www.example.com/images/your.php 运行它,your.php 可以做什么呢? 它可以查看所有文件,修改所有文件,创建其他php文件,去你可include目录下看config.php然后下载数据库。 内部开发人员偷偷将一个程序植入到系统中,这个做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 案例分析 nginx / lighttpd : root web server 子进程 : nobody php-fpm : root php-fpm 子进程 : nobody HTDOCS 目录 : /var/www /var/www |--include |--image |--temp |--... fastcgi 遇到的问题与上面apache案例中遇到的问题类似,不同是的fastcgi把动态于静态完全分开了,这样更容易管理,我们可以这样入手 nginx / lighttpd : root web server 子进程 : nobody php-fpm : root 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 1.4. 防止URL注入 if ($request_uri ~* (.*)(insert|select|delete|update|count|concat|cost|union|drop|table|*|%|master|truncate|declare|'|;|and|or|(|)|exec)(.*)$ ) { return 403; } if ( $query_string ~* ".*[;'<>].*" ){ return 403; } 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 设置不能过大,因为可以通过你的网站上传功能,持续上传实现攻击。 最后修改:2023 年 08 月 07 日 © 允许规范转载 赞 如果觉得我的文章对你有用,请随意赞赏