WordPress5.0远程代码执行踩坑分析

分析由来

       这个洞费了我很长时间,因为踩坑比较多,基本上能踩的都踩了。刚开始跟着别人的分析文章走,发现走不通。可能忽略了很多东西,加上我第一次认识 WordPress,不是很熟悉这个框架,并且前端比较复杂,很难找到对应的调用点。其实完全分析到复习成功看来其实确实是一个很精妙的攻击链,不得不赞叹一下!

整个攻击链的流程

  1. 上传我们构造exif头带shell的图片
  2. 进入编辑图片第一次进入edit_post修改图片postmeta_wp_attachment_file字段为辅助目录,再进入crop-image 创建辅助目录
  3. 进入编辑图片第二次进入edit_post修改图片postmeta_wp_attachment_file字段为目标目录,再进入crop-image 创建目标文件
  4. 创建一个新的文章页面
  5. 进去新创建的文章页面,获取wponcepost_id
  6. 自己构造编辑内容即手动添加wponcepost_id同上修改postmeta_wp_page_template字段为上传文件名字前加上cropped- 7.访问修改过的文章页面Getshell

填坑

  1. 第一步构造exif没什么难度,随便找个可写的tag 比如User Comment

  2. 第二步即整个链的关键之处,可以修改和添加任意post的postmeta,但是为什么这里有两步修改_wp_attachment_file,这是其中第一个坑,我的看的分析文章中都没有提过这个问题。列如我们需要将图片移动至相应的主题文件下充当模板。 上传m.jpg 路径为/wp-content/upload/2019/3/05/m.jpg 则需要构造m.jpg?/../../../../themes/twentynineteen/m2.jpg 如果第一步就直接构造这个是有一点问题,会导致再crop-image中写了修改过的图片。

          在 /wp-includes/class-wp-image-editor.php 402行中 会调用对应图片编辑扩展的writeImage函数来写图片,会报错文件地址非法,这里原因就是不存在m.jpg?这个目录,前面虽然有mkdir(dirname($filename),777,true),以递归的形式写,而且会返回true,但是并没有创m.jpg这个目录。而一般涉及到读写的函数都会逐级去判断目录,imagick::writeImage在判断路径时就发生了错误,但是为什么mkdir会执行成功呢,可能原因在于mkdir第三个参数,之前我在看php的内核源码的看见过关于路径的一个expand过程,过程中会将./ ../ 这两个目录展开,我猜测可能是进行了展开,将m.jpg?../抵消了。

           但是问题来了,同样我寻思这样的情况也是在window下成立,确实如此,但是在window情况下出现了两种情况第一种如果文件里面包含的是?,window不允许文件名包含?,道理上在true情况下会忽略,但实际上在truefalse的情况下都写不了,当文件名包含#时在true的情况可以写

          这时候出现了第二种情况,我看见一篇分析中在文件名包含?,却在false的情况下执行成功了,true的情况却不行。作者的解释时true时递归判断了m.jpg?目录名的合法性,在false下没有判断出现了这个问题。这不明显矛盾了吗?

         事后我联系了文章作者询问其php版本为7.0,window我的为5.5。三种不同的情况(包括全程复现下的环境kali/debian php 7.3),这mkdir真的有趣,非常有必要去看一下。

    以下均为文件名包含?的情况

    1. window+php 5.5 mkdir(filename,777,true),mkdir(filename,777,false) 均为false
    2. window+php 7.0 mkdir(filename,777,true)=>false,mkdir(filename,777,false) =>true
    3. linux(kali/debian)+php7.3 mkdir(filename,777,true)=>true,mkdir(filename,777,false) =>false

          写完这篇文章后,mkdir内部的实现必须要看了。结合前面说的,必须先创建辅助目录m.jpg?才能在都后面写图片成功。这一步需要写两次!!

  3. 接下来的模板包含情况,一开始我认为在edit_post 里面只能编辑附件,无奈wordpress 前端太复杂,我找不到接口。只能看wordpress 解析请求的过程,看如何构造才能走到 singer 或者 page 模式上去,因为只有这两个模式加载的时候才会从postmeta中取wp_page_template来加载模板,可惜无论我怎样构造都会加载到attachment的模式上去。

          仔细思考了一下,文章可以走到singer上去,但是能不能修改文章类型的postmeta,因为在请求的时候wordpress有自带的防csrf机制,如果想要改必须要相对应的wpnonce才行。比较wpnonce过程为

    1
    2
    check_admin_referer('update-post_'.$post_id);
    wp_verify_nonce($_REQUEST['wpnonce'],$action); //$action='update-post_'.$post_id

           显然需要动作和post_id结合而成的wpnonce,有了这个我们就能使用edit_post,找了很长时间都没有找到带这个参数的接口,突然我觉得可以在文件编辑页面的html源码搜索一下,尽然有!!,在meta-form 中,到现在我都不知道如何操作才会使用到这个表单,修改文章过程中都是传的json

          拿到wpnoce和post_id拼接请求,且通过meta_input[]修改wp_page_template,包含图片拿到shell。

总结

      确实在看别人分析文章的时候,会出现一些偏差,导致自己的思维有些被限制住了,整个链从任意修改数据库中postmeta,到写文件的路径穿越,到模板文件的加载一气呵成,这样的链也不多了。总以为在分析完一个东西以后,会万事大吉,却发现问题只会源源不断的来 :)。文件最后也遗留了一个关于phpmkdir的处理过程。这也是我接下来的下一篇文章。 第一次用markdown写东西,写的不好大家别介意:)

附上做的一点验证性实验,很有可能我的猜测是正确的!第一个是为false的情况,第二是为true递归执行的情况,我用的是php7.4-dev

图丢失lol