uploads-lab靶場闖關
0x01 Pass-01 前端檢測
源代碼
function checkFile() {
//這里getElementsByName取name的值,,判斷表單里是否選擇了文件
var file = document.getElementsByName('upload_file')[0].value;
if (file == null || file == "") {
alert("請選擇要上傳的文件!");
return false;
}
//定義允許上傳的文件類型
var allow_ext = ".jpg|.png|.gif";
//提取上傳文件的類型
// substring()表示返回一個從里面的參數位置開始到末尾的字符串
// lastIndexOf()表示 返回'.'首次出現的位置,如果未發現此字符串返回 -1
var ext_name = file.substring(file.lastIndexOf("."));
//判斷上傳文件類型是否允許上傳
if (allow_ext.indexOf(ext_name + "|") == -1) {
var errMsg = "該文件不允許上傳,請上傳" + allow_ext + "類型的文件,當前文件類型為:" + ext_name;
alert(errMsg);
return false;
}
}
Get到javascript的知識點:
onsubmit="return false;" 將無論何時都阻止表單的提交
onsubmit="return check();" 是否提交表單取決於check()的返回值
onsubmit="check();" check()的返回值無影響
null:代表聲明了一個空對象,不是一個字符串,可以賦給任何對象,是沒有地址。
"": 代表聲明了一個對象實例,這個對象實例的值是一個長度為0的空字符串,是有地址但是里面的內容是空的。
繞過方法
-
[ ] 在控制台修改前端js代碼,在allow_ext中 添加 '.php' 即可繞過
-
[ ] 在前端界面審查元素處,刪除檢測上傳的js函數,或者瀏覽器禁止js運行
-
[ ] 上傳shell.jpg,用burpsuit抓包,修改后綴為shell.php 即可繞過
0x02 Pass-02 MIME類型 檢測
源代碼
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) { //upload_file 已經在別的文件里定義好了,表示'../upload/'
if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif')) {
$temp_file = $_FILES['upload_file']['tmp_name'];//獲取臨時文件名
$img_path = UPLOAD_PATH . '/' . $_FILES['upload_file']['name'] //上傳目錄+上傳時的文件名
if (move_uploaded_file($temp_file, $img_path)) {//把臨時文件移到UPLOAD_PATH目錄
$is_upload = true;
} else {
$msg = '上傳出錯!';
}
} else {
$msg = '文件類型不正確,請重新上傳!';
}
} else {
$msg = UPLOAD_PATH.'文件夾不存在,請手工創建!';
}
}
將content-type修改為 image/jpeg
image/png
image/gif
其中一個即可
0x03 Pass-03
源碼
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array('.asp','.aspx','.php','.jsp');
$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); //收尾去空
if(!in_array($file_ext, $deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
if (move_uploaded_file($temp_file,$img_path)) {
$is_upload = true;
} else {
$msg = '上傳出錯!';
}
} else {
$msg = '不允許上傳.asp,.aspx,.php,.jsp后綴文件!';
}
} else {
$msg = UPLOAD_PATH . '文件夾不存在,請手工創建!';
}
}
- [x] 閱讀源碼發現,通過自定義的deldot函數進行末尾去點處理,如果沒有對末尾的點進行處理,則可以利用windows特性上傳后自動去除文件后綴名的點
通過源碼可以發現是 檢測黑名單后綴,所以這里可以通過將后綴改為.php3 .phtml
GET Apache 相關知識點
.htaccess
一般來說,配置文件的作用范圍都是全局的,但 Apache 提供了一種很方便的、可作用於當前目錄及其子目錄的配置文件—— .htaccess
(分布式配置文件)
要想使 .htaccess
文件生效,需要兩個條件:
1. Apache的配置文件中,要寫上: AllowOverride All
2. Apache要加載mod_Rewrite模塊。加載該模塊,需要在Apache的配置文件中寫上:LoadModule rewrite_module /usr/lib/apache2/modules/mod_rewrite.so
.htaccess文件內容:
AddType application/x-httpd-php xxx
<FilesMatch "shell.jpg">
SetHandler application/x-httpd-php
</FilesMatch>
- httpd.conf
如果想要apache可以將xxx的后綴當作php來執行 .php3 .phtml .xxx等后綴,需要在apache下的httpd.conf配置:
AddType application/x-httpd-php .php3 .phtml
這時,服務器會將php3, phtml
后綴的文件當成php
解析。
- mime.types
有一個名為mime.types的文件,其中記錄着Apache認識的后綴
可以修改mime.types的配置 讓apache 將不常用的后綴解析為php
application/x-httpd-php .php .php3 .phtml
GET windows特性知識點
測試結果
可以成功將phtml文件解析
通過重寫.htacess文件 來繞過黑名單(出現點小問題)
由於黑名單沒有過濾掉 .htaccess
,所以我們能夠上傳.htaccess
文件導致其后上傳的其他文件能夠解析成 php,從而拿到shell ,
問題就在這里出現了,發現上傳后的 .htaccess被改寫為 2019***.htaccess,所以這里並不能直接上傳htaccess
發現源碼使用隨機數對文件名進行重寫
0x04 Pass-04 (.htaccess)繞過
源碼
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2","php1",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2","pHp1",".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");
$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); //收尾去空
if (!in_array($file_ext, $deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上傳出錯!';
}
} else {
$msg = '此文件不允許上傳!';
}
} else {
$msg = UPLOAD_PATH . '文件夾不存在,請手工創建!';
}
}
?>
黑名單過濾了一堆后綴名,但唯獨沒有過濾 .htaccess
於是上傳一個 .htaccess文件,內容:
表示將所有文件名包含jpg的,都作為php解析
<FilesMatch "jpg">
SetHandler application/x-httpd-php
</FilesMatch>
上傳成功
再上傳一個shell.jpg看能否執行
可以正常執行,說明 .htaccess 成功上傳
方法二:
因為apache 由右向左👈解析的特性, 因此可以上傳一個它不認識的后綴來繞過黑名單檢測,比如 shell.php.aaaaaa ,這樣可以繞過黑名單后綴讓文件成功上傳,又因為apache的特性,可以成功以php來解析
0x05 Pass-05 后綴名大小寫繞過
查看源碼可以知道是 根據黑名單后綴名檢測,且沒有對大小寫進行統一轉換,所有這里可以利用 pHP 等來繞過
0x06 Pass-06 空格繞過黑名單
源碼
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$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");
$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
if (!in_array($file_ext, $deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
if (move_uploaded_file($temp_file,$img_path)) {
$is_upload = true;
} else {
$msg = '上傳出錯!';
}
} else {
$msg = '此文件不允許上傳';
}
} else {
$msg = UPLOAD_PATH . '文件夾不存在,請手工創建!';
}
}
通過源碼發現,和前面幾關相比,這關並沒有對上傳的文件名進行首位去除空白字符(或者其他字符)
所以可以利用windows的特性,末尾加空格后會自動去掉空格
於是上傳 'shell.php ',這個后綴名剛好可以繞過黑名單,並且上傳上去空格自動去掉,可以被解析
另外的思路
看到一篇博客上的作者說,可以用 'shell.php. ' 繞過,但是我分析之后發現是不行的,因為這里的源碼會將上傳文件重命名,首先它會將最后一個'.' 到 末尾的字符串 認為是后綴名, '.'的前面的字符會生成隨機數,所以最后上傳上去的文件應該是 'xxxx. ' ,又因為windows特性,所以 文件變成 'xxxx'
0x07 Pass-07 (沒有對后綴名末尾去除.)
閱讀源碼發現,沒有刪除文件名末尾的點,所以這里又是利用windows的特性
上傳 shell.php.
別的思路
- 發現沒有對文件進行沖命名,所以這里可以上傳 '.htaccess.' 'shell.php.xxxxxxx'
0x08 Pass-08(利用windows NTFS $DATA特性繞過)
源碼
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$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");
$file_name = trim($_FILES['upload_file']['name']);
$file_name = deldot($file_name);//刪除文件名末尾的點
$file_ext = strrchr($file_name, '.');
$file_ext = strtolower($file_ext); //轉換為小寫
$file_ext = trim($file_ext); //首尾去空
if (!in_array($file_ext, $deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上傳出錯!';
}
} else {
$msg = '此文件類型不允許上傳!';
}
} else {
$msg = UPLOAD_PATH . '文件夾不存在,請手工創建!';
}
}
閱讀源碼可以看到,基本過濾了所以可以解析的后綴名, 前幾關的幾個windows小特性也不能利用,對'.' 空格 d等 做了過濾
GET知識點 Windows NTFS $DATA特性
當我們訪問 a.php:: $DATA時,就是請求 a.php本身的數據
::$DATA之后的數據當成文件流處理,不會檢測后綴名.且保持 $DATA之前的文件名
上傳成功, 訪問 123.php
0x09 Pass-09 .[空格]. 繞過黑名單
源碼
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$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");
$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); //首尾去空
if (!in_array($file_ext, $deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上傳出錯!';
}
} else {
$msg = '此文件類型不允許上傳!';
}
} else {
$msg = UPLOAD_PATH . '文件夾不存在,請手工創建!';
}
}
通過閱讀源碼發現,
這里上傳后的文件名用的還是未完全過濾之前的,前面幾關采用了拼接經過strrchr()
處理過的函數名,而這一關並沒有。因為源碼中只進行了一次點號的清除,且我們至需要讓經過過濾后的后綴名不在黑名單即可,所以我們直接采用xxx.php.[空格]. 就可以進行繞過。
測試結果
方法二
依舊采用 apache特性👈由右向左解析, 上傳 'xxx.php.rrrr' ,繞過黑名單
0x10 Pass-10 雙寫繞過黑名單
源碼
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array("php","php5","php4","php3","php2","html","htm","phtml","pht","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess");
$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;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上傳出錯!';
}
} else {
$msg = UPLOAD_PATH . '文件夾不存在,請手工創建!';
}
}
閱讀源碼可以發現,這里將把上傳的文件名中所有包含黑名單字符的,全部替換為空,所以這里可以利用雙寫來繞過, xx.p[php]hp,
測試結果
0x11 Pass-11 00截斷
源碼
$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 = $_GET['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類型文件!";
}
}
這里先參考下這個鏈接
淺顯的意思就是在url中%00表示ascii碼中的0,而ascii碼中0作為保留字符,表示字符串結束,所以當url中出現%00時就會認為讀取已結束
所以在burpsuit中作出以下修改
最后成功上傳2.php文件
上傳思路分析
1.這里是因為上傳路徑可控,所以才可以對路徑進行截斷
2.當前版本是5.2 存在%00截斷問題 (php 5.3 不可以)
GET 到的知識點--> %00和%00(urldecode)
在網上常見用Burp將數據包中的%00進行urldecode的操作,那為什么要進行這一個操作?網上也常見直接放入%00就可以截斷成功的案例,為什么呢?
- 首先解釋為什么要進行urldecode操作呢?其原因在於上傳的表單中有一個
enctype
的屬性,並且需要enctype="multipart/form-data"
(不對表單中數據進行編碼),path
大多數都是存放在表單中的,因此需要在數據包中進行urldecode操作使%00
變成字符串結束符號。
- 那么為什么網上也有直接添加
%00
而不進行urldecode操作呢?因為path也可以存放在URL或者Cookie中,而在提交數據的時候,瀏覽器會對數據做一次urldecode的操作,而到服務端,會對數據進行一次urldecode的操作,因此如果path在非enctype=multipart/form-data
的表單中或URL or Cookie中的時候,就可以直接寫%00
不需要進行URLdecode操作,讓服務端對%00
進行URL解碼即可。
- %00截斷的使用是在路徑上
GET到的知識點--> 00截斷利用條件
PHP<5.3.4,且GPC關閉
GET到的知識點--> 誤區
有很多朋友喜歡在文件名中加%00
進行截斷,筆者認為這種方式是不對的,為什么呢?比如攻擊者構造文件名:admintony.php%00a.jpg
,在提取后綴名的時候遇到%00
則認為字符串結束了,那么他提取到的后綴名會是.php
,.php
后綴又不允許上傳所以上傳失敗了(這里有必要提一句,有人可能會說在一些情況下,%00截斷文件名可以成功,這種案例你試一下是不是任意文件上傳,西普的00截斷實驗就是一個任意文件上傳的上傳點,既然是任意文件上傳又何必用00截斷繞過呢?)
0x12 Pass-12 POST類型 截斷
這關和上一關基本一致,這里就不詳細說了,附上測試結果
0x13 Pass-13 -15文件頭類型檢查
13關源碼
function getReailFileType($filename){
$file = fopen($filename, "rb");//fopen() 函數打開一個文件或 URL。
$bin = fread($file, 2); //只讀2字節 fread() 函數讀取打開的文件。
fclose($file);
$strInfo = @unpack("C2chars", $bin);
$typeCode = intval($strInfo['chars1'].$strInfo['chars2']);
$fileType = '';
switch($typeCode){
case 255216:
$fileType = 'jpg';
break;
case 13780:
$fileType = 'png';
break;
case 7173:
$fileType = 'gif';
break;
default:
$fileType = 'unknown';
}
return $fileType;
}
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$temp_file = $_FILES['upload_file']['tmp_name'];
$file_type = getReailFileType($temp_file);
if($file_type == 'unknown'){
$msg = "文件未知,上傳失敗!";
}else{
$img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$file_type;
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = "上傳出錯!";
}
}
}
通過閱讀源碼發現,getReailFileType() 只會讀取文件的前兩個字節
14關
這題一樣使用getimagesize
函數判斷文件類型,但是這個函數也是檢查文件頭,是不可信的。
15關
exif_imagetype
的作用是判斷一個圖像的類型 ,其實是讀取一個圖像的第一個字節並檢查其簽名。
方法一:
使用winhex等編輯器 打開圖片文件,將webshell直接插入末尾
方法二:
直接上傳webshell,使用burpsuit 攔截並添加圖片的文件頭
方法三:
使用cmd命令行 copy 生成圖片馬
方法四: php 讀文件
若是服務器采用文件頭驗證, 則可以先上傳一個 php文件讀文件 readfile() file_get_contents() highlight()
0x16 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 = "上傳出錯!";
}
}else if(($fileext == "png") && ($filetype=="image/png")){
if(move_uploaded_file($tmpname,$target_path)){
//使用上傳的圖片生成新的圖片
$im = imagecreatefrompng($target_path);
if($im == false){
$msg = "該文件不是png格式的圖片!";
@unlink($target_path);
}else{
//給新圖片指定文件名
srand(time());
$newfilename = strval(rand()).".png";
//顯示二次渲染后的圖片(使用用戶上傳圖片生成的新圖片)
$img_path = UPLOAD_PATH.'/'.$newfilename;
imagepng($im,$img_path);
@unlink($target_path);
$is_upload = true;
}
} else {
$msg = "上傳出錯!";
}
}else if(($fileext == "gif") && ($filetype=="image/gif")){
if(move_uploaded_file($tmpname,$target_path)){
//使用上傳的圖片生成新的圖片
$im = imagecreatefromgif($target_path);
if($im == false){
$msg = "該文件不是gif格式的圖片!";
@unlink($target_path);
}else{
//給新圖片指定文件名
srand(time());
$newfilename = strval(rand()).".gif";
//顯示二次渲染后的圖片(使用用戶上傳圖片生成的新圖片)
$img_path = UPLOAD_PATH.'/'.$newfilename;
imagegif($im,$img_path);
@unlink($target_path);
$is_upload = true;
}
} else {
$msg = "上傳出錯!";
}
}else{
$msg = "只允許上傳后綴為.jpg|.png|.gif的圖片文件!";
}
}
這里有個函數imagecreatefromjpeg
,作用是由文件或 URL 創建一個新圖象。
原理:
將一個正常顯示的圖片,上傳到服務器。尋找圖片被渲染后與原始圖片 部分對比仍然相同的數據塊部分,將Webshell代碼插在該部分,然后上傳。具體實現需要自己編寫Python程序,人工嘗試基本是不可能構造出能繞過渲染函數的圖片webshell的。
先知上有人針對16關專門開了一帖分析,分析得十分詳細:upload-labs之Pass16
也可以參考國外大神,二次渲染與繞過方法參考連接: https://secgeek.net/bookfresh-vulnerability/
0x17 Apache 換行解析漏洞(CVE-2017-15715)
影響版本:
2.4.0~2.4.29版本
源碼
<?php
if(isset($_FILES['file'])) {
$name = basename($_POST['name']);
$ext = pathinfo($name,PATHINFO_EXTENSION);
if(in_array($ext, ['php', 'php3', 'php4', 'php5', 'phtml', 'pht'])) {
exit('bad file');
}
move_uploaded_file($_FILES['file']['tmp_name'], './' . $name);
}
可見,這里用到了黑名單,如果發現后綴在黑名單中,則進行攔截。
利用CVE-2017-15715,上傳一個包含換行符的文件。注意,只能是\x0A
,不能是\x0D\x0A
,所以我們用hex功能在1.php后面添加一個\x0A
:
然后訪問/1.php%0A
,即可發現已經成功getshell:
總結
研究這個漏洞的過程中遇到幾個問題:
-
獲取文件名時不能用
$_FILES['file']['name']
,因為他會自動把換行去掉,這一點有點雞肋 -
默認的Apache配置即可利用,因為默認Apache配置就使用了``:
-
<FilesMatch \.php$> SetHandler application/x-httpd-php </FilesMatch>
所以理論上,只要用正則來匹配后綴進行php解析的Apache就有這個問題。而這個做法剛好是為了解決Apache老的解析漏洞而做的,可謂非此即彼,必然存在一種解析漏洞。
原理
正則中$可以匹配行尾或一個換行符,所以可以上傳一個后綴末尾包含換行符的文件,來繞過FilesMatch。當然繞過FilesMatch不一定就能被PHP解析,所以這個漏洞也需要看情況,比較雞肋
0x18 Pass-18 條件競爭
源碼
*過濾代碼:*unlink()
函數作用是刪除文件。若成功,則返回 true,失敗則返回 false。
$is_upload = false;
$msg = null;
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);
}
}else{
$msg = '上傳出錯!';
}
}
上面源碼的代碼邏輯:
- 首先接收上傳的文件
- 然后判斷上傳的文件的文件后綴是否在白名單中
- 在白名單中,則將文件重命名
- 不在白名單則刪除文件
通過不斷發送請求包,直到訪問shell.php成功為止
0x19 常見中間件解析漏洞
一:IIS 服務器
01:IIS<=6.0 解析漏洞
起因是解析標准不一致,即Web應用程序與IIS服務器對同一個文件的文件名稱(類型)理解不一致造成。
*** 利用方法有兩種:**
1. 畸形目錄解析/xxxx.asp/xxx.jpg
2. 分號文件解析test.asp;.jpg
- 第1種是因為 xxx.jpg文件在某個以.asp結尾的目錄下,而被IIS當成可執行文件來解析
- 第2中雖然以.jpg結尾,但是IIS解析時忽略了';'分號后面的.jpg,因此就當成test.asp來處理
02: IIS 7.0 & 7.5 畸形解析漏洞
默認fast-cgi
開啟狀況下,在一個文件路徑后面加上/xx.php會將原來的文件解析為php文件
*** 利用方法有兩種:**
- 上傳一張圖片馬 test.jpg,然后訪問test.jpg/.php或test.jpg/abc.php 可以被執行。
二:nginx
01:畸形解析漏洞
默認fast-cgi開啟狀況下,在一個文件路徑后面加上/xx.php會將原來的文件解析為php文件
和 IIS7.0&7.5畸形解析漏洞一樣,上傳圖片馬test.jpg,訪問/test.jpg/.php或test.jpg/abc.php 可以被執行
02:空字節代碼執行漏洞
在fast-cgi關閉的情況下,nginx版本:0.5., 0.6., 0.7- 0.7.65, 0.8 -0.8.37,nginx在圖片后附加 php代碼然后通過訪問
xx.jpg%00.php
意思是指在上傳了一個圖片馬之后,通過訪問 xx.jpg%00.php 來執行php代碼
03:文件名邏輯漏洞(CVE-2013-4547)
受影響的nginx版本: 0.8.41至1.4.3和1.5.7之前的1.5.x正常上傳一個附加代碼的圖 片"test.jpg",訪問時后面+"空格"+"\0"+".php",即讓圖片作為php文件解析
"/test.jpg \0.php"
三:Apache 解析漏洞
apache 是從右到左開始判斷解析文件名,如果為不可識別的后綴名,就再往左判斷,直到可以解析為止,如
test.php.jpegxtxt.xtie 解析成 test.php