作者:绿盟科技
来源:<http://blog.nsfocus.net/drupal8-cve-2017-6926/>
近期,著名的Drupal CMS网站爆出7个漏洞,其中1个严重漏洞CVE-2017-6926,具有发表评论权限的用户可以查看他们无权访问的内容和评论,并且还可以为该内容添加评论。绿盟科技于上周发布了[《Drupal下周将发布重要安全补丁威胁预警通告》](http://blog.nsfocus.net/drupal-vulnerability/ "《Drupal下周将发布重要安全补丁威胁预警通告》")。
本篇文章对Drupal 8 – CVE-2017-6926漏洞进行了详细分析。
#### CVE-2017-6926 漏洞详情
先看下drupal官网的通告:
<https://www.drupal.org/sa-core-2018-001></https://www.drupal.org/sa-core-2018-001>
![](https://images.seebug.org/content/images/2018/04/3f7271bf-e7e2-4c5c-99a7-d1abe254a9ab.png-w331s)
有发布评论权限的用户,可以查看他们无权访问的内容和评论。
并且还可以为此内容添加评论。
想要触发这个漏洞,必须启用评论系统,并且攻击者必须有权发布评论。
从漏洞描述来看,问题可能出在评论的权限控制上了。
但是这里有一个坑,笔者当时就掉进去了:这里的权限,指的是文章的权限?还是评论的权限呢?是攻击者可以访问他们不公开的评论呢?还是攻击者可以访问不公开的文章的公开评论呢?下面我会详细的分析这个漏洞,并给出上面问题的答案。
漏洞是在8.4.5上解决的,看一下8.4.5修改的内容:
![](https://images.seebug.org/content/images/2018/04/5084eca0-585f-4995-9642-9a8de34419a9.png-w331s)
这里在CommentController.php(评论控制器)里加上了一个对entity实体是否有view权限的判断。
这个好理解,之前没有对entity的权限进行判断,导致不可view的entity也通过了权限检查,从而导致了越权。
我们看下关于entity的介绍:
![](https://images.seebug.org/content/images/2018/04/2a5bdf97-abd2-46dd-b3d0-3c31dfae2828.png-w331s)
我们再看下漏洞存在的函数
`\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评论进行评论:
![](https://images.seebug.org/content/images/2018/04/b9f260aa-121c-422c-bc26-5bfd72d3c95e.png-w331s)
![](https://images.seebug.org/content/images/2018/04/bae2e36e-de20-4d68-b053-433957e66cfe.png-w331s)
注意看url:<http://127.0.0.1/d/drupal4/comment/reply/node/1/comment/1></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的数据
![](https://images.seebug.org/content/images/2018/04/9dbabf7f-a0ce-4376-93ae-26f94133321f.png-w331s)
在大概知道$entity是什么之后,我们再来看下补丁代码:
![](https://images.seebug.org/content/images/2018/04/fcd4edb5-19f6-4576-abb0-4156a899dbe4.png-w331s)
可见补丁加了一个判断
```
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:
![](https://images.seebug.org/content/images/2018/04/534441aa-285b-4e9a-9582-a47d052bd893.png-w331s)
首先来找找,关于回复评论处的权限设置:
我们在admin账号下,发表一片名为kingsguard的文章,此文章有一个kingsguard test的评论:
![](https://images.seebug.org/content/images/2018/04/10e36b40-07aa-4920-871b-d6c904afa239.png-w331s)
我们将kingsguard test 这条评论的权限编辑成不公开
![](https://images.seebug.org/content/images/2018/04/df365502-61ae-4e55-a632-e9786dd03bec.png-w331s)
我们用admin账号回复一下这个评论,后台看下$$test = $entity->access(‘view’);的值:
![](https://images.seebug.org/content/images/2018/04/ddccd9af-4cfd-43a4-98e1-2451df4acbaf.png-w331s)
![](https://images.seebug.org/content/images/2018/04/09619b47-a0fb-4f0f-8b5e-a9be25009a0f.png-w331s)
毫无疑问,$test的值是true
现在用另一个账号登录,也访问<http://127.0.0.1/d/drupal4/comment/reply/node/2/comment/6></http://127.0.0.1/d/drupal4/comment/reply/node/2/comment/6>这个连接试试:
![](https://images.seebug.org/content/images/2018/04/2ec38203-cf3c-469f-9090-0325318f9c4e.png-w331s)
$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/1/comment/6>(回复第一篇文章的第六个评论)
<http://127.0.0.1/d/drupal4/comment/reply/node/2/comment/6></http://127.0.0.1/d/drupal4/comment/reply/node/2/comment/6>(回复第二篇文章的第六个评论)
显然这里的1和2,指的是文章的编号,同时也是entity的编号,那显然entity指的是文章而不是评论。
我们用admin账号编辑kingsguard这篇文章,把published选项的勾去掉:
![](https://images.seebug.org/content/images/2018/04/7169e338-0a02-40b9-8799-87fc5123ede8.png-w331s)
这时候用admin账号回复kingsguard test这条评论:
![](https://images.seebug.org/content/images/2018/04/bda6024a-21ca-423c-bf7e-1fb489c3e113.png-w331s)
对于admin账号来说,$entity->access(‘view’);仍为true
再用其他账号登录:
![](https://images.seebug.org/content/images/2018/04/0d5d323b-e178-494a-a401-f080c09fbe3a.png-w331s)
因为admin账号发布的文章都被admin账号设置为不公开了,所以这里看不到任何文章。
继续用这个账号访问:
<http://127.0.0.1/d/drupal4/comment/reply/node/2/comment/6></http://127.0.0.1/d/drupal4/comment/reply/node/2/comment/6>
![](https://images.seebug.org/content/images/2018/04/c8816238-294e-46e4-8430-3a4bd1336c2d.png-w331s)
可以看到,虽然文章不公开,仍然可以看到不公开文章的评论,并且还能对评论进行回复。
再看下后台下的断点:
![](https://images.seebug.org/content/images/2018/04/a60ab6ed-bbfc-4eae-a50a-b827c6a25c8e.png-w331s)
此时的$entity->access(‘view’)变成了false
#### 利用验证
假设id为{node_id}的文章被作者设置为不公开,想查看/回复此文章的id为{comment_id}的评论。
访问<http://x.x.x.x/comment/reply/node/{node_id}/comment/{comment_id}></http://x.x.x.x/comment/reply/node/{node_id}/comment/{comment_id}>
#### 漏洞修复
升级Drupal至最新版本
暂无评论