論PHP常見的漏洞

wyzsk發表於2020-08-19
作者: ′ 雨。 · 2015/01/14 10:08

0x00 前言


裡面很多都是像laterain學習到的, 如果能考上cuit的話 自動獻菊花了。

0x01 安裝的問題


首先拿到一份原始碼 肯定是先install上。 而在安裝檔案上又會經常出現問題。

一般的安裝檔案在安裝完成後 基本上都不會自動刪除這個安裝的檔案 我遇到過的會自動刪除的好像也就qibocms了。

其他的基本都是透過生成一個lock檔案 來判斷程式是否安裝過了 如果存在這個lock檔案了 就會退出了。 這裡首先 先來說一下安裝檔案經常出現的問題。

根本無驗證。


這種的雖然不多 但是有時還是會遇到個。 在安裝完成後 並不會自動刪除檔案 又不會生成lock來判斷是否安裝過了。 導致了可以直接重灌過

例子: WooYun: PHPSHE B2C 重灌。

安裝file


因為install 一般都會有step 步驟啥的。。 Step 1 check 啥啥 step 2 是安裝啥的。 而一些cms 預設step是1 而step又是GET 來的 而他check lock的時候就是在step1裡面。 這時候如果我們直接用GET提交step 2 那麼就直接進入下一步了 就沒check lock了。

例如某cms中的安裝檔案

#!php
if (empty ($step))
{
    $step = 1;//當使用者沒有提交step的時候 賦值為1
}
require_once ("includes/inc_install.php");
$gototime = 2000;

/*------------------------
顯示協議檔案
------------------------*/
if ($step == 1) //當1才檢測lock
{
    if (file_exists('installed.txt'))
    {
        echo '<html>
        <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        </head>
        <body>
        你已經安裝過該系統,如果想重新安裝,請先刪除install目錄下的 installed.txt 檔案,然後再安裝。
        </body>
        </html>';
        exit;
    }
    include_once ("./templates/s1.html");
    exit ();
}

/*------------------------
測試環境要求
------------------------*/
else
    if ($step == 2) // 我們直接提交step為2 就不check lock了
    { 
        $phpv = @ phpversion();
        $sp_os = $_ENV["OS"];
        $sp_gd = @ gdversion();
        $sp_server = $_SERVER["SERVER_SOFTWARE"];
        $sp_host = (empty ($_SERVER["SERVER_ADDR"]) ? $_SERVER["SERVER_HOST"] : $_SERVER["SERVER_ADDR"]);
        $sp_name = $_SERVER["SERVER_NAME"];
        $sp_max_execution_time = ini_get('max_execution_time');
        $sp_allow_reference = (ini_get('allow_call_time_pass_reference') ? '<font color=green>[√]On</font>' : '<font color=red>[×]Off</font>');
        $sp_allow_url_fopen = (in

變數覆蓋導致重灌


#!php
header("Content-Type: text/html; charset={$lang}");
    
foreach(Array('_GET','_POST','_COOKIE') as $_request){
    
    foreach($$_request as $_k => $_v) ${$_k} = _runmagicquotes($_v);
    
}
    
function _runmagicquotes(&$svar){
    
    if(!get_magic_quotes_gpc()){
    
        if( is_array($svar) ){
    
            foreach($svar as $_k => $_v) $svar[$_k] = _runmagicquotes($_v);
    
        }else{
    
            $svar = addslashes($svar);
    
        }
    
    }
    
    return $svar;
    
}
    
if(file_exists($insLockfile)){
    
    exit(" 程式已執行安裝,如果你確定要重新安裝,請先從FTP中刪除 install/install_lock.txt!");
    
}

foreach($$_request as $_k => $_v) ${$_k} = _runmagicquotes($_v);

這裡是一個經常遇到的一個變數覆蓋。

導致了我們可以覆蓋掉$insLockfile 從而讓file_exists 為false就不會退出了。導致再次重灌。 這個變數覆蓋不知道咋的 能在一些小cms的安裝檔案裡看到。

之前看的xdcms 和 frcms 都存在這個變數覆蓋。

例子: WooYun: frcms 重灌系統

判斷Lock後 無exit的。


這個從早期的phpdisk 的那個 header bypass 到現在的又遇到各種。

很久前的phpdisk的安裝檔案中。

判斷是否存在lock檔案 如果存在lock檔案了 就會header到index.php

但是header 後 他並沒有exit 所以並不會退出 導致了又是一個重灌。

跟這種類似的還有javascript 彈個框 啥的 也沒exit的。

例子: WooYun: 開源輕論壇StartBBS前臺getshell

例子: WooYun: FengCMS 修復不當導致getshell

解析漏洞


這個也比較少, 就隨便說句。 就是像dedecms很久以前的那樣 在安裝完成後會在install.php rename 為 Install.php.bak 但是由於apache的解析漏洞 如果無法識別最後的一個字尾的話 就會向上解析,那麼就又成php了。 然後又結合dedecms安裝時的變數覆蓋 又成重灌了。

滿足一些條件不會退出的。


這種例子也不算太多, 自己好像也沒遇到過太多。

首先以之前發過的sitestar舉例下

#!php
if(file_exists($lockfile) && ($_a=='template' || $_a=='setting' || $_a=='check')) {
    
        exit('please delete install.lock!');
    
}

這裡我們來理解一下這個邏輯, 這裡的file_exists($lockfile) 因為安裝成功後 lockfile 肯定存在的 所以這裡肯定會是true 然後再看一下 這裡是一個 && true true 才會進入語句塊。 那麼如果$_a 不為 template 、 setting 、 check 的話 那麼後面的就為false True and false => false就不會進入這個語句塊 就不會exit 再配合後面的

#!php
else if($_a=="create"){
    
    $link = mysql_connect($db_host,$db_user,$db_pwd);

剛好有個其他的 如果$_a 為 create 那麼就不會退出這個指令碼

剛好這個create 能達到Getshell的效果

例子: WooYun: 建站之星Sitestar前臺Getshell一枚

剩下的還有hdwiki之前也有一個基本差不多這樣的例子

#!php
if (file_exists(HDWIKI_ROOT.'/data/install.lock') && $step != '8') {
    echo "<font color='red'>{$lang['tipAlreadyInstall']}</font>";
    exit();
}

如果step為8的話 那麼就不會執行exit了。

#!php
case 8:
                require_once HDWIKI_ROOT.'/config.php';
                require_once HDWIKI_ROOT.'/lib/hddb.class.php';
                require_once HDWIKI_ROOT.'/lib/util.class.php';
                require_once HDWIKI_ROOT.'/lib/string.class.php';

                $db = new hddb(DB_HOST, DB_USER, DB_PW, DB_NAME, DB_CHARSET);
                //install 
                $setting=$db->result_first('select `value` from '.DB_TABLEPRE.'setting WHERE `variable` = \'site_appkey\'');
                if ($setting){
                    echo "<span style='font-size:20px;'>百科聯盟開通成功.</span><a href='../'>進入首頁</a>";
                    break;
                }

                //update info
                $data = $_GET['info'];
                $data = str_replace(' ', '+', $data);
                $info = base64_decode($data);

                if ($info){
                    $obj = unserialize($info);
                    if(is_array($obj)){
                        $url2 = 'http://localhost/count2/in.php?action=update&sitedomain='.$_SERVER['SERVER_NAME'].'&info='.$data;
                        $data = util::hfopen($url2);
                        //if gbk then toutf8
                        if ($lang['commonCharset'] == 'GBK'){
                            $obj['sitenick'] = string::hiconv($obj['sitenick'], 'gbk', 'utf-8');

剛好這裡step 8 又能執行一些特殊的操作。。 現在就把case 8 註釋掉了。

這裡程式碼我就不復制過了 免得佔篇幅。

這裡差不多是我比較常遇到的一些安裝檔案經常遇到的問題了,突然想也想不到其他啥的了。

0x02 包含漏洞


這裡再來談一下包含

其實包含也並沒有什麼好說的。

包含一般也就分為LFI RFI local file inclusion 和 remote嘛

對於LFI的話 因為很多都限制了包含的字尾結尾必須為.php Include ($a.'.php') 例如這種的

所以我們想包含我們的圖片馬兒的話 那麼就需要截斷後面的這.php

1: 00截斷 需要gpc off && php<5.3.4 2: 長檔名截斷 反正這個我很少成功。 3: 轉換字符集造成的截斷 這個對包含的話基本用不上。上傳的話 就是felixk3y牛發的那個轉換字符集造成的上傳截斷那個。

還有一些cms限制包含的字尾必須為.php的時候用的是擷取字元判斷是不是.php 例如下面一段簡單的程式碼

#!php
$include_file=$_GET[include_file];
if ( isset( $include_file ) && strtolower( substr( $include_file, -4 ) ) == ".php" )
        {    
                require( $include_file );
        }

對傳遞過來的擷取了後面4個字元 判斷是不是.php 如果是.php才進行包含。

這裡可以用zip(或者phar)協議嘛(當然這個也是找laterain學的 哈哈)。

首先新建一個1.php 裡面隨便寫個phpinfo把

然後壓縮成.zip 然後把zip的名字改成 yu.jpg

然後把這個.jpg上傳上去 然後包含

enter image description here

對於一些LFI 找不到上傳圖片的地方的話 也有很多牛發過了一些不能上傳圖片LFI的技巧 各種包含日誌 環境變數啥的 這裡我就也不多說了。

下面再來說RFI

如果能RFI的話 那麼就是最方便的了。

包含遠端檔案 或者又是php://input data啥的 各種偽協議。

但是也都知道RFI最大的限制條件就是需要allow_url_include on

且 變數前未定義路徑 或者 常量。

Allow_url_include 預設都是off

那麼無論是allow_url_include on 還是 變數前無路徑 或者 常量

那都是rfi的硬傷。

這裡介紹一種在allow_url_include off的情況下也能rfi的

但是成功率也並不太高。

首先在php.ini裡看一下allow_url_include

; Whether to allow include/require to open URLs (like http:// or ftp://) as files.
allow_url_include = Off

翻譯一下,允許包含url 例如 http:// ftp:// 之類的協議。

當off的時候肯定就是不允許去包含這樣的協議。

這裡我們先來測試一下

#!php
<?php
include($_GET[yu]); 

首先 allow_url_include && allow_url_fopen 都為on的時候

enter image description here

成功RFI。

然後 allow_url_include 為 on allow_url_fopen 為off

enter image description here

直接包含遠端檔案失敗 這時候我們用一下偽協議試試。

enter image description here

再次成功rfi。

當allow_url_include && allow_url_fopen 為off的時候。

enter image description here

偽協議失敗。

包含檔案

enter image description here

URL file-access is disabled in the server configuration 不允許包含。

肯定還有不少人記得很久以前的那個星外無可執行目錄的時候

利用遠端呼叫cmd繼續提權

那個利用的是共享檔案 然後在星外主機上來執行。

那麼這裡我們也試試

enter image description here

包含共享檔案成功。 這裡只本地測試了 沒具體測試。

但是由於445的原因 可能基本都失敗。

0x03 注入


下面來說一下注入。 這裡談的是mysql。 注入大概也就是把使用者可控的一些變數, 帶入到了資料庫的各種操作當中且沒有做好很好的過濾。 比如註冊使用者的時候檢測使用者名稱是否存在的時候,把使用者提交的使用者名稱拿到資料庫中去查詢。 查詢是否存在這個使用者名稱, 如果這裡對使用者名稱沒有做好過濾的話 那麼使用者就可以提交一些特殊字元來注入了。

現在注入的主要原因是 很多程式設計師在寫sql語句的時候 還是搞的語句拼接。

一些用了預編譯或者是在查詢的函式中再來過濾 很多時候就給跪了。

select update insert delete

因為mysql query 並不能執行多行語句, 除非pdo啥的能多行 所以不能像mssql那樣 還能在select後執行個update管理的語句。

對於這四種型別的注入一般的語句的構造也不同。

如果有mysql error的話

那麼這四種就都能用報錯注入 這種是比較方便的

如果沒mysql error的話

Select 的注入 一般是用union select 如果把對資料庫中的查詢結果展示出來的話那麼就能直接出資料了。 如果無回顯的話 那麼當然就是盲注了。

Update的注入 如果是在update set的位置的話 那麼我們可以找找這個表的哪個column會被展示出來 例如如果一個update的注入點是在使用者表且是在set位置可控的話 那麼我們可以update email這個column 然後去使用者資料看一下自己的email就出資料了 語句例如 update table set email=(select user()) 如果是在where後的話 那麼一般也就是盲注了。

Insert 的注入 也是一般是透過找哪個column會不會顯示出來 儘量把要出的資料插入到這個column裡面去。 如果沒顯示的話 也是盲注。

Delete的注入 一般都是盲注了。

數字型注入主要就是因為他的變數並沒有用單引號引住。

但是基本上都是被強制型別轉換了 intval啥的。

但是有時候會有遺漏的嘛。

而字元型和搜尋型的 都是會有單引號引住的。

所以需要閉合單引號再來進行注入。

說到單引號不得不說個php.ini裡的配置

Magic_quotes_gpc 在稍微高點的版本預設都是on

但是卻在應該是5.4就已經廢除了。

從字面意思上來看 就是對GPC QUOTE嘛

GPC 對應的就是GET POST COOKIE

會被轉義的字元為 ' “ \ NULL 會在前面新增上一個轉義符。

導致了失去本來的意義 無法閉合單引號進行注入。

(1) 全域性沒有做addslashes的

像這種全域性沒有對GET POST COOKIE 做addslashes的 這種廠商基本是會在查詢的時候 再對一些使用者可控的變數進行addslashes 甚至是不進行addslashes 直接帶入查詢的。

這樣的就算在查詢的時候進行addslashes 在很多時候也都能找到幾處遺漏了addslashes的。 這種的比較簡單 不多說。

(2) 全域性做addslashes

現在稍微好一點的廠商都知道了在全域性檔案中對 GET POST COOKIE 做addslashes (甚至是在帶入查詢的函式中再做了轉義或者預編譯 這種給跪) 所以基本不用擔心哪裡遺漏了哪裡忘記了addslashes) 這種的基本是首先先get magic quotes gpc 判斷gpc是否開啟 如果沒開啟的話 再呼叫addslashes來轉義 。 如果開啟的話 就不用來addslashes了。 沒開啟就addslashes.

這裡主要講的就是這種型別的注入的一些常見的

寬位元組注入


這個是一個老生常談的問題, 從一開始的資料庫字符集GBK的寬位元組注入 到現在也有很久了。

但是並不是字符集為GBK的就能寬位元組注入。

總有一些小夥伴說咋我看的cms 字符集是gbk的 但是咋不能寬位元組呢?

這是因為資料庫的連線方式不同

Set names gbk 這樣的就能寬位元組

但是現在這樣的基本都看不到了。 因為基本都是設定了二進位制讀取了。

Binary。

這樣的寬位元組基本沒了, 卻有了另外一種。

因為轉換字符集造成的寬位元組注入

從utf8轉到gbk 或者從gbk轉到 utf8啥的。

例子: WooYun: 74cms 最新版 注入8-9

錦 從UTF8 轉成 GBK之後成了 %e5%5c
74cms對GET POST COOKIE …… 都做了addslashes
所以' 轉義後為\'
->%5C %e5%5c%5c' 兩個\ 則單引號出來

例子2: WooYun: qibocms 下載系統SQL隱碼攻擊一枚(官網可重現)

解碼導致


因為在全域性檔案中addslashes

如果我們能找到一些解碼的 例如urldecode base64_decode的

那麼我們先提交encode之後的 那麼就能不被轉義了。

然後decode後 再帶入查詢 造成了注入 無視gpc。

這種的很常見。

例子很多 隨便找一個

例子: WooYun: qibocms B2b 注入一枚 //qibocms 注入

例子: WooYun: phpdisk V7 sql注入2 //phpdisk 注入

變數覆蓋


常見的變數覆蓋 有啥extract 和 parse_str 函式啥的

當然還有$$

變數覆蓋得結合一些具體的場景了。

例如extract($_POST)啥的 直接從POST陣列中取出變數

這樣的還是遇到過幾個 然後覆蓋掉之前的一些變數。

覆蓋的話 一般是覆蓋掉表字首之類的

Select * from $pre_admin where xxx 像這種的就覆蓋掉$pre

然後直接補全語句然後注入。

例子: WooYun: qibocms分類注入一枚可提升自己為管理

例子2: WooYun: phpmps 注入一枚

當然 $$ 也挺經常用到的 這個例子很不錯。

例子3: WooYun: MetInfo最新版(5.2.4)一處SQL盲注漏洞

一些replace造成的


一些cms中 總有一些逗比過濾函式

會把’ 啥的 replace 成空

但是他似乎忘記了自己全域性有轉義?

使用者提交一個' 全域性轉義成\' 然後這過濾函式又會把 ' replace 成空

那麼就留下了\ 導致可以吃掉一個單引號 是double query的話

Select * from c_admin where username=’admin\’ and email=’inject#’

這樣就可以注入了。

話說之前還遇到過一個廠商。。 之前提交了漏洞 是因為他會把

' " 都會替換成空 然後提交之後 他就去掉了' 就是不把' 替換成空了。

但是他似乎忘記了 " 也會被轉義。。 那麼提交一個 " 就又剩下了一個轉義符。

例子: WooYun: PHPCMS全版本通殺SQL隱碼攻擊漏洞

當然還有一些replace 是使用者可控的。就是說使用者可以想把啥提交成空就提交成空

例如很久前的cmseasy 和 ecshop的那個注入

例如這段程式碼

#!php
$order_sn = str_replace($_GET['subject'],'',$_GET['out_trade_no']);

這裡因為會被轉義 如果提交 ' 就成 \' 這裡可以看到

這裡清成空的 是我們get來的 那我們就想辦法把\ replace掉

但是如果我們GET提交把\ replace 那麼會被轉義 就是replace掉\

但是我們只是 \' 所以不能把\去掉 如果我有\ 還要你清空個毛啊。

這裡我們來理清一下思路。

Addslashes 會對' " \ NULL 轉義

' =>  \'
" => \"
\ => \\
NULL => \0

那這裡我們就提交 %00’ 就會被轉義生成 \0\' 這時候我們再提交把0替換成空 那麼就成了\' 單引號也就成功出來了。

例子: WooYun: cmseasy繞過補丁SQL隱碼攻擊一枚

SERVER 注入


因為在很多cms中 基本上都只是對GET POST COOKIE 進行addslashes

而沒有對SERVER進行轉義。

而一些SERVER的變數也是使用者可以控制的。

例如啥 QUERY_STRING X_FORWARDED_FOR CLIENT_IP HTTP_HOST ACCEPT_LANGUAGE 很多。

這裡最常見的當然也就是X_FORWARDED_FOR

這個一般是在ip函式中用到 如果後面沒有進行驗證ip是否合法的話就直接return 這個大部分時候都會導致注入。

例子1: WooYun: Phpyun注入漏洞二

這裡說到驗證ip 這裡基本都是用的正則來驗證是否合法。

而一些廠商連正則都寫錯。

例如在cmseasy中的驗證ip的正則中(%.+)

導致了後面可以寫任意字元。

例子2: WooYun: CmsEasy最新版本無限制SQL注射

最近自己在看douphp 裡面的驗證ip的正則自己也發現了一點小問題。

不過也就只是小問題而已。

Douphp中的獲取ip的函式。

#!php
function get_ip() {
        static $ip;
        if (isset($_SERVER)) {
            if (isset($_SERVER["HTTP_X_FORWARDED_FOR"])) {
                $ip = $_SERVER["HTTP_X_FORWARDED_FOR"];
            } else if (isset($_SERVER["HTTP_CLIENT_IP"])) {
                $ip = $_SERVER["HTTP_CLIENT_IP"];
            } else {
                $ip = $_SERVER["REMOTE_ADDR"];
            }
        } else {
            if (getenv("HTTP_X_FORWARDED_FOR")) {
                $ip = getenv("HTTP_X_FORWARDED_FOR");
            } else if (getenv("HTTP_CLIENT_IP")) {
                $ip = getenv("HTTP_CLIENT_IP");
            } else {
                $ip = getenv("REMOTE_ADDR");
            }
        }

        if (preg_match('/^(([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]).){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/', $ip)) {
            return $ip;
        } else {
            return '127.0.0.1';
        }
    }
}

來看看驗證ip是否合法的正則

#!php
preg_match('/^(([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]).){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/', $ip)

這裡我們仔細來看看 他這裡是準備匹配小數點 但是他直接寫成了.

都知道在正則中.表示的是匹配任意字元 除開換行符意外 但是在開啟/s 修正符以後 換行符也會匹配。

不過他這個.後面沒啥+或者?的 導致也就只能寫一個字元。

他這裡直接寫成了. 那在這裡我們就能引入單引號了。不過也就一個字元。

這裡的正確寫法應該是.

FILES注入。


也差不多 也是因為全域性只對COOKIE GET POST 轉義 遺漏了FILES 且不受gpc。

FILES 注入一般是因為上傳 會把上傳的名字帶到insert當中入庫。

然後這裡檔案的名字是我們可以控制的 所以導致了注入。

而這裡的上傳的名字是我們可以控制的。

例子: WooYun: qibocms 黃頁系統SQL隱碼攻擊一枚

還有一些 在入庫的時候才對檔案的名字進行了轉義 而在獲取字尾後 在入庫的時候對檔名轉義了卻沒有對字尾轉義也導致了注入

例子: WooYun: Supesite 前臺注入 #2 (Insert)

未初始化造成的注入


很久以前php<4.20的時候 為了方便 register_globals 預設都是on。

而到了後面 register_globals 的弊端也顯現了出來, 所以也在很久以前預設都是off了。

而到了現在, 很多cms 卻喜歡模仿register_globals 搞起了偽全域性機制。

例如啥qibocms metinfo destoon 啥的啊。

這樣是方便了不少, 但是如果哪裡遺漏了初始化 那麼就會導致注入了。

感覺這種的挺好玩的 多找了幾個例子。

例子: WooYun: qibocms地方門戶系統注入一個問題(demo測試)

例子: WooYun: qibocms地方門戶系統注入(多處類似,demo測試)

例子: WooYun: 齊博地方門戶系統SQL隱碼攻擊漏洞(無需登入可批次)

例子: WooYun: 齊博整站/地方門戶SQL隱碼攻擊漏洞

陣列中的key。


因為在對全域性轉義的時候

很多cms 都只是判斷gpc 是否開啟

如果off 就對陣列中的value就行addslashes

卻忘記了對陣列中的key進行轉義。

那麼這樣也導致了一個問題。 也就是在Gpc off的時候那麼陣列的key沒有被過濾 導致可以引入單引號。(聽說低版本的php對二維陣列中的key就算gpc on 也不會轉義)

如果哪裡把陣列中的key 讀取出來 然後把key帶入到了查詢當中

那麼也會造成安全問題。

而且這樣的例子很多。 簡直慘不忍睹。 例子: WooYun: qibocms V7 整站系統最新版SQL隱碼攻擊一枚 & 另外一處能引入轉義符的地方。 //陣列key的注入例子: WooYun: qibocms多個系統繞過補丁繼續注入2

例子: WooYun: qibocms全部開源系統 Getshell

例子: WooYun: Discuz 5.x 6.x 7.x 前臺SQL隱碼攻擊漏洞一枚

offset


這種算是比較常見的一種注入的。

程式碼大概如

#!php
<?php
$key=0;
$a=$_GET[a][$key];
$b=$_GET[b];
Mysql_query("select * from table where xxx='$a' and xx='$b'")

如果這裡$_GET[a] 提交的是一個陣列 且含有一個key為0的那麼$a就是對應的這個key的value

但是這裡並沒有強制要求為陣列。

那麼我們提交一個字串 那麼後面的[0] 那麼就是擷取的第一個字元

在全域性中 單引號被轉義為\' 擷取第一個字元就為了\

吃掉一個單引號 然後就在$b處寫入inject可以注入了。

例子: WooYun: qibocms 地方門戶系統 注入#4(demo測試)

還有map發的那Disucz 7.2的那注入也一樣。

第三方外掛


很常見的一種洞。

比較常見的uc 和 alipay tenpay chinabank 啥的

特別是uc 因為預設uc裡面都會striplashes

Uc的話 一般會遇到的問題是uckey預設的。

或者是uckey這個常量根本就沒有初始化。

導致了uckey可控 再導致了Getshell 或者 注入啥的。

還有tenpay 和 alipay 啥的 一些是因為忘記把過濾的檔案包含進來

且key預設是空的 導致可以透過驗證。

例子: WooYun: phpmps 注入 (可修改其他使用者密碼,官網成功) // phpmps uc致注入

例子: WooYun: PHPEMS (線上考試系統) 設計缺陷 Getshell一枚(官網已shell) /phpems uc致getshell

例子: WooYun: 最土團購注入一枚可直接提升自己為管理 & 無限刷錢。 //最土團購 chinabank致注入

例子: WooYun: Destoon Sql注入漏洞2(有條件) //destoon tenpay致注入

例子: WooYun: CSDJCMS程式舞曲最新版Sql 一枚 //csdj tenpay致注入

數字型注入


其實也不只是數字型 只是說一些忘記加單引號的地方都這樣。

只是一般數字型的都不會加單引號的。

一般的是

#!php
$id=$_GET[id];
Select * from table where id=$id;

$id 沒被單引號 且 沒有被強制型別轉換 那麼就算addslashes了 由於不需要去閉合單引號 所以也無影響。

例子: WooYun: qibocms 地方門戶系統 注入#3 (demo測試)

並不是一些數字型 一些其他的點也有些忘記加單引號 導致了注入。 例子: WooYun: Supesite 前臺注入 #3 (Delete)

這裡supesite的注入還涉及到了一個設計缺陷。
這裡把

#!php
$query = $_SGLOBAL['db']->query('SELECT * FROM '.tname('spacetags').' WHERE itemid=\''.$itemid.'\' AND status=\''.$status.'\'') 
    

$itemid 首先帶入到了查詢當中 是被單引號了的。。 如果查詢出來的有結果 才會帶入到delete中 如果無結果 就不執行delete的語句了。
而在資料庫中itemid中 儲存的是int型別 所以他這裡本意是想要使用者只能提交數字型才能查詢出結果。 如果不是提交的數字的話 那麼就查詢不出來結果 就不去執行下面的delete語句了。
但是由於mysql的型別轉換 因為他這裡儲存的是int型別   所以我們提交4xxxxx 跟我們提交4 是一樣的

#!php
$_SGLOBAL['db']->query('DELETE FROM '.tname('spacetags').' WHERE itemid='.$itemid.' AND tagid IN ('.simplode($deletetagidarr).') AND status=\''.$status.'\''); 

然後就執行這個delete語句 然後沒單引號 造成了注入。

例子: WooYun: phpyun v3.2 (20141226) 兩處注入。

這個phpyun的注入 主要是因為php是弱型別語言

一些廠商喜歡這樣寫

#!php
If ($a>1){
    Mysql_query(select id from table where id=$a)
}

他這個本來是想使用者提交數字才能透過這個判斷 但是由於弱語言 1+asd啥的 都能透過 所以又導致了注入。

二次注入


也是一種比較常見的注入。 涉及到的是入庫和出庫。 因為有全域性轉義 然後入庫的時候

#!php
Insert into table (username) values ('a\'');

這樣入庫後 轉義符就會消失 那麼就是a' 如果哪裡再把這個查詢出來 那麼也就是出庫的是a' 如果再把出庫的 再帶入到了查詢啥的 那麼就再次成功的引入了單引號導致了注入

例子: WooYun: phpyun v3.2 (20141226) 兩處注入。 例子: WooYun: qibocms 地方門戶系統 二次注入#5(demo測試) 例子: WooYun: 74cms (20140709) 二枚二次注入 例子: WooYun: Hdwiki最新版二次注入一枚

比較是硬傷的是 很多時候資料庫中儲存的長度是有限制的。 所以一些也不是太好利用。

查詢當中key可控


不知道也應不應該把這個歸為一類。

大概是因為一些查詢的時候 直接把$_POST啥的 直接帶入到了查詢函式當中

例如cmseasy的rec_insert的查詢函式中。

然後foreach key 出來 然後foreach 出來的key 做了查詢中的column

這種的防止方法一般是 把資料庫中的column查詢出來 然後in_array 判斷一下$_POST出來的key 是否在資料庫中的column中 下面兩個例子就是這樣修復的。

例子: WooYun: 雲人才系統SQL隱碼攻擊,繞過WAF 例子: WooYun: Cmseasy SQL注射漏洞之三

striplashes


有些cms 在全域性addslashes後 然後在後面的檔案中又stripslashes
去掉了轉義符 然後又可以閉合單引號了。

#!php
$_SESSION['flow_consignee'] = stripslashes_deep($consignee);

例子: http://www.2cto.com/Article/201301/182509.html //之前的ecshop注入 。

擷取字元導致的注入


有些cms 有的時候會限制使用者輸入的長度

所以只擷取一部分

例如uchome的cutstr($asd,32);

這樣只允許輸入32個字元 而且uchome裡面的這個也沒有像dz那樣擷取字元的後面加...

那麼如果我們提交一個1111111111111111111111111111111’

被轉義後成1111111111111111111111111111111\’

然後擷取32個字元 就是1111111111111111111111111111111\

如果又是double query的話 吃掉一個單引號 然後下一個連著的可控變數又可以注入了。

結果在uchome中找到了個能引入轉義符的 結果只有一個可控的。

例子: WooYun: Hdwiki (20141205) 存在7處SQL隱碼攻擊漏洞(含之前處理不當安全的漏洞) //裡面的0x06

繞過限制繼續註冊GLOBALS變數


不知道放哪。這個也放到注入板塊來把。。

其實就是這次的DZ6.X 7.X 那個任意程式碼執行的漏洞

#!php
if (isset($_REQUEST['GLOBALS']) OR isset($_FILES['GLOBALS'])) {
    exit('Request tainting attempted.');
}

foreach(array('_COOKIE', '_POST', '_GET') as $_request) {
    foreach($$_request as $_key => $_value) {
        $_key{0} != '_' && $$_key = daddslashes($_value);
    }
}

主要關鍵程式碼就上面這兩段。 這裡把GET POST COOKIE 迴圈出來 然後註冊一個變數 但是 這裡不允許建立GLOBALS變數 然後DZ7.X 就是用這樣處理的 如果設定了REQUEST 的 GLOBALS

就直接退出

這段程式碼在很久以前確實是沒什麼問題

因為那時候的request order 還是gpc

但是在php 5.3 以後 request order 預設成了gp

也就是成了get 和 Post 不包含cookie了。

所以 $_REQUEST裡面就不包含COOKIE提交來的了。

而且這後面也把COOKIE迴圈出來 註冊變數

所以這裡我們在COOKIE裡面提交GLOBALS 就不會被檢測出來了。

而且也成功註冊了GLOBALS變數。

所以在結合後面的一些些程式碼就造成了程式碼執行。

例子: WooYun: Discuz!某兩個版本前臺產品命令執行(無需登入)

以上就差不多是我經常所遇到的注入問題 好像暫時也想不到其他什麼的了

0x04 找回密碼出現的問題。


下面介紹一些我在cms遇到的找回密碼時候犯得錯誤。

找回密碼很多都是驗證的token 就是在找回密碼的時候生成一個token 然後儲存到資料庫中。 然後把找回密碼的地址發到郵箱中 url中就含有token 由使用者點開後就能修改密碼 基本就是驗證的這個token。 其實一般的可以找回任意使用者密碼的原因就是弱token 導致可以被攻擊者搞到。 包括很多廠商驗證的時候就是四位純數字啥的。 可以列舉。 當然也可以延伸一下, 一些cms的密碼加密方式很難破掉。 有時候我們拿到了管理的密碼破不掉也是雞肋。 所以有時候也可以利用這種方法 一般找回密碼是用的郵箱 首先我們可以注入把管理的郵箱注入出來 然後再去找回密碼 再把資料庫的token注入出來 再構造一下地址 就能重置密碼。 這個給我印象比較深的是 在ssctf的比賽中嘛 當時機油問了問我 那wordpress那題 有個外掛的注入 然後因為都知道wp的加密基本很難破。 所以也是用的這種方法。 因為一般都是弱token的問題 隨便找幾個例子了

rand 函式生成的token


#!php
$resetpwd = md5(rand());

可以看到這個生成的token 就是對rand()函式生成出來的數字進行md5一次

來看一下rand()

註釋:在某些平臺下(例如 Windows)RAND_MAX 只有 32768。如果需要的範圍大於 32768,那麼指定 min 和 max 引數就可以生成大於 RAND_MAX 的數了,或者考慮用 mt_rand() 來替代它。 如果不指定一些引數的話 那麼最大值才32768 一個並不算大的值 那麼我們首先對這32768種可能 md5出來一個列表 然後我們直接列舉這32768種可能 總會有一個對的。

例子: WooYun: Thinksaas找回密碼處設計錯誤利用賬戶可找回密碼。

修改hdwiki任意使用者密碼


#!php
$encryptstring=md5($this->time.$verification.$auth);

補丁後 多了一個$auth

$timetemp=date("Y-m-d H:i:s",$this->time);

$auth = util::strcode($timetemp, 'ENCODE');
 可以$auch 是對時間來了一個演算法。 結果這個演算法的KEY並沒有初始化 導致瞭如果我們知道了這個時間 就可以自己生成出來加密的字串 這裡帶入演算法的是時間 這裡是我們可以知道的。

例子: WooYun: Hdwiki設計缺陷知郵箱可改密碼(包括管理員) //繞過補丁繼續找回hdwiki任意使用者密碼

0x05 上傳


這個上傳就大概說說。

一般的上傳漏洞可能是未驗證上傳字尾 或者是驗證上傳字尾被bypass 或者是上傳的檔案驗證了上傳字尾但是檔名不重新命名。

對於那些驗證了字尾但是檔名不重新命名的

一般可以試試截斷yu.php%00.jpg 當然%00 要urldecode

當然 畢竟截斷雞肋了。 上面提到過限制條件了。

還可以是結合各種webserver的解析漏洞

例如iis6的 xx.asp/yu.jpg yu.php;.jpg yu.asp;.jpg aspx 當然不能這樣解析了。

如果不重新命名的就上傳這樣就行了。

Nginx的低版本解析漏洞: yu.jpg/1.php 對於這種直接上傳一個xxxx.jpg 再在這後面加上各種/.php 試試的

Apache解析漏洞 yu.php.xxx 在最後一個字尾識別不出來的時候 那麼就向上解析

最終解析成.php

像phpweb後臺那個上傳漏洞。很多人遇到apache的時候

無法截斷的時候就上傳一個yu.php.jpg 有些人比較疑問的是為啥有時候成功有時候失敗。

這個主要是看os 像windows的話 .jpg 就直接是圖片了

所以在windows下 就直接識別成圖片了 而不是.php

而在linux下 .jpg不被識別 就向上識別成.php

這些解析漏洞在上傳中也挺經常遇到的。

上傳的驗證一般是 MIME、客戶端的JS驗證、白名單、黑名單。

前面兩種都比較簡單。

白名單就是允許使用者上傳哪些字尾的。 黑名單就是禁止使用者上傳哪些字尾的。

這兩種相比來說一般是黑名單容易bypass一點。 黑名單的繞過還是得具體看他黑名單的程式碼。 有的直接大小寫就過。 有些沒對檔名trim的 直接在檔名後面加空格。 Windows下的 檔名後%81-%99 decode後的 或者是windows下的特性 .php::$data 這樣上傳上去依舊是.php

其實上傳還挺重要的。。 但是我又不知道說哪些。 還是具體看程式碼把。

任意檔案操作


這個主要是涉及到的是 任意檔案刪除 任意檔案複製 任意檔案重新命名 任意檔案移動 任意檔案下載……。 因為像現在的cms很多都自帶得有加密 解密 函式 例如qibocms的mymd5 Dz的authcode 啥的。 對於這些任意檔案操作的 首先可以試試拿到配置檔案中的資料庫的連線帳號和密碼 嘗試外聯一下 但是很多時候都是隻允許本地連的 很多時候不好利用的時候可以利用拿到配置檔案 然後拿到這些函式的key 然後自己生成一個加密的字串 然後再結合具體的程式碼進行最大化的利用。

對於任意檔案刪除


一般是挺不好利用的,還是結合具體的場景,有些因為全域性的過濾而不能注入的,可以嘗試用任意檔案刪除,刪掉這個檔案,再進行注入 一般的利用還是透過刪除安裝檔案生成的lock檔案,然後達到重灌。

不過這樣弊很大。

例子: WooYun: phpyun (20141230) 任意檔案刪除致注入可改任意使用者密碼(4處打包)

任意檔案複製 / 任意檔案移動 / 任意檔案重新命名


複製的話 肯定涉及到了 要複製的檔案 要複製到的路徑。

如果是要複製的檔案可控 要複製到的路徑不可控的話 例如qibocms之前的一個洞

#!php
copy(ROOT_PATH."$webdb[updir]/$value",ROOT_PATH."$webdb[updir]/{$value}.jpg");
    

這裡$value 是可控的 但是又不能截斷 複製到的路徑限制了.jpg結尾。

這時候我們就可以把$value控制為 儲存了qibocms的加密函式的key的配置檔案

然後複製後 成了一個.jpg 那我們就可以直接開啟 看到key了

例子: WooYun: Qibocms圖片系統任意檔案檢視導致的多處注入(可提升自己為管理員)

如果兩個都完全可控的話 那肯定是直接把自己的圖片複製成一個.php馬兒了。

任意檔案下載


其實跟上面複製差不多, 很多時候也是透過下載配置檔案 拿到key。 再進行各種操作。。

例子: WooYun: qibocmsV7整站系統任意檔案下載導致無限制注入多處(可提升自己為管理 Demo演示)

這個例子還涉及到了一個win的特性bypass 黑名單

0x06 加密函式問題。


這種問題主要是想進各種辦法把這些加密函式的key拿到 或者想辦法加密一些特殊字元然後拿到加密的字串

加密函式肯定就涉及到了各種演算法。

加密可逆


演算法問題一般主要是因為一些弱演算法 導致了 知道明文 知道密文 可逆

拿到加密函式中的key 從而再自己生成一個自己想要的加密字串。

再結合具體的點 然後進行具體的利用。

例子: WooYun: DedeCMS-V5.7-SP1(2014-07-25)sql注入+新繞過思路 例子: WooYun: phpcms最新版繞過全域性防禦暴力注入(官網演示)

加密可控


還有的一類算是 一個點 要加密的是我們可控的 而且密文會輸出 而且這個可控的點能引入特殊字元 那麼我們就把一些特殊字元帶入到這裡面 然後拿到密文 再找到一處decode後會進行特殊操作的點 然後進行各種操作。

例子: WooYun: 程氏舞曲CMS某洩露,導致sql注入

例子: WooYun: PHPCMS最新版(V9)SQL隱碼攻擊一枚

key洩漏

例子: WooYun: 一個PHPWIND可拿shell的高危漏洞

例子: WooYun: PHPCMS V9 一個為所欲為的漏洞

0x07 後記


寥寥草草的把這篇文章寫完了。

比自己預期想的少寫了很多, 因為在一開始寫的時候還是挺有感覺的。

因為讀書一個月也才放一次假, 都是抽時間在慢慢寫著。

後面差不多寫了1W字的時候,存稿竟然丟了, 弄了半天也沒找回

就感覺不想寫了, 後面又翻了翻 找到了一篇自己之前儲存的寫了差不多兩三千字的

然後就再慢慢的開始寫了, 也就草草的結束了。

當然這裡只是總結了一些常見的型別, 肯定在實戰中會遇到各種各樣的情況 各種過濾啥的。

各種邏輯錯誤需要自己慢慢去體會了。

本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!

相關文章