drupal mysql配置文件_Drupal 8 配置文件下载漏洞分析

Author: p0wd3r (知道创宇404安全实验室)

Date: 2016-09-22

0x00 漏洞概述

1.漏洞简介

Drupal ( https://www.drupal.org )是一个自由开源的內容管理系统,近期研究者发现在其8.x < 8.1.10的版本中发现了三个安全漏洞,其中一个漏洞攻击者可以在未授权的情况下下载管理员之前导出的配置文件压缩包config.tar.gz。Drupal官方在9月21日发布了升级公告( https://www.drupal.org/SA-CORE-2016-004 )。

2.漏洞影响

未授权状态下下载管理员之前导出的配置文件

3.影响版本

8.x < 8.1.10

0x01 漏洞复现

1. 环境搭建

Dockerfile(来自Docker Hub)

# from https://www.drupal.org/requirements/php#drupalversions

FROMphp:7.0-apache

RUN a2enmod rewrite

# install the PHP extensions we need

RUN apt-get update && apt-get install -y libpng12-dev libjpeg-dev libpq-dev \

&& rm -rf /var/lib/apt/lists/* \

&& docker-php-ext-configure gd --with-png-dir=/usr --with-jpeg-dir=/usr \

&& docker-php-ext-install gd mbstring opcache pdo pdo_mysql pdo_pgsql zip

# set recommended PHP.ini settings

# see https://secure.php.net/manual/en/opcache.installation.php

RUN { \

echo 'opcache.memory_consumption=128'; \

echo 'opcache.interned_strings_buffer=8'; \

echo 'opcache.max_accelerated_files=4000'; \

echo 'opcache.revalidate_freq=60'; \

echo 'opcache.fast_shutdown=1'; \

echo 'opcache.enable_cli=1'; \

} > /usr/local/etc/php/conf.d/opcache-recommended.ini

WORKDIR/var/www/html

# https://www.drupal.org/node/3060/release

ENVDRUPAL_VERSION 8.1.9

ENVDRUPAL_MD5 4de7c001ecbd5c27e5837c97e40facc2

RUN curl -fSL "https://ftp.drupal.org/files/projects/drupal-${DRUPAL_VERSION}.tar.gz" -o drupal.tar.gz \

&& echo "${DRUPAL_MD5}*drupal.tar.gz" | md5sum -c - \

&& tar -xz --strip-components=1 -f drupal.tar.gz \

&& rm drupal.tar.gz \

&& chown -R www-data:www-data sites modules themes

docker run --name dp -p 8080:80 -d drupal

2.漏洞分析

首先我们进入后台把配置文件导出,默认导出到了/tmp/config.tar.gz。

c84859a466d3126fb56c71a413df167c.png

然后看代码,我们在core/modules/system/system.routing.yml可以看到这样一个路由项:

620e08da2c7a67a45d5317d8d41c55eb.png

这是访问管理员页面时的路由,可以看到requirements._permission指定了需要管理员权限。

然后我们再看这一项:

fb3e3f32c909856f0673453e1792e002.png

可以看到并没有设置_permission,并且_access=TRUE,也就是说在未授权的情况下是可以访问这个功能的。

接下来我们跟进它的controller,在core/modules/system/src/FileDownloadController.php第41-68行的download函数:

public function download(Request $request, $scheme = 'private') {

$target = $request->query->get('file');

// Merge remaining path arguments into relative file path.

$uri = $scheme . '://' . $target;

if (file_stream_wrapper_valid_scheme($scheme) && file_exists($uri)) {

// Let other modules provide headers and controls access to the file.

$headers = $this->moduleHandler()->invokeAll('file_download', array($uri));

foreach ($headers as $result) {

if ($result == -1) {

throw new AccessDeniedHttpException();

}

}

if (count($headers)) {

return new BinaryFileResponse($uri, 200, $headers, $scheme !== 'private');

}

throw new AccessDeniedHttpException();

}

throw new NotFoundHttpException();

}

函数获取了我们传入的file参数,与$scheme拼接后检测文件是否存在。我们访问 http://xxx/system/temporary/?file=config.tar.gz 然后下断点动态调试,执行到该函数时各变量的值如下:

695018cd4ccff3509112846659f74369.png

也就是说$scheme的值是temporary。然后程序进入了file_stream_wrapper_valid_scheme函数检查协议有效性,动态跟进直到core/lib/Drupal/Core/StreamWrapper/LocalStream.php中第495-506行的url_stat函数:

69b24624219a5aeea919ce96091d76b1.png

可以看到temporary://config.tar.gz被映射到了/tmp/config.tar.gz,也就是我们刚备份到的位置,所以也就通过了file_exists。

通过检查后回到download函数中,接下来执行了如下语句:

$headers = $this->moduleHandler()->invokeAll('file_download', array($uri));

跟进invokeAll函数,在core/lib/Drupal/Core/Extension/ModuleHandler.php中第397-409行:

public function invokeAll($hook, array $args = array()) {

$return = array();

$implementations = $this->getImplementations($hook);

foreach ($implementations as $module) {

$function = $module . '_' . $hook;

$result = call_user_func_array($function, $args);

if (isset($result) && is_array($result)) {

$return = NestedArray::mergeDeep($return, $result);

}

elseif (isset($result)) {

$return[] = $result;

}

}

}

动态调试情况如下图:

7e02bddb050b4f9a67b68f771003a68c.png

implementations的值有config, file, image,遍历这三个值并调用相应函数:

config_file_download

file_file_download

image_file_download

首先调用的是config_file_download,位于core/modules/config/config.module第64-78行:

function config_file_download($uri) {

$scheme = file_uri_scheme($uri);

$target = file_uri_target($uri);

if ($scheme == 'temporary' && $target == 'config.tar.gz') {

$request = \Drupal::request();

$date = DateTime::createFromFormat('U', $request->server->get('REQUEST_TIME'));

$date_string = $date->format('Y-m-d-H-i');

$hostname = str_replace('.', '-', $request->getHttpHost());

$filename = 'config' . '-' . $hostname . '-' . $date_string . '.tar.gz';

$disposition = 'attachment; filename="' . $filename . '"';

return array(

'Content-disposition' => $disposition,

);

}

}

可以看到当且仅当文件是config.tar.gz时设置响应头以供下载,最后当返回到download函数时,执行如下语句:

return new BinaryFileResponse($uri, 200, $headers, $scheme !== 'private');

将最终的响应返回给了用户,从而触发了下载漏洞。

16c4698e671e847d1039f52ddb25dc46.png

到这里有一个想法,我们可不可以传入../../etc/passwd\x00config.tar.gz这样的参数来截断并且跳到别的目录呢?

我们看一下在core/lib/Drupal/Core/StreamWrapper/LocalStream.php中第120到144行的getLocalPath()函数,它在上面提到的url_stat函数中被调用:

protected function getLocalPath($uri = NULL) {

if (!isset($uri)) {

$uri = $this->uri;

}

$path = $this->getDirectoryPath() . '/' . $this->getTarget($uri);

if (strpos($path, 'vfs://') === 0) {

return $path;

}

$realpath = realpath($path);

if (!$realpath) {

// This file does not yet exist.

$realpath = realpath(dirname($path)) . '/' . drupal_basename($path);

}

$directory = realpath($this->getDirectoryPath());

if (!$realpath || !$directory || strpos($realpath, $directory) !== 0) {

return FALSE;

}

return $realpath;

}

可以看到路径中的../被realpath过滤,跳出/tmp的目的也就不能达到了。

另外还有一个方面,config_file_download函数比较苛刻,只允许下载config.tar.gz,而image_file_download是为了下载图片,那么file_file_download函数能否供我们利用以下载系统的敏感文件呢?

file_file_download函数在core/modules/file/file.module中第582-633行:

function file_file_download($uri) {

// Get the file record based on the URI. If not in the database just return.

/** @var \Drupal\file\FileInterface[] $files */

$files = entity_load_multiple_by_properties('file', array('uri' => $uri));

if (count($files)) {

foreach ($files as $item) {

if ($item->getFileUri() === $uri) {

$file = $item;

break;

}

}

}

if (!isset($file)) {

return;

}

...

}

有以下三点:

根据注释可以看到该函数是根据uri来查询数据库中的文件记录再进行下载

我们请求中的$scheme为temporary,在file_stream_wrapper_valid_scheme($scheme) && file_exists($uri)限制了我们只能下载/tmp目录下存在的文件

默认/tmp下除了config.tar.gz只有.htaccess

综合这三点来看file_file_download函数是不存在下载漏洞的。

所以总的来说该漏洞只能在管理员导出备份的情况下下载/tmp/config.tar.gz。

3.补丁分析

26dae574c1ffb1949665dbafe992b796.png

增加了权限验证,使未授权用户不能下载cnofig.tar.gz。

0x02 修复方案

升级Drupal到8.1.10

0x03 参考

https://www.seebug.org/vuldb/ssvid-92436

https://www.drupal.org/SA-CORE-2016-004

本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/50/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值