2月份的时候,RIPS团队发布了一篇文章,公开了一个Wordpress的RCE漏洞,该漏洞由变量覆盖修改数据库、目录穿越写文件和模板包含三个漏洞构成,而且漏洞环境并不需要修改什么配置,质量相当的高。
漏洞要求
- WordPress commit <= 43bdb0e193955145a5ab1137890bb798bce5f0d2 (WordPress 5.1-alpha-44280)
- 拥有一个author权限的账号
影响包括windows、linux、mac在内的服务端,后端图片处理库为gd/imagick都受到影响,只不过利用难度有所差异。
官网上可以下载的5.0.0已经修复该漏洞。实际在WordPress 5.1-alpha-44280更新后未更新的4.9.9~5.0.0的WordPress都受到该漏洞影响。
修复主要是新增了_wp_get_allowed_postdata函数对输入变量进行了黑名单过滤,防止变量覆盖,也可以通过这一点查看该wordpress版本是否已经修复漏洞。
踩坑日记
第一个坑
为了方便,我在我的Ubuntu虚拟机里用docker搭建了一个环境,结果wordpress死活连不上数据库。第三天debug一下知道原因了,MySQL连接会返回一个1698报错,原因是这个环境下MySQL中user表的root用户,plugin字段默认是auth_socket,而不是mysql_native_password,导致无法使用密码登录。
解决方法,先:
update mysql.user set authentication_string=PASSWORD('root'), plugin='mysql_native_password' where user='root';
再:
flush privileges;
解决。
第二个坑
这次我用的是我的VPS的docker,环境是ubuntu18+apache2+mysql5.7+php7.2,然后在复现到第二步,也就是目录穿越写文件的时候,写文件的有其中一环:
$result = call_user_func_array( $function, $arguments );
意思是调用Imagick类的writeImage方法,参数为类似这样的字符串:
/var/www/html/wp-content/uploads/2019/03/poc.jpg?/../../../../themes/twentynineteen/cropped-32-e1552231820819.jpg
即目录穿越写入的payload,而在我这个环境下,这个调用会发生一个这样的错误:
unable to open image `/var/www/html/wp-content/uploads/2019/03/poc.jpg?/../../../../themes/twentynineteen/cropped-32-e1552231820819.jpg': No such file or directory @ error/blob.c/OpenBlob/2701
然后没有返回值,导致后面的fopen不会执行,也就没有写入新文件了。
明天再换个环境试一试好了。
第三个坑
今天外出办事,花了一个早上+中午。
第二天使用的环境为docker+ubuntu16+apache2+php7.0,跟第二个坑一样的错误,原因似乎是Imagick的路径处理不支持Linux通配符的样子。
找到了能够解决我踩的坑的文章了:
https://mp.weixin.qq.com/s/9DMGLOvFJUjq8MaMr9eg6A
这个坑是确实存在的,可以通过多次上传来解决。第一次上传我们可以创建这个带有#或者?号的目录,第二次复现就没有问题了。因为我一开始自行测试的时候,mkdir函数不加recursive参数无法从不存在的目录开始创建,所以就没有想到这一点Orz,忽略了。
第四个坑
因为apache存在rewrite的问题,所以有的时候访问上传的文件会导致404,在前面加个index.php就好了,对漏洞影响不大。如果需要开启rewrite的话,可以按照这样来:
sudo a2enmod rewrite
sudo vim /etc/apache2/apache2.conf,将AllowOverride None改成All,如下:
<Directory /var/www/> Options Indexes FollowSymLinks AllowOverride All Require all granted </Directory>
在后台-设置-连接中修改默认的路由方式,比如改成Day and name。
环境搭建
快肯定是本地环境快啊,毕竟没钱,所以环境用的是本地Ubuntu16虚拟机+docker+Ubuntu18.04的搭建方式。
从wordpress官网下载一个5.0版本的,先检查一下是不是漏洞版本,为了防止wordpress自动更新,在wp-config-sample.php中加上一句:
define('AUTOMATIC_UPDATER_DISABLED', true);
然后再找一个国内Ubuntu18的源来替换掉/etc/apt/sources.list,然后就是装装装了:
apt-get install php7.2
apt-get install mysql-server
apt-get install php7.2-mysql
apt-get install php7.2-imagick
为了方便调试,我使用Xdebug进行远程调试:
apt-get install php7.2-xdebug
详细的phpstorm+xdebug远程调试配置看这里:
https://blog.csdn.net/yf3585595511/article/details/76848443
然后把wordpress放去web目录,启动服务,给MySQL插入一个wordpress库,再修改一下mysql.user表,环境搭建完成。
漏洞复现
首先准备两张,然后选一张用exiftool将payload写入exif信息中,生成poc图片:
exiftool finally.jpg -documentname='<?php phpinfo();?>'
我使用的正常图片名为poc.jpg,poc图片为finally.jpg。
然后用author权限的用户登录wordpress后台,在Media-Add New中上传正常的图片:
然后在Media-Library点击那一张图片,进入Attachment Details页面,再点击Edit more details,进入详细信息编辑页面,随便改点什么然后update,并开启抓包:
抓到一个类似这样的包,Send to Repeater并Forward。接下里点击图片下方的Edit Image,进行图片裁剪,用鼠标随便框一部分图片,然后点击上方第一个按钮保存裁剪,在save的时候抓包:
同样Send to Repeater并Forward,接下来开始改包重发,第一个包增加一个参数:
此时如果查看数据库,应该就会看到wordpress库的wp_postmeta中这张图片的路径发生了改变。第二个包修改action的值,将postid键改为id,将history及后面的参数改为裁剪的参数:
可以看到在upload/2019/03目录下已经生成了一个新的目录:
然后我们再上传poc图片,如法炮制:
可以看到我们的poc图片已经上传到了主题目录下:
最后一步,我们随便上传一个txt文件,同样对信息进行修改,继续抓包repeat:
然后点击Media-Library->poc图片->View attachment page,RCE成功:
漏洞分析
变量覆盖
我们去修改上传文件信息,可以看到post.php会对action进行switch,最后进行editpost的分支:
跟入edit_post函数继续,我们可以在375行附近看到一个跟漏洞流程很相似的函数wp_update_post,并且参数是postdata,我们给这里下断点,然后前进到这里,进入函数看看,可以看到因为post_type为attachment的缘故,参数postarr最后会进入wp_insert_attachment中:
经过wp_parse_args处理又进入wp_insert_post函数中:
其中在3600行附近,代码对post的meta_input数组进行了遍历,然后依次调用update_post_meta来处理:
我们跟入update_post_meta函数看看,可以看到它再次调用update_metadata函数进行处理,那我们进行 跟入:
可以看到,函数用meta_input的数据对wp_metapost表进行了update操作,这就导致攻击者可以修改文件在数据库中保存的路径。
文件夹创建&&目录穿越文件写入
我们去裁剪图片,然后抓包重发,可以看到代码会调用do_action( ‘wp_ajax_crop-image’ ):
我们跟进去看看,在最后调用了$wp_filter[ $tag ]->do_action( $args ):
然后通过call_user_func_array调用了漏洞函数wp_ajax_crop_image:
wp_ajax_crop_image再调用wp_crop_image做真正的裁剪:
我们跟入,可以看到两个重点函数wp_mkdir_p和save,其中wp_mkdir_p用来创建保存裁剪后图片的目录而save用来保存裁剪后的图片,wp_mkdir_p函数的用处我们再回来看,我们先进入save函数,一步步跟入,可以看到通过make_image、fopen最后创建了图片,因为没有对目录穿越进行过滤的原因,所以这里可以目录穿越写入文件:
但是这里有一个问题,在写入文件之前会调用Imagick类的writeImage函数,而在Linux的环境下,这个函数不支持不存在的目录跳转,所以我们第一次上传图片并裁剪的时候,/var/www/html/wp-content/uploads/2019/03/poc.jpg#这个目录其实并不存在,所以这里返回的result就会为空,下面的fopen函数也就不会执行,新文件也就不会创建了。这里就需要用到前面wp_mkdir_p函数了,这个目录不存在,但是我们可以通过这个函数把它创建出来:
然后我们第二次上传一个finally.jpg并裁剪的时候,就可以正常执行,然后在主题目录下写入poc文件了。
还有一点需要注意的是,在裁剪图片之前,wordpress会确认一遍本地图片是否存在,如果不存在则拼接上URL去访问,这个时候?和#就可以对路径进行截断,后面的目录穿越就不会造成影响。所以修改图片路径的时候要注意第一次要创建出符合第二次上传的图片名字的目录来,比如我的第一次正常图片是poc.jpg,第二次恶意图片是finally.jpg,我就要用/var/www/html/wp-content/uploads/2019/03/poc.jpg#/../finally.jpg#/cropped-Twings.jpg这个payload来创建出finally.jpg#这个目录,这样第二次的payload(2019/03/finally.jpg#/../../../../themes/twentynineteen/Twings.jpg)就既可以获取到poc图片,又可以正确写入文件夹了。
模板包含RCE
最后可以看到是get_single_template函数触发了漏洞,我们直接进入函数看看:
可以看到它最后调用了get_query_template函数,而模板名称则通过前面的get_page_template_slug函数获取,我们跟入看看:
可以看到get_page_template_slug函数调用了get_post_meta函数,通过postID来获取模板,而获取的模板的meta_key为_wp_page_template,而这个值跟前面的_wp_attached_file一样,可以通过前面的变量覆盖进行修改,这就导致了文件包含RCE。
参考文章: