前言:
准备阶段
metinfo<=6.2
php<5.4(我用5.3)+mysql
windows10
参考文章
参考学长的文章,虽然要自己POC,但是文章写得很清楚了,基本就是把POC全部给出来了。
某info <= 6.2.0前台任意文件上传漏洞
Metinfo6 Arbitrary File Upload Via Iconv Truncate
漏洞成因
iconv在windows下编码转换存在截断问题,构造合适的文件路径和文件名,对其进行恶意文件上传。
漏洞分析
(1)
函数 douploadfile()
定位:/app/system/include/module/uploadify.class.php::65行1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public function doupfile(){
global $_M;
$this->upfile->set_upfile();
$info['savepath'] = $_M['form']['savepath'];
$info['format'] = $_M['form']['format'];
$info['maxsize'] = $_M['form']['maxsize'];
$info['is_rename'] = $_M['form']['is_rename'];
$info['is_overwrite'] = $_M['form']['is_overwrite'];
$this->set_upload($info);
$back = $this->upload($_M['form']['formname']);
if($_M['form']['type']==1){
if($back['error']){
$back['error'] = $back['errorcode'];
}else{
$backs['path'] = $back['path'];
$backs['append'] = 'false';
$back = $backs;
}
}
$back['filesize'] = round(filesize($back['path'])/1024,2);
echo jsonencode($back);
}
首先先调用了set_upfile()函数,设置上传文件的模式,就是先设定上传文件的信息、
定位:/app/system/include/module/upfile.class.php::67行1
2
3
4
5
6
7
8
9
10
11
/**
* 设置上传文件模式
*/
public function set_upfile() {
global $_M;
$this->set('savepath', 'file');
$this->set('format', $_M['config']['met_file_format']);
$this->set('maxsize', min($_M['config']['met_file_maxsize']*1048576, 1073741824));
$this->set('is_rename', $_M['config']['met_img_rename']);
}
接着调用set_upload()函数,参数来自于$info
定位:/app/system/include/module/uploadify.class.php::28行1
2
3
4
5
6
7
8public function set_upload($info){
global $_M;
$this->upfile->set('savepath', $info['savepath']);
$this->upfile->set('format', $info['format']);
$this->upfile->set('maxsize', $info['maxsize']);
$this->upfile->set('is_rename', $info['is_rename']);
$this->upfile->set('is_overwrite', $info['is_overwrite']);
}
我们可以看到这两个函数均对savepath进行处理,并且要命的是,set_upload()中处理的savepath来自于$_M[‘form’][‘savepath’],这个数据是用户可控的,这是本次漏洞中的注入位置。
我们跟踪查看一下set函数
定位:/app/system/include/module/upfile.class.php::43行1
2
3
4
5
6
7
8public function set($name, $value) {
... ...
switch ($name) {
case 'savepath':
$this->savepath = path_standard(PATH_WEB.'upload/'.$value);
... ...
}
}
我们可以看到最终savepath将会为‘webserver/upload/file/‘的形式指定文件存放的位置。
接着调用函数1
$back = $this->upload($_M['form']['formname']);
(2)
跟踪查看
定位:/app/system/include/module/upfile.class.php::90行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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89public function upload($form = '') {
global $_M;
if($form){
foreach($_FILES as $key => $val){
if($form == $key){
$filear = $_FILES[$key];
}
}
}
if(!$filear){
foreach($_FILES as $key => $val){
$filear = $_FILES[$key];
break;
}
}
//是否能正常上传
//空间超容 有些虚拟主机不支持此函数
//目录不可写
//文件大小是否正确{}
//文件后缀是否为合法后缀
$this->getext($filear["name"]); //获取允许的后缀
if (strtolower($this->ext)=='php'||strtolower($this->ext)=='aspx'||strtolower($this->ext)=='asp'||strtolower($this->ext)=='jsp'||strtolower($this->ext)=='js'||strtolower($this->ext)=='asa') {
return $this->error($this->ext." {$_M['word']['upfileTip3']}");
}
//$_M['config']['met_file_format']:
//rar|zip|sql|doc|pdf|jpg|xls|png|gif|mp3|jpeg|bmp|swf|flv|ico
if ($_M['config']['met_file_format']) {
if($_M['config']['met_file_format'] != "" && !in_array(strtolower($this->ext), explode('|',strtolower($_M['config']['met_file_format']))) && $filear){
return $this->error($this->ext." {$_M['word']['upfileTip3']}");
}
} else {
return $this->error($this->ext." {$_M['word']['upfileTip3']}");
}
if ($this->format) {
if ($this->format != "" && !in_array(strtolower($this->ext), explode('|',strtolower($this->format))) && $filear) {
return $this->error($this->ext." {$_M['word']['upfileTip3']}");
}
}
//文件名重命名
$this->set_savename($filear["name"], $this->is_rename);
//新建保存文件
if(stripos($this->savepath, PATH_WEB.'upload/') !== 0){
return $this->error($_M['word']['upfileFail2']);
}
if(strstr($this->savepath, './')){
return $this->error($_M['word']['upfileTip3']);
}
if (!makedir($this->savepath)) {
return $this->error($_M['word']['upfileFail2']);
}
//复制文件
$upfileok=0;
$file_tmp=$filear["tmp_name"];
$file_name=$this->savepath.$this->savename;
if (stristr(PHP_OS,"WIN")) {
$file_name = @iconv("utf-8","GBK",$file_name);
}
if (function_exists("move_uploaded_file")) {
if (move_uploaded_file($file_tmp, $file_name)) {
$upfileok=1;
} else if (copy($file_tmp, $file_name)) {
$upfileok=1;
}
} elseif (copy($file_tmp, $file_name)) {
$upfileok=1;
}
if (!$upfileok) {
if (file_put_contents($this->savepath.'test.txt','metinfo')) {
$_M['word']['upfileOver4']=$_M['word']['upfileOver5'];
}
unlink($this->savepath.'test.txt');
$errors = array(0 => $_M['word']['upfileOver4'], 1 =>$_M['word']['upfileOver'], 2 => $_M['word']['upfileOver1'], 3 => $_M['word']['upfileOver2'], 4 => $_M['word']['upfileOver3'], 6=> $_M['word']['upfileOver5'], 7=> $_M['word']['upfileOver5']);
$filear['error']=$filear['error']?$filear['error']:0;
return $this->error($errors[$filear['error']]);
} else {
if(stripos($filear['tmp_name'], PATH_WEB) === false){
@unlink($filear['tmp_name']); //Delete temporary files
}
}
load::plugin('doqiniu_upload',0,array('savename'=>str_replace(PATH_WEB, '', $this->savepath).$this->savename,'localfile'=>$file_name));
$back = '../'.str_replace(PATH_WEB, '', $this->savepath).$this->savename;
return $this->sucess($back);
}
这个函数对之前设置的参数进行检验过滤处理等等……
相当严格的过滤,所以我们能上传的文件类型只能是白名单中的。
查看文件重命名操作1
$this->set_savename($filear["name"], $this->is_rename);
跟进查看函数set_savename()
定位:/app/system/include/module/upfile.class.php::354行1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18protected function set_savename($filename, $is_rename) {
if ($is_rename) {
... ...
}else{
$name_verification = explode('.',$filename);
$verification_mun = count($name_verification);
if($verification_mun>2){
$verification_mun1 = $verification_mun-1;
$name_verification1 = $name_verification[0];
for($i=0;$i<$verification_mun1;$i++){
$name_verification1 .= '_'.$name_verification[$i];
}
$name_verification1 .= '.'.$name_verification[$verification_mun1];
$filename = $name_verification1;
}
$filename = str_replace(array(":", "*", "?", "|", "/" , "\\" , "\"" , "<" , ">" , "——" , " " ),'_',$filename);
... ...
... ...
是否对文件名进行重命名操作,而这个操作的参数也是来自于用户输入$_M[‘form’]
如果我们试图写入shell.php.jpg文件名,之后文件名会被改成shell_shell_php.jpg
查看新建保存文件功能,这里对文件路径进行验证,要以upload开头,并且不能有‘./‘
tip:CTF中,如果想绕过’./‘的过滤,在 windows 下可以使用’..\’来进行路径穿越
查看函数makedir()
将会创建一个dir
接着漏洞引发点1
2
3
4
5
6
7
8
9//复制文件
$upfileok=0;
$file_tmp=$filear["tmp_name"];
$file_name=$this->savepath.$this->savename;
if (stristr(PHP_OS,"WIN")) {
$file_name = @iconv("utf-8","GBK",$file_name);
}
...
...
复制文件处1
$file_name=$this->savepath.$this->savename;
文件名由savepath和savename拼接而成
然后如果是windows系统就会通过iconv()函数转码,这个函数在win下,php版本小于5.4时,我使用5.3.将会出现截断,
比如shell.php%80xxxxxx,%80以后的内容会由于转码的问题,而被截断。
关于这个函数参考文章:PHP iconv函数字符串转码导致截断问题
然后后续函数中会将文件内容存入filename,这时候的filename,可以是我们指定的文件形式。由此写入文件
POC
总结
学习到了iconv()函数,然后代码的理解以及逻辑结构,函数之间的关联的理解能力有待提高,要了解参数传入处
比如$_M[‘form’],要会溯源,然后发现在common.inc.php有定义1
2
3
4/**
* 获取GET,POST,COOKIE,存放在$_M['form'],系统表单提交变量数组
*/
所以还是要多学,多向身边的同学学习