0x01 概述
4月25日,Drupal官方发布通告,Drupal Core 存在一个远程代码执行漏洞,影响 7.x 和 8.x 版本。据分析,这个漏洞是CVE-2018-7600
的绕过利用,两个漏洞原理是一样的,通告还称,已经发现了这个漏洞和CVE-2018-7600
的在野利用,详情请看 https://www.drupal.org/sa-core-2018-004
0x02 影响版本
Drupal 6.x,7.x,8.x
修复版本 Drupal 7.59,Drupal 8.4.8,Drupal 8.5.3
0x03 环境搭建
历史版本 https://www.drupal.org/project/drupal/releases
0x04 漏洞分析
分析还是以7.57版本为例。跟7600漏洞的7.x版本很相似,只不过入口不一样,可以参考http://blog.nsfocus.net/cve-2018-7600-drupal-7-x/
上回漏洞的关键点是让系统缓存一个form_build_id
,这个form存着我们传入的恶意参数,第二个请求从中取出来然后执行。
这次的原理还是一样,触发漏洞还是需要发两个post包,一个存入form_build_id
一个取出后执行。
这次的问题出在删除文章的时候,因此需要文章删除权限,我们先走一遍正常删除文章的逻辑
请求中每个node即代表一篇文章。
可以看到是会重定向到文章页面的,根据上个漏洞的分析我们猜测,一定还是走到了drupal_redirect_form()
,我们已经知道如果走到drupal_redirect_form()
分支,是不会往数据库缓存form_build_id
的,我们的目的还是让程序不满足一定条件从而不进行表单提交后重定向,所以还是跟着CVE-2018-7600
的套路来走
从代码层面看一下
之前的流程还是一样,直接跳到drupal_build_form()
方法第386行
drupal_process_form($form_id, $form, $form_state);
跟入drupal_process_form()
还是一样,$form_state['submitted']
被设置为true
回到902行
if ($form_state['submitted'] && !form_get_errors() && !$form_state['rebuild'])
条件被满足,进入这个分支便会执行drupal_redirect_form()
而在这一步之前需要经过的判断是_form_element_triggered_scripted_submission()
所以回到一开始的问题,构造一个_triggering_element_value
使得键值对相等,从而不进行rebuild
我们传入_triggering_element_name=form_id
可以看到条件被满足,$form_state['submitted']
没有被设置为true,还是保持默认值false
进入drupal_rebuild_form()
表单被缓存
然后我们发送第二个post包来取出我们构造好的form,向file/ajax/actions/cancel/%23options/path
发起请求
参数传递进去
最终还是跟入到
$output = drupal_render($form);
根据前几次的经验,我们还是选择'#post_render'
参数,
假如我们能控制这个参数,在drupal_render()
方法里就会把这个参数作为$function
函数名,而传给它的参数则是[%23markup]
所以问题回到了一开始,我们需要传递什么样的恶意参数,可以让系统直接接收而不经过过滤,还是之前的套路,搜索module下删除文章的相关操作
可以看到node_form_delete_submit()
方法从get方法直接接收参数destination
,与最初分析正常删除文章的参数正是同一个,那么我们就可以利用destination
传进恶意参数
构造如下
destination=a?q[%2523post_render][]=passthru%26q[%2523type]=markup%26q[%2523markup]=dir
注意此处需要转义百分号,对#
进行二次编码,以绕过CVE-2018-7600
的补丁,不然在取值时会被认为q[
是一个值
原因:
includes/common.inc
的drupal_parse_url()
方法对url进行了解析,而在url传入到Drupal内部的时候已经经过一层解码,也就是说
destination=a?q[%2523post_render][]=passthru%26q[%2523type]=markup%26q[%2523markup]=dir
在进入Drupal时已经被解码成
destination=a?q[%23post_render][]=passthru%26q[%23type]=markup%26q[%23markup]=dir
然后经过parse_url()
方法对url结构进行解析
a
参数是次要的,主要是q
参数,因为在drupal_parse_url()
下半部分从q取出值赋给$options['path']
,也就是a被覆盖了,这个时候的$options['path']
就是我们传入的数组
参数缓存进整个form后通过第二个请求取出,同样经过
foreach ($form_parents as $parent) {
$form = $form[$parent];
}
遍历叶子节点取出参数
进入drupal_render()
执行
0x05 PoC
略
0x06 补丁
7.x的补丁
https://github.com/drupal/drupal/commit/080daa38f265ea28444c540832509a48861587d0
其中一个重要操作就是对destination
参数进行了净化
0x07 总结
总的来说这个漏洞是CVE-2018-7600的另一个利用点,只是入口方式不一样,最终执行点还是相同的,所以还是那句话,一旦参数可控并且没有经过正确的过滤,就很有可能出问题。