近期,著名的Drupal CMS网站爆出7个漏洞,其中1个严重漏洞CVE-2017-6926,具有发表评论权限的用户可以查看他们无权访问的内容和评论,并且还可以为该内容添加评论。绿盟科技于上周发布了《 Drupal下周将发布重要安全补丁威胁预警通告 》。
本篇文章对Drupal 8 – CVE-2017-6926漏洞进行了详细分析。
CVE-2017-6926 漏洞详情
先看下drupal官网的通告:
https://www.drupal.org/sa-core-2018-001
有发布评论权限的用户,可以查看他们无权访问的内容和评论。
并且还可以为此内容添加评论。
想要触发这个漏洞,必须启用评论系统,并且攻击者必须有权发布评论。
从漏洞描述来看,问题可能出在评论的权限控制上了。
但是这里有一个坑,笔者当时就掉进去了:这里的权限,指的是文章的权限?还是评论的权限呢?是攻击者可以访问他们不公开的评论呢?还是攻击者可以访问不公开的文章的公开评论呢?下面我会详细的分析这个漏洞,并给出上面问题的答案。
漏洞是在8.4.5上解决的,看一下8.4.5修改的内容:
这里在CommentController.php(评论控制器)里加上了一个对entity实体是否有view权限的判断。
这个好理解,之前没有对entity的权限进行判断,导致不可view的entity也通过了权限检查,从而导致了越权。
我们看下关于entity的介绍:
我们再看下漏洞存在的函数
\core\modules\comment\src\Controller\CommentController.php
public function replyFormAccess(EntityInterface $entity, $field_name, $pid = NULL) { // Check if entity and field exists. $fields = $this->commentManager->getFields($entity->getEntityTypeId()); if (empty($fields[$field_name])) { throw new NotFoundHttpException(); } $account = $this->currentUser(); // Check if the user has the proper permissions. $access = AccessResult::allowedIfHasPermission($account, 'post comments'); $status = $entity->{$field_name}->status; $access = $access->andIf(AccessResult::allowedIf($status == CommentItemInterface::OPEN) ->addCacheableDependency($entity));
再来看下这个方法的路由
\core\modules\comment\comment.routing.yml
comment.reply: path: '/comment/reply/{entity_type}/{entity}/{field_name}/{pid}' defaults: _controller: '\Drupal\comment\Controller\CommentController::getReplyForm' _title: 'Add new comment' pid: ~ requirements: _custom_access: '\Drupal\comment\Controller\CommentController::replyFormAccess' options: parameters: entity: type: entity:{entity_type}
可见replyFormAccess其实是getReplyForm方法的权限检查模块,传入replyFormAccess方法的参数将会是{entity_type}/{entity}/{field_name}/{pid}
我们实际测一下,访问这个模块,看看发送的参数是什么样子的:
对kingsguard test评论进行评论:
注意看url:http://127.0.0.1/d/drupal4/comment/reply/node/1/comment/1
{entity_type}:node
{entity}:1
{field_name}:comment
{pid}:1
现在可以明确了,传入replyFormAccess里的entity类型是node节点类型,接着下断看下entity的数据
在大概知道$entity是什么之后,我们再来看下补丁代码:
可见补丁加了一个判断
andIf(AccessResult::allowedIf($entity->access('view')));
看下allowedIf方法是怎么实现的:
public static function allowedIf($condition) { return $condition ? static::allowed() : static::neutral(); }
可见allowedIf通过传入的参数的True/False来判断是否有权限进行操作。
这样看来,$entity->access(‘view’)只有True/False两种可能出现的值。
我们在后台下断,并且构造一个$test方法值等于$entity->access(‘view’),下断看看$test何时能为False:
首先来找找,关于回复评论处的权限设置:
我们在admin账号下,发表一片名为kingsguard的文章,此文章有一个kingsguard test的评论:
我们将kingsguard test 这条评论的权限编辑成不公开
我们用admin账号回复一下这个评论,后台看下$$test = $entity->access(‘view’);的值:
毫无疑问,$test的值是true
现在用另一个账号登录,也访问
http://127.0.0.1/d/drupal4/comment/reply/node/2/comment/6这个连接试试:
$test仍然为true。
当时笔者就是掉到这个坑里了,明明这条评论设置为不公开,为什么不同用户访问,$entity->access(‘view’)都是true呢?
后来证明了,其实这里的$entity,指的不是评论,而是这篇文章,$entity->access(‘view’)也同样不是单条评论的是否有view权限,而是这篇文章是否有view权限。
其实观察url就可以发现这个问题了:
http://127.0.0.1/d/drupal4/comment/reply/node/1/comment/6(回复第一篇文章的第六个评论)
http://127.0.0.1/d/drupal4/comment/reply/node/2/comment/6(回复第二篇文章的第六个评论)
显然这里的1和2,指的是文章的编号,同时也是entity的编号,那显然entity指的是文章而不是评论。
我们用admin账号编辑kingsguard这篇文章,把published选项的勾去掉:
这时候用admin账号回复kingsguard test这条评论:
对于admin账号来说,$entity->access(‘view’);仍为true
再用其他账号登录:
因为admin账号发布的文章都被admin账号设置为不公开了,所以这里看不到任何文章。
继续用这个账号访问:
http://127.0.0.1/d/drupal4/comment/reply/node/2/comment/6
可以看到,虽然文章不公开,仍然可以看到不公开文章的评论,并且还能对评论进行回复。
再看下后台下的断点:
此时的$entity->access(‘view’)变成了false
CVE-2017-6926 利用验证
假设id为{node_id}的文章被作者设置为不公开,想查看/回复此文章的id为{comment_id}的评论。
访问http://x.x.x.x/comment/reply/node/{node_id}/comment/{comment_id}
CVE-2017-6926 漏洞修复
升级Drupal至最新版本