PHP…

0x01

任意文件删除

漏洞分析

漏洞出现在/web/source/site/category.ctrl.php中的176行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
elseif ($do == 'delete') {
load()->func('file');
$id = intval($_GPC['id']);
$category = pdo_fetch("SELECT id, parentid, nid FROM ".tablename('site_category')." WHERE id = '$id'");
if (empty($category)) {
message('抱歉,分类不存在或是已经被删除!', url('site/category/display'), 'error');
}
$navs = pdo_fetchall("SELECT icon, id FROM ".tablename('site_nav')." WHERE id IN (SELECT nid FROM ".tablename('site_category')." WHERE id = {$id} OR parentid = '$id')", array(), 'id');
if (!empty($navs)) {
foreach ($navs as $row) {
file_delete($row['icon']);
}
pdo_query("DELETE FROM ".tablename('site_nav')." WHERE id IN (".implode(',', array_keys($navs)).")");
}

这里有一个file_delete函数,跟到该函数,在/framework/function/file.func.php

1
2
3
4
5
6
7
8
9
10
11
12
function file_delete($file) {
if (empty($file)) {
return FALSE;
}
if (file_exists($file)) {
@unlink($file);
}
if (file_exists(ATTACHMENT_ROOT . '/' . $file)) {
@unlink(ATTACHMENT_ROOT . '/' . $file);
}
return TRUE;
}

可以看到这里是一个删除文件的操作,所以需要控制传进来的\$row[‘icon’],就可以达到任意文件删除的操作了,但是注意这里需要插入数据的数据库是tablename(‘site_nav’),回到catefory.crtl.php,看到post的部分

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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
elseif ($do == 'post') {
$parentid = intval($_GPC['parentid']);
$id = intval($_GPC['id']);
$setting = uni_setting($_W['uniacid'], array('default_site'));
$site_styleid = pdo_fetchcolumn('SELECT styleid FROM ' . tablename('site_multi') . ' WHERE id = :id', array(':id' => $setting['default_site']));
if($site_styleid) {
$site_template = pdo_fetch("SELECT a.*,b.name,b.sections FROM ".tablename('site_styles').' AS a LEFT JOIN ' . tablename('site_templates') . ' AS b ON a.templateid = b.id WHERE a.uniacid = :uniacid AND a.id = :id', array(':uniacid' => $_W['uniacid'], ':id' => $site_styleid));
}

$styles = pdo_fetchall("SELECT a.*, b.name AS tname, b.title FROM ".tablename('site_styles').' AS a LEFT JOIN ' . tablename('site_templates') . ' AS b ON a.templateid = b.id WHERE a.uniacid = :uniacid', array(':uniacid' => $_W['uniacid']), 'id');
if(!empty($id)) {
$category = pdo_fetch("SELECT * FROM ".tablename('site_category')." WHERE id = '$id' AND uniacid = {$_W['uniacid']}");
if(empty($category)) {
message('分类不存在或已删除', '', 'error');
}
if (!empty($category['css'])) {
$category['css'] = iunserializer($category['css']);
} else {
$category['css'] = array();
}
} else {
$category = array(
'displayorder' => 0,
'css' => array(),
);
}
if (!empty($parentid)) {
$parent = pdo_fetch("SELECT id, name FROM ".tablename('site_category')." WHERE id = '$parentid'");
if (empty($parent)) {
message('抱歉,上级分类不存在或是已经被删除!', url('site/category/display'), 'error');
}
}
$category['style'] = $styles[$category['styleid']];
$category['style']['tname'] = empty($category['style']['tname'])? 'default' : $category['style']['tname'];
if(!empty($category['nid'])) {
$category['nav'] = pdo_get('site_nav', array('id' => $category['nid']));
} else {
$category['nav'] = array();
}
$multis = pdo_getall('site_multi', array('uniacid' => $_W['uniacid']), array(), 'id');

if (checksubmit('submit')) {
if (empty($_GPC['cname'])) {
message('抱歉,请输入分类名称!');
}
$data = array(
'uniacid' => $_W['uniacid'],
'name' => $_GPC['cname'],
'displayorder' => intval($_GPC['displayorder']),
'parentid' => intval($parentid),
'description' => $_GPC['description'],
'styleid' => intval($_GPC['styleid']),
'linkurl' => $_GPC['linkurl'],
'ishomepage' => intval($_GPC['ishomepage']),
'enabled' => intval($_GPC['enabled'])
);

$data['icontype'] = intval($_GPC['icontype']);
if($data['icontype'] == 1) {
$data['icon'] = '';
$data['css'] = serialize(array(
'icon' => array(
'font-size' => $_GPC['icon']['size'],
'color' => $_GPC['icon']['color'],
'width' => $_GPC['icon']['size'],
'icon' => empty($_GPC['icon']['icon']) ? 'fa fa-external-link' : $_GPC['icon']['icon'],
),
));
} else {
$data['css'] = '';
$data['icon'] = parse_path($_GPC['iconfile']);
}

$isnav = intval($_GPC['isnav']);
if($isnav) {
$nav = array(
'uniacid' => $_W['uniacid'],
'categoryid' => $id,
'displayorder' => $_GPC['displayorder'],
'name' => $_GPC['cname'],
'description' => $_GPC['description'],
'url' => "./index.php?c=site&a=site&cid={$category['id']}&i={$_W['uniacid']}",
'status' => 1,
'position' => 1,
'multiid' => intval($_GPC['multiid']),
);
if ($data['icontype'] == 1) {
$nav['icon'] = '';
$nav['css'] = serialize(array(
'icon' => array(
'font-size' => $_GPC['icon']['size'],
'color' => $_GPC['icon']['color'],
'width' => $_GPC['icon']['size'],
'icon' => empty($_GPC['icon']['icon']) ? 'fa fa-external-link' : $_GPC['icon']['icon'],
),
'name' => array(
'color' => $_GPC['icon']['color'],
),
));
} else {
$nav['css'] = '';
$nav['icon'] = $_GPC['iconfile'];
}
if($category['nid']) {
$nav_exist = pdo_fetch('SELECT id FROM ' . tablename('site_nav') . ' WHERE id = :id AND uniacid = :uniacid', array(':id' => $category['nid'], ':uniacid' => $_W['uniacid']));
} else {
$nav_exist = '';
}
if(!empty($nav_exist)) {
pdo_update('site_nav', $nav, array('id' => $category['nid'], 'uniacid' => $_W['uniacid']));
} else {
pdo_insert('site_nav', $nav);
$nid = pdo_insertid();
$data['nid'] = $nid;
}
} else {
if($category['nid']) {
$data['nid'] = 0;
pdo_delete('site_nav', array('id' => $category['nid'], 'uniacid' => $_W['uniacid']));
}
}
if (!empty($id)) {
unset($data['parentid']);
pdo_update('site_category', $data, array('id' => $id));
} else {
pdo_insert('site_category', $data);
$id = pdo_insertid();
$nav_url['url'] = "./index.php?c=site&a=site&cid={$id}&i={$_W['uniacid']}";
pdo_update('site_nav', $nav_url, array('id' => $data['nid'], 'uniacid' => $_W['uniacid']));
}
message('更新分类成功!', url('site/category'), 'success');

}
template('site/category');

这里需要寻找插入tablename('site_nav')的操作以及包含icon字段,看到140行

1
pdo_insert('site_nav', $nav);

接着往上面寻找\$nav,看到130行

1
$nav['icon'] = $_GPC['iconfile'];

$_GPC是一个由GET和POST合并的数组,之后寻找路由的时候会提及,这里被iconfile赋值,并且没有做任何的过滤,但是有一些if判断需要注意.

1
2
3
if (checksubmit('submit'))
if($isnav)
if ($data['icontype'] == 1)

这些判断都需要我们绕过.接下来寻找利用路由.首先看到index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
require './framework/bootstrap.inc.php';
$host = $_SERVER['HTTP_HOST'];
if (!empty($host)) {
$bindhost = pdo_fetch("SELECT * FROM ".tablename('site_multi')." WHERE bindhost = :bindhost", array(':bindhost' => $host));
if (!empty($bindhost)) {
header("Location: ". $_W['siteroot'] . 'app/index.php?i='.$bindhost['uniacid'].'&t='.$bindhost['id']);
exit;
}
}
if($_W['os'] == 'mobile' && (!empty($_GPC['i']) || !empty($_SERVER['QUERY_STRING']))) {
header('Location: ./app/index.php?' . $_SERVER['QUERY_STRING']);
} else {
header('Location: ./web/index.php?' . $_SERVER['QUERY_STRING']);
}

不是手机登陆,所以跟到/web/index.php

1
require '../framework/bootstrap.inc.php';

跟到该文件,这可以看到$_GPC的定义

1
2
3
4
$_W = $_GPC = array();
...
$_GPC = array_merge($_GET, $_POST, $_GPC);
$_GPC = ihtmlspecialchars($_GPC);

所以之前的icon是通过表单提交的,接着看到最后的部分

1
2
3
$controller = $_GPC['c'];
$action = $_GPC['a'];
$do = $_GPC['do'];

这里定义了controller\action\do,回到index.php,看到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$controllers = array();
$handle = opendir(IA_ROOT . '/web/source/');
if(!empty($handle)) {
while($dir = readdir($handle)) {
if($dir != '.' && $dir != '..') {
$controllers[] = $dir;
}
}
}
if(!in_array($controller, $controllers)) {
$controller = 'account';
}
...
$actions = array();
$handle = opendir(IA_ROOT . '/web/source/' . $controller);
if(!empty($handle)) {
while($dir = readdir($handle)) {
if($dir != '.' && $dir != '..' && strexists($dir, '.ctrl.php')) {
$dir = str_replace('.ctrl.php', '', $dir);
$actions[] = $dir;
}
}
}

这样就能直接调用/web/site/category.ctrl.php中的delete方法了,从而造成任意文件删除.


0x02

漏洞利用

首先注册一个账号,添加一个公众号,选择暂不接入,接着管理公众号


接着到分类设置

已经到达我们需要的路由了

添加一个分类,接着就是对if判断的绕过了,提交就不看了,看到if($isnav)

所以这里我们选择,接着是if ($data['icontype'] == 1)

这里选择自定义上传进行绕过,同时那个上传图标的地方填写文件路径即可,选择提交

提交前

点击删除

删除之后


0x03

One’storm

Logic:把要删除文件的路径写入数据库->再利用函数进行删除路径文件

诶…和二次注入好像…

(ง •_•)ง