作者:Ricter Z
作者博客:https://ricterz.me/posts/Drupal%207%20-%20CVE-2018-7600%20PoC%20Writeup
0x00 前言
前幾天我分析了 Drupal 8.5.0 的 PoC 構造方法,但是 Drupal 7 還是仍未構造出 PoC。今天看到了 Drupalgeddon2 支持了 Drupal 7 的 Exploit,稍微分析了下,發現 PoC 構建的十分精妙,用到了諸多 Drupal 本身特性,我構造不出果然還是太菜。
首先,Drupal 7 和 Drupal 8 這兩個 PoC 本質上是同一原因觸發的,我說的同一個原因並不是像是 #pre_render
的 callback 這樣,而是都是由於 form_parent
導致 Drupal 遍歷到用戶控制的 #value
,接着進行 render 的時候導致 RCE。Drupal 8 中的 element_parents
十分明顯,且從 $_GET
中直接獲取,所以很容易的能分析出來,而 Drupal 7 中的 form_parent
就藏得比較隱晦了。
那麼,這個 PoC 用到了 Drupal 中的哪些特性呢?
-
Drupal 的 router 傳參
-
Drupal 的 form cache
那麼,先從 router 講起。
0x01 Router
當訪問 file/ajax/name/#default_value/form-xxxx
的時候,在 menu.inc
中,Drupal 是這樣處理的:
function menu_get_item($path = NULL, $router_item = NULL) {
$router_items = &drupal_static(__FUNCTION__);
if (!isset($path)) {
$path = $_GET['q'];
}
var_dump($router_items);
if (isset($router_item)) {
$router_items[$path] = $router_item;
}
if (!isset($router_items[$path])) {
// Rebuild if we know it's needed, or if the menu masks are missing which
// occurs rarely, likely due to a race condition of multiple rebuilds.
if (variable_get('menu_rebuild_needed', FALSE) || !variable_get('menu_masks', array())) {
if (_menu_check_rebuild()) {
menu_rebuild();
}
}
$original_map = arg(NULL, $path);
$parts = array_slice($original_map, 0, MENU_MAX_PARTS);
$ancestors = menu_get_ancestors($parts);
$router_item = db_query_range('SELECT * FROM {menu_router} WHERE path IN (:ancestors) ORDER BY fit DESC', 0, 1, array(':ancestors' => $ancestors))->fetchAssoc();
if ($router_item) {
// Allow modules to alter the router item before it is translated and
// checked for access.
drupal_alter('menu_get_item', $router_item, $path, $original_map);
$map = _menu_translate($router_item, $original_map);
$router_item['original_map'] = $original_map;
if ($map === FALSE) {
$router_items[$path] = FALSE;
return FALSE;
}
看不動?沒關係,我來解釋下:
- 從
$_GET["q"]
取出 path; - 將 path 分割後進行組合,得到一個數組;
- 數組進入數據庫查詢;
組合的結果大概是這樣:
0 = file/ajax/name/#default_value/form-xxxx
1 = file/ajax/name/#default_value/%
2 = file/ajax/name/%/form-xxxxx
3 = file/ajax/name/%/%
4 = file/ajax/%/%/%
5 = file/%/name/%/form-xxxxx
....
12 = file/%/name
13 = file/ajax
14 = file/%
15 = file
這些是什麼呢?實際上這些是 Drupal 的 router,在數據庫的 menu_router 表裡。這麼一串 array 最終和數據庫中的 file/ajax
相匹配。Drupal 會根據數據庫中的 page_callback
進行回調,也就是回調到 file_ajax_upload
函數。回調的現場: