Wordpress 5.0.0远程代码执行漏洞分析与复现
发布时间 2019-02-232月19日,Rips在博客上披露了一个关于Wordpress 5.0.0远程代码执行漏洞。该漏洞为CVE-2019-8942和 CVE-2019-8943组合漏洞,漏洞代码在Wordpress核心中已存在了6年。
漏洞由三部分构成:
(核心问题)PostMeta可以被覆盖。攻击者可以控制POST中meta_input字段的值,从而自由更改wp_postmeta表中的meta_key与meta_value的值。
更新附件时,攻击者可自由设置对应附件的_wp_attached_file的值,并结合裁剪功能实现目录穿越,从而将恶意图片保存到任意目录。
更新文章时,攻击者可以自由设置文章的_wp_page_template的值,并结合模板功能实现本地文件包含,从而最终造成代码执行。
2.1 Post Meta覆盖(核心问题)
当编辑一个POST时会调用edit_post方法,wp-admin/includes/post.php:208
此时会将$_POST赋值给$post_data。然后$post_data的值又会被带入到wp_update_post函数中。
跟踪wp_update_post函数,wp-includes/post.php:3969
在该函数末尾,都会调用wp_insert_post函数,并将$postarr传入到该函数中,wp_insert_post函数略长,在该函数中有这样一段代码,wp-includes/post.php:3779 。
对$postarr['meta_input']做一个遍历,并将键值都带入到update_post_meta函数中,该函数内容如下:
调用了update_metadata函数,对应的$meta_key和$meta_value都是攻击者可控的。该函数主要功能就是wp_postmeta表进行更新和插入。
wp_postmeta表结构如下:
通过该函数,攻击者可以自由增加和修改对应post_id的meta_key和meta_value的值。
目录穿越问题是以Post Meta覆盖为铺垫的。在wp-admin/includes/ajax-actions.php:3520
在wp_ajax_crop_image函数中,第一行就传入了一个$_POST['id']参数。然后还传入了$_POST['cropDetails']参数。都是攻击者可控的。并将这些值带入到了wp_crop_image函数中,函数体如下:
在28行,会进入该if,传入的$src是攻击者可控的,带入到get_attached_file函数中,函数体如下:
调用get_post_meta函数,将wp_postmeta表里对应的post_id字段meta_key值为_wp_attached_file的meta_value值查询出来并返回。由上文可知,该值是攻击者可以自己覆盖的,是可控的。
进入第二个if分支中,调用wp_get_attachment_url函数,查看该函数:
如上图标注的所示,最后形成的是一个url链接。
如果,攻击者将meta_value更改为2019/02/evil.jpg#/../../../../../theme-compat/evil.jpg。
最后,形成的url就是这样http://localhost/wp-content/uploads/2019/02/evil.jpg#/../../../../../theme-compat/evil.jpg。
跟踪该函数:
其中有一步细节的操作是在_wp_image_editor_choose这个函数中:
Wordpress提供了两种方式来处理图片,Imagick是优先级最高的,GD其次。这个顺序会影响最终环境的利用。
而Imagick和GD对图片也有不同的处理:
Imagick不会去除掉图片中的exif部分,所以我们可以将待执行payload代码加入到exif部分。
GD会去除图片的exif部分,并且其中的phpcode很难存活。除非通过精心构造一张图片才可以。
在这里我们选择Imagick库,选择好图片处理库之后就返回该库并调用load方法加载url:
这里有一个坑点,就是Imagick处理类的load函数中调用的是readImage函数,但在高版本的Imagick上该函数不支持远程图片链接,因此我采用Imagick-6.9.7来复现,环境如下图:
调用完load函数后,就是对获取到的图片内容进行裁剪处理,然后把新生成的图片进行保存,查看保存新文件的操作(造成目录穿越的终点):
$dst_file是保存的文件名,生成规则如上图标注所示。因此生成最终文件路径为:uploaddir/2019/02/cropped-evil.jpg#/../../../../../theme-compat/cropped-evil.jpg。
这里有一个注意点,就是会先调用wp_mkdir_p函数来创建目录,然后再调用save函数保存文件。
save函数核心如下图:
这里调用了make_image函数,函数体如下:
这里又有一个坑点,这里会用call_user_func_array函数来调用Imagick的writeImage函数,并将$filename传递进去,值得注意的一点是该函数在Linux下不支持不存在的目录跳转。
但是为了达到目录穿越的目的,我们这里传入的$filename就是uploaddir/2019/02/cropped-evil.jpg#/../../../../../theme-compat/cropped-evil.jpg 。
由于cropped-evil.jpg#是个不存在的目录,因此该函数会调用失败抛出错误终止流程,自然也无法调用fopen和fwrite进行写文件的操作。
借助多次上传裁剪就可以绕过这个坑点,但是生成的新图片又有什么用呢?
在wp-includes/template-loader.php:55
这里调用了get_single_template函数:
第一行获取请求的对象。当我们通过路由浏览文章时这里会返回WP_Post对象,其中包含文章的一些属性。
然后将该对象带入到了get_page_template_slug函数中,函数体如下:
根据post_id从wp_postmeta表中取出meta_key字段值为_wp_page_template的meta_value的值并返回。
从核心问题可知,这里返回的meta_value的值同样是可以被攻击者自由覆盖的,因此该值是攻击者可控的。
调用locate_template函数,函数体如下:
这里做路径的拼接和判断。$template_name是可控的,因此结合上文的目录穿越,将新生成的图片放到theme-compat目录下即可。
调用include将图片包含,执行代码。
3、漏洞复现
这里,我们采用mac os+php7.1+wordpress4.9.8+imagick6.9.7进行复现。
然后更新该图片信息:
并使用burp抓包,更改如图所示:
然后开始裁剪图片,点击edit Image并抓包得到nonce并构造报文。
继续上传并裁剪一张名为finally.jpg的图片,成功创建文件:
然后新增加一篇文章,在update时继续抓包更改如下图所示:
完毕后。查看该文章,成功触发phpinfo!
4、补丁分析
添加了_wp_get_allowed_postdata方法,将meta_input字段从POST报文中去掉了。