700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > drupal 7 ajax 【漏洞分析】CVE--7600 Drupal 7.x 版本代码执行

drupal 7 ajax 【漏洞分析】CVE--7600 Drupal 7.x 版本代码执行

时间:2021-05-30 15:20:18

相关推荐

drupal 7 ajax 【漏洞分析】CVE--7600 Drupal 7.x 版本代码执行

阅读:

8,003

CVE--7600影响范围包括了Drupal 6.x,7.x,8.x版本,前几天8.x版本的PoC出来之后大家都赶紧分析了一波,然后热度似乎慢慢退去了。两天前Drupalgeddon2项目更新了7.x版本的exp,实际环境也出现了利用,下面就简单来看一下

0x01 概述

CVE--7600影响范围包括了Drupal 6.x,7.x,8.x版本,前几天8.x版本的PoC出来之后大家都赶紧分析了一波,然后热度似乎慢慢退去了。两天前Drupalgeddon2项目更新了7.x版本的exp,实际环境也出现了利用,下面就简单来看一下

看到项目上这样写

Drupal < 7.58 ~ user/password URL, attacking triggering_element_name form & #post_render parameter, using PHP’s passthru function

提示了问题出在user/password路径下,通过#post_render传递恶意参数,问题出现在triggering_element_name表单处理下

0x02 漏洞分析

我们从三个问题入手,为什么PoC发了两个包,第二次请求为什么要带上一个form_build_id,以及为什么选择user/password这个入口

先分析第一个post,照例还是先看一下Drupal 7的表单处理流程,跟8版本不太一样,但是入口还是相似的。

根据文档描述,当我们提交一个表单(例如找回密码)时,系统会通过form_builder()方法创建一个form

一系列预处理后,会由drupal_build_form

()方法创建一个表单,在第386行调用drupal_process_form()方法,

跟进drupal_process_form()方法,这时候默认的$form_state['submitted']为false

不满足if条件,$form_state['submitted']被设置为true

于是进入这个分支,最终被drupal_redirect_form重定向

我们的目的是要让系统缓存一个form_build_id,以便后面拿出来用。要想form被缓存,就得想办法让if ($form_state['submitted'] && !form_get_errors() && !$form_state['rebuild'])不成立,也就是说要使$form_state['submitted']为false

从而进入下面的drupal_rebuild_form

那么如何让$form_state['submitted']为false呢?

在includes/form.inc第886行

$form = form_builder($form_id, $form, $form_state);

跟进form_builder方法,第1987行

当$form_state['triggering_element']['#executes_submit_callback']存在值的时候就为true,那么我们就想办法让这个值为空

往上看第1972行

如果没有设置$form_state['triggering_element'],那么$form_state['triggering_element']就设置为第一个button的值,所以正常传递表单的时候$form_state['triggering_element']['#executes_submit_callback']就总会有值

现在问题来了,如何构造一个form能够确保$form_state['triggering_element']['#executes_submit_callback']为空或者说不存在这个数组呢?

我们注意到第1864行

_form_builder_handle_input_element()方法对表单先进行了处理,跟进去看一下

第2144行

这里$form_state['triggering_element']被设置为$element,前提是满足_form_element_triggered_scripted_submission()方法,继续跟入

第2180行

这个方法的意思是说如果_triggering_element_value和$element的键值都相等的话,返回true

$form_state['triggering_element']赋值为$element,其中不含['#executes_submit_callback'],一开始的条件就成立了

根据PoC,我们传入_triggering_element_name=name

看到进入这个分支,进入form_set_cache()方法

数据库中插入缓存form_build_id

成功写入缓存

接下去来看一下这个缓存有什么用

分析PoC的第二个包,请求参数是这样q=file/ajax/name/%23value/form_build_id

form_build_id即我们上一个写入数据库的缓存表单

首先请求会进入includes/menu.inc的menu_get_item()方法,

$path即我们传进去的q参数,经过一系列处理传给menu_get_ancestors()方法,该方法把path重新组合成一堆router,也就是Drupal处理路由到具体url的传参方式,最终被db_query_range()带入数据库查询

我们关注查询结果$router_item的page_callback值,因为这个值最终会作为参数被带入call_user_func_array()

到这里就跟8版本的情况有点类似了

跟入回调函数file_ajax_upload()

还是一样,把$form_parents完整取出赋值给$form,加上一些前缀后缀后最终进入drupal_render()方法

最终得到执行

到目前为止我们分析清楚了为什么PoC要发两次包,以及第二次请求为什么要带上一个form_build_id,现在来想一想为什么要请求user/password这个路径呢?

在user这个module下的user_pass()方法

看到这里是不是感觉跟8版本很相似,#default_value从get的name参数里取值,而name可以作为数组传入,它的属性在下面正好可以被利用,一个巧妙的利用链就串起来了。

0x03 总结

Drupal 7.x的利用比8.x要复杂一些,但触发点和一开始的风险因素还是类似的,一是接收参数过滤不当,而是可控参数进入危险方法。官方补丁把入口处的#全给过滤了,简单粗暴又有效,估计再利用框架本身的特性想传递进一些数组或元素就很难了。

0x04 参考

/dreadlocked/Drupalgeddon2

/uncovering-drupalgeddon-2/

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。