知乎專欄 | 多維度架構 | 微信號 netkiller-ebook | QQ群:128659835 請註明“讀者” |
目錄權限安全
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
現在我們開始講解安全配置問題
我們目的是避免用戶通過漏洞提升權限,或者由於權限配置不當產生漏洞
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>
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以外的目錄是,將被拒絶
Apache: ServerTokens ProductOnly ServerSignature Off Nginx: server_tokens off;
你在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
限于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); } }
這些函數應該儘量避免使用它們
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,
$ 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
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
選擇一個MVC開發框架,它們的目錄結構一般是這樣的:
/www /www/htdocs/index.php htdocs目錄下只有一個index.php檔案,他是MVC/HMVC框架入口檔案 /www/htdocs/static 這裡防止靜態檔案 /www/app/ 這裡放置php檔案
然後放行index.php檔案,在URL上不允許請求任何其他php檔案,並返回404錯誤
<Location ~ "/((static/)|(css/)|(images/)).*\.php"> Order Deny,Allow Deny from all </Location>
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 也需要作同樣的處理,上面代碼僅供參考,未做過運行測試 |
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 . "'" // 更多代碼 ?>
如果是web應用程序,通常我們必須將執行時間控制在30秒以內, 10秒最佳. 否則用戶是沒有耐心等待你的網站打開.
下面的流程展示了從用戶打開瀏覽器到頁面展示出來的整個流程, 每個流程都可能出現 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); ?>