你不知道的文件上傳漏洞

漏洞描述php

開發中文件上傳功能很常見,做爲開發者,在完成功能的基礎上咱們通常也要作好安全防禦。
文件處理通常包含兩項功能,用戶上傳和展現文件,如上傳頭像。html

文件上傳攻擊示例
upload.phpmysql

<?php
$uploaddir = 'uploads/'; 
$uploadfile = $uploaddir . basename($_FILES['userfile']['name']);
if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)){
    echo "File is valid, and was successfully uploaded.\n";
} 
else {
    echo "File uploading failed.\n";
}
?>

upload.htmlweb

<form name="upload" action="upload.php" method="POST" ENCTYPE="multipart/formdata">
Select the file to upload: <input type="file" name="userfile">
<input type="submit" name="upload" value="upload">
</form>

上述代碼未通過任何驗證,惡意用戶能夠上傳php文件,代碼以下sql

<?php eval($_GET['command']);?>

惡意用戶能夠經過訪問 如http://server/uploads/shell.php?command=phpinfo(); 來執行遠程命令shell

Content-type驗證數據庫

upload.php安全

<?php
if($_FILES['userfile']['type'] != "image/gif") {//獲取Http請求頭信息中ContentType
    echo "Sorry, we only allow uploading GIF images";
    exit;
}
$uploaddir = 'uploads/';
$uploadfile = $uploaddir.basename($_FILES['userfile']['name']);
if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)){
    echo "File is valid, and was successfully uploaded.\n";
} else {
    echo "File uploading failed.\n";
}
?>


該方式是經過Http請求頭信息進行驗證,可經過修改Content-type ==> image/jpg繞過驗證,能夠經過腳本或BurpSuite、fiddle修改
以下
Content-Disposition: form-data; name="userfile"; filename="shell.php"
Content-Type: image/gif服務器

圖片類型驗證
該方法經過讀取文件頭中文件類型信息,獲取文件類型fetch

備註:如JPEG/JPG文件頭標識爲FFD8

upload.php

<?php
$imageinfo = getimagesize($_FILES['userfile']['tmp_name']);
if($imageinfo['mime'] != 'image/gif' && $imageinfo['mime'] != 'image/jpeg') {
    echo "Sorry, we only accept GIF and JPEG images\n";
    exit;
}
$uploaddir = 'uploads/';
$uploadfile = $uploaddir . basename($_FILES['userfile']['name']);
if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)){
    echo "File is valid, and was successfully uploaded.\n";
} else {
    echo "File uploading failed.\n";
}
?>

能夠經過圖片添加註釋來繞過此驗證。
如添加註釋<?php phpinfo(); ?>,保存圖片後將其擴展名改成php,則可成功上傳。
上傳成功後訪問該文件則可看到以下顯示

 

文件擴展名驗證

 經過黑名單或白名單對文件擴展名進行過濾,以下代碼

upload.php

<?php
$blacklist = array(".php", ".phtml", ".php3", ".php4");
foreach ($blacklist as $item) {
if(preg_match("/$item\$/i", $_FILES['userfile']['name'])) {
    echo "We do not allow uploading PHP files\n";
    exit;
}
}
$uploaddir = 'uploads/';
$uploadfile = $uploaddir . basename($_FILES['userfile']['name']);
if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)){
    echo "File is valid, and was successfully uploaded.\n";
} else {
    echo "File uploading failed.\n";
}
?>

當黑名單不全,構造特殊文件名能夠繞過擴展名驗證

直接訪問上傳的文件
將上傳文件保存在非web root下其餘文件夾下,能夠防止用戶經過路徑直接訪問到文件。
upload.php

<?php
$uploaddir = 'd:/uploads/';
$uploadfile = $uploaddir . basename($_FILES['userfile']['name']);
if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)) {
  echo "File is valid, and was successfully uploaded.\n";
} else {
  echo "File uploading failed.\n";
}
?>

用戶不能夠直接經過http://localhost/uploads/ 來訪問文件,必須經過view.php來訪問
view.php

<?php
$uploaddir = 'd:/uploads/';
$name = $_GET['name'];
readfile($uploaddir.$name);
?>

查看文件代碼未驗證文件名,用戶能夠經過例如http://localhost/view.php?name=..//php/upload.php,查看指定的文件


解決漏洞示例

upload.php

<?php
require_once 'DB.php';
$uploaddir = 'D:/uploads/'; 
$uploadfile = tempnam($uploaddir, "upload_");
if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)) {
  $db =& DB::connect("mysql://username:password@localhost/database");
  if(PEAR::isError($db)) {
    unlink($uploadfile);
    die "Error connecting to the database";
  }
  $res = $db->query("INSERT INTO uploads SET name=?, original_name=?,mime_type=?",
      array(basename($uploadfile,basename($_FILES['userfile']['name']),$_FILES['userfile']['type']));   if(PEAR::isError($res)) {     unlink($uploadfile);     die "Error saving data to the database. The file was not uploaded";   }   $id = $db->getOne('SELECT LAST_INSERT_ID() FROM uploads');   echo "File is valid, and was successfully uploaded. You can view it <a href=\"view.php?id=$id\">here</a>\n"; } else {   echo "File uploading failed.\n"; } ?>

  view.php

<?php
require_once 'DB.php';
$uploaddir = 'D:/uploads/';
$id = $_GET['id'];
if(!is_numeric($id)) {
  die("File id must be numeric");
}
$db =& DB::connect("mysql://root@localhost/db");
if(PEAR::isError($db)) {
  die("Error connecting to the database");
}
$file = $db->getRow('SELECT name, mime_type FROM uploads WHERE id=?',array($id), DB_FETCHMODE_ASSOC);
if(PEAR::isError($file)) {
  die("Error fetching data from the database");
}
if(is_null($file) || count($file)==0) {
  die("File not found");
}
header("Content-Type: " . $file['mime_type']);
readfile($uploaddir.$file['name']);
?>

上述代碼文件名隨機更改,文件被存儲在web root以外,用戶經過id在數據庫中查詢文件名,讀取文件,能夠有效的阻止上述漏洞發生

總結

經過以上示例分析,可總結一下幾點

1.文件名修改,不使用用戶上傳的文件名

2.用戶不能夠經過上傳路徑直接訪問文件

3.文件查看採用數據庫獲取文件名,從而在相應文件服務器讀取文件

4.文件上傳限制文件大小,我的上傳數量等

相關文章
相關標籤/搜索