隨着代碼安全的普及,愈來愈多的開發人員知道了如何防護sqli、xss等與語言無關的漏洞,可是對於和開發語言自己相關的一些漏洞和缺陷卻知之甚少,因而這些點也就是咱們在Code audit的時候的重點關注點。本文旨在總結一些在PHP代碼中常常形成問題的點,也是咱們在審計的時候的關注重點。(PS:本文也只是簡單的列出問題,至於形成問題的底層緣由未作詳細解釋,有興趣的看官能夠自行GOOGLE或者看看底層C代碼。知其然,且知其因此然)php
本文如有寫錯的地方,還請各位大佬斧正 :)html
TODO: 繼續豐富並增長各個點的實際漏洞事例linux
例如以下代碼sql
<?php $filename = __DIR__ . '/tmp/' . $user['name']; $data = $user['info']; file_put_contents($filename, $data); if (file_exists($filename)) { unlink($filename); } ?>
這裏引用小密圈中P牛的解讀windows
查看php源碼,其實咱們能發現,php讀取、寫入文件,都會調用php_stream_open_wrapper_ex來打開流,而判斷文件存在、重命名、刪除文件等操做則無需打開文件流。數組
咱們跟一跟php_stream_open_wrapper_ex就會發現,其實最後會使用tsrm_realpath函數來將filename給標準化成一個絕對路徑。而文件刪除等操做則不會,這就是兩者的區別。安全
因此,若是咱們傳入的是文件名中包含一個不存在的路徑,寫入的時候由於會處理掉「../」等相對路徑,因此不會出錯;判斷、刪除的時候由於不會處理,因此就會出現「No such file or directory」的錯誤。app
因而乎linux能夠經過xxxxx/../test.php
、test.php/.
windows能夠經過test.php:test
test.ph<
來繞過文件刪除curl
此外發現還可使用僞協議php://filter/resource=1.php
在file_ge_contents、copy等中讀取文件內容,卻能夠繞過文件刪除xss
extract函數從數組導入變量(如\$_GET、 \$_POST),將數組的鍵名做爲變量的值。而parse_str函數則是從相似name=Bill&age=60的格式字符串解析變量.若是在使用第一個函數沒有設置EXTR_SKIP
或者EXTR_PREFIX_SAME
等處理變量衝突的參數時、第二個函數沒有使用數組接受變量時將會致使變量覆蓋的問題
所以,在32位系統上 intval('1000000000000') 會返回 2147483647
當小數小於10^-16後,PHP對於小數就大小不分了
var_dump(1.000000000000000 == 1) >> TRUE
var_dump(1.0000000000000001 == 1) >> TRUE
而'.'能夠出如今任意位置,E、e能出如今參數中間,仍能夠被判斷爲數字。也就是說is_numeric("\r\n\t 0.1e2") >> TRUE
int strcmp ( string $ str1 , string \$str2 )
參數 str1第一個字符串。str2第二個字符串。若是 str1 小於 str2 返回 < 0;
若是 str1 大於 str2 返回 > 0;若是二者相等,返回 0。
可是若是傳入的兩個變量是數組的話,函數會報錯返回NULL,若是隻是用strcmp()==0來判斷的話就能夠繞過
sha1() MD5()函數默認接收的參數是字符串類型,可是若是若是傳入的參數是數組的話,函數就會報錯返回NULL。相似sha1(\$_GET['name']) === sha1(\$_GET['password’])的比較就能夠繞過
這方面問題普及的不少,不做過多的解釋
md5('240610708'); // 0e462097431906509019562988736854
md5('QNKCDZO'); // 0e830400451993494058024219903391
md5('240610708') == md5('QNKCDZO')
md5('aabg7XSs') == md5('aabC9RqS')
sha1('aaroZmOk') == sha1('aaK1STfY')
sha1('aaO8zKZF') == sha1('aa3OFF9m')
'0010e2' == '1e3'
'0x1234Ab' == '1193131'
'0xABCdef' == ' 0xABCdef’
當轉換爲boolean時,如下只被認爲是FALSE:FALSE、0、0.0、「」、「0」、array()、NULL
PHP 7 之前的版本里,若是向八進制數傳遞了一個非法數字(即 8 或 9),則後面其他數字會被忽略。var_dump(0123)=var_dump(01239)=83
PHP 7 之後,會產生 Parse Error。
字符串轉換爲數值時,若字符串開頭有數字,則轉爲數字並省略後面的非數字字符。若一開頭沒有數字則轉換爲0
\$foo = 1 + "bob-1.3e3"; // $foo is integer (1)
\$foo = 1 + "bob3"; // $foo is integer (1)
\$foo = 1 + "10 Small Pigs"; // $foo is integer (11)
'' == 0 == false
'123' == 123
'abc' == 0
'123a' == 123
'0x01' == 1
'0e123456789' == ‘0e987654321'
[false] == [0] == [NULL] == [‘']
NULL == false == 0» true == 1
eregi()默認接收字符串參數,若是傳入數組,函數會報錯並返回NULL。同時還能夠%00 截斷進行繞過
parse_str("na.me=admin&pass wd=123",$test); var_dump($test); array(2) { ["na_me"]=> string(5) "admin" ["pass_wd"]=> string(3) "123" }
in_arrary(「1asd」,arrart(1,2,3,4)) => true in_arrary(「1asd」,arrart(1,2,3,4),TRUE) => false \\(須要設置strict參數爲true纔會進行嚴格比較,進行類型檢測)
printf()和sprintf()函數中能夠經過使用%接一個字符來進行padding功能
例如%10s 字符串會默認在左側填充空格至長度爲10,還能夠 %010s 會使用字符0進行填充,可是若是咱們想要使用別的字符進行填充,須要使用 ‘ 單引號進行標識,例如 %’#10s 這個就是使用#進行填充(百分號不只會吃掉’單引號,還會吃掉\ 斜槓)
同時sprintf()可使用指定參數位置的寫法
](http://or48znikk.bkt.clouddn.com/dmsj/dmsj17.png)
%後面的數字表明第幾個參數,$後表明格式化類型
因而當咱們輸入的特殊字符被放到引號中進行轉義時,可是又使用了sprintf函數進行拼接時
例如%1$’%s’ 中的 ‘%被當成使用%進行padding,致使後一個’逃逸了
還有一種狀況就是’被轉義成了\’,例如輸入%’ and 1=1#進入,存在SQL過濾,’被轉成了\’
因而sql語句變成了 select * from user where username = '%\' and 1=1#’;
若是這個語句被使用sprintf函數進行了拼接,%後的\被吃掉了,致使了’逃逸
<?php $sql = "select * from user where username = '%\' and 1=1#';"; $args = "admin"; echo sprintf( $sql, $args ) ; //result: select * from user where username = '' and 1=1#' ?>
不過這樣容易遇到 PHP Warning: sprintf(): Too few arguments
的報錯
這個時候咱們可使用%1$來吃掉轉移添加的\
<?php $sql = "select * from user where username = '%1$\' and 1=1#' and password='%s';"; $args = "admin"; echo sprintf( $sql, $args) ; //result: select * from user where username = '' and 1=1#' and password='admin'; ?>
例如is_numeric($a) and is_numeric($b)
程序本意是要a、b都爲數字纔會繼續,可是當$a爲數字and即返回TRUE,即 true and false >> true
http://user@eval.com:80@baidu.com
進行解析時,PHP獲取的host是baidu.com是容許訪問的域名,而最後調用libcurl進行請求時則是請求的eval.com域名,能夠形成ssrf繞過https://evil@baidu.com
這樣的域名進行解析時,php獲取的host是evil@baidu.com
,可是libcurl獲取的host倒是evil.comfilter_var()函數對於http://evil.com;google.com
會返回false也就是認爲url格式錯誤,可是對於0://evil.com:80;google.com:80/
、0://evil.com:80,google.com:80/
、0://evil.com:80\google.com:80/
卻返回true。
例如以下代碼
if(filter_var($argv[1], FILTER_VALIDATE_URL)) { // parse URL $r = parse_url($argv[1]); print_r($r); // check if host ends with google.com if(preg_match('/baidu\.com$/', $r['host'])) { // get page from URL $a = file_get_contents($argv[1]); echo($a); } else { echo "Error: Host not allowed"; } } else { echo "Error: Invalid URL"; }
雖然經過filter_var函數對url的格式進行檢查,而且使用正則對url的host進行限定
可是能夠經過data://baidu.com/plain;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pgo=
頁面會將<script>alert(1)</script>
返回給客戶端,就有可能形成xss