近期,著名的Drupal CMS网站爆出7个漏洞,其中1个严重漏洞CVE-2017-6926,具有发表评论权限的用户可以查看他们无权访问的内容和评论,并且还可以为该内容添加评论。

本篇文章对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至最新版本