PHP セキュリティ
http://netkiller.github.com/article/phpsecurity.html
<openunix@163.com>
著作権 ? 2011、2012 http://netkiller.github.com
概要
?
以下は、長年にわたって蓄積された私の経験の要約であり、参考のために文書にまとめられています:
?
Netkiller Architect 手札 | Netkiller Linux 手札 | Netkiller Developer 手札 | Netkiller Database 手札 |
Netkiller Debian 手札 | Netkiller CentOS 手札 | Netkiller FreeBSD 手札 | Netkiller Shell 手札 |
Netkiller Web 手札 | Netkiller Monitoring 手札 | Netkiller Storage 手札 | Netkiller Mail 手札 |
Netkiller Security 手札 | Netkiller Multimedia 手札 | Netkiller Writer 手札 | Netkiller Version 手札 |
Netkiller PostgreSQL 手札 | Netkiller MySQL 手札 | Netkiller Cryptography 手札 | Netkiller Cisco IOS 手札 |
Netkiller LDAP 手札 | Netkiller Intranet 手札 | ? | ? |
?
目次
ディレクトリ権限のセキュリティ
Web サーバーの起動ユーザーを実行ユーザーと同じユーザーにすることはできません
Web サーバーを実行しているユーザーと 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 サーバーを起動します。このとき、Web サーバーの親プロセスは 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 に似ており、両方ともルートの親プロセスであり、その後、子プロセスを派生します。fastcgi は 9000 を使用するため、root を使用せずに php-fpm を起動できます。
ここからはセキュリティ構成の問題について説明していきます
私たちの目的は、ユーザーが脆弱性を利用して権限を昇格したり、不適切な権限設定により脆弱性を作成したりすることを防ぐことです
Apache のケース
Apache : ルート
Apache 子プロセス: 誰も
HTDOCS ディレクトリ: /var/www
/var/www |--include |--image |--temp |--...
多くの人は、/var/www ユーザーとグループを none:nogroup / nothing:nobody に設定します。同時に、画像はファイルをアップロードするため、777 に設定する必要があります。多くの本のチュートリアルにもこれが記載されています。 . この構成の問題点は何ですか?それを分析してみましょう:
ユーザーがファイルを画像ディレクトリにアップロードすると想定します。以下のような状況が考えられます。
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
<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
/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以外的目录是,将被拒绝
Apache: ServerTokens ProductOnly ServerSignature Off Nginx: server_tokens off;
这些函数应该尽量避免使用它们
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
$ cat chdir.php <pre class="brush:php;toolbar:false"> <?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"; } ?>
运行结果
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
?
expose_php Off
display_errors = Off
error_log = php_errors.log
选择一个MVC开发框架,它们的目录结构一般是这样的:
/www /www/htdocs/index.php htdocs目录下只有一个index.php文件,他是MVC/HMVC框架入口文件 /www/htdocs/static 这里防止静态文件 /www/app/ 这里放置php文件
然后放行index.php文件,在URL上不允许请求任何其他php文件,并返回404错误
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 也需要作同样的处理,上面代码仅供参考,未做过运行测试
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();
SHELL 命令注入
<?php system("iconv -f ".$_GET['from']." -t ".$_GET['from']." ".$_GET['file'])