MySQL/API
請記住,網際網路是由那些不希望我們有任何秘密的人創造的。還要記住,很多人被付錢來了解我們的秘密,並將它們註冊在某個地方。
偏執是一種智慧形式。
有時,連線引數(包括使用者名稱和密碼)儲存在純文字檔案中,例如 .ini 檔案。這是不安全的:如果使用者猜到它的名稱,他就可以讀取它。如果它位於 Web 伺服器的 WWW 目錄之外,它更安全,但將其作為常量儲存在程式檔案中是一個更好的做法。
使用者總是可能獲得您的 FTP 密碼或其他密碼。因此,用於連線到 MySQL 的使用者名稱和密碼應與其他使用者名稱/密碼不同。
MySQL 密碼必須安全。您無需記住它們。它們應包含小寫字母、大寫字母、數字和符號(如 '_');它們不應包含現有單詞或您的出生日期;它們絕不應透過電子郵件傳送(如果傳送,必須有一些方法可以修改它們);它們不應儲存在絕對沒有必要儲存它們的地方。
在理想世界中,您將知道 $_POST 中包含的值是可以插入 SQL 語句中的值。但在理想世界中,沒有貧困或專有軟體,因此情況並非如此。這些值可能包含稱為“SQL 注入”的攻擊。當您期望像“'42'”這樣的值時,您可能會發現像“'42' OR 1”這樣的值。因此,當您嘗試建立這樣的語句時
DELETE FROM `articles` WHERE `id`=42
您可能會建立這樣的語句:
DELETE FROM `articles` WHERE `id`=42 OR 1
這會刪除所有記錄。
在某些情況下,您嘗試執行這樣的查詢
SELECT * FROM `my_nice_table` WHERE title='bla bla'
使用者可能會將其更改為
SELECT * FROM `my_nice_table` WHERE title='bla bla'; TRUNCATE TABLE `my_nice_table`
這些只是示例。如果所有記錄都從您的表中消失,很容易意識到。如果表已正確備份,您可以重新填充它們。但也有更糟糕的情況。如果使用者學會了如何操縱您的資料庫,他可以為自己建立一個管理帳戶,或者對您網站的內容進行您永遠不會看到的修改,甚至可以註冊他未支付的款項。
簡單來說,必須表示值的輸入,如果包含更多內容,則不應接受。
- 字串值
它們用“引號”括起來。它們中出現的每個引號都應轉換為“''”或“\'”。PHP 建議使用 `mysql_real_escape_string` 來替換這些特殊字元。
- 數字(整數、浮點數)
它們必須是數字輸入。如果它們包含“OR”或空格等內容,則不是數字。
- 日期
將它們用“引號”括起來,並像對待字串一樣管理它們。
- NULL / UNKNOWN / TRUE /FALSE
這些值絕不應由使用者輸入,而應以程式設計方式建立。
- SQL 名稱
在某些情況下,SQL 名稱可能包含在使用者輸入中。一個常見的例子是在 ORDER BY 子句中使用的列名,它可能來自 $_GET。將它們用“反引號”括起來,並將每個“`”替換為“``”。當然,一般來說,如果 SQL 名稱僅在 ORDER BY 子句中使用,這是一種非常糟糕的做法。
- 註釋
使用者輸入絕不應插入 SQL 註釋中。
當密碼儲存在資料庫中時,它們通常是加密的。加密應由指令碼完成,而不是由 MySQL 完成。如果它是透過 SQL 完成的,密碼將透過語句以純文字形式寫入。這意味著它們可以透過以下方式檢視
- 可能,一些系統日誌,如果與資料庫的通訊是透過網路進行的並且沒有加密
- MySQL 日誌
- SHOW PROCESSLIST
因此,人們絕不應該傳送這樣的查詢
SELECT 1 FROM `users` WHERE `password`=MD5('abraxas')
但是,在 PHP 中,您應該編寫
$sql = "SELECT 1 FROM `users` WHERE `password`=MD5('".md5('abraxas')."')";
您絕不應使用不安全的加密函式,如 PASSWORD()。此外,您也不應使用雙向加密。只有 SHA256 等密碼雜湊是安全的,並且不要使用 MD5 等舊的雜湊演算法。
密碼,即使它們被安全地加密,也不應透過 SELECT 檢索。這是不安全的,單向加密不需要這樣做。
如果您的所有資料庫內容都是公開的,則沒有理由使用加密進行通訊。但通常情況下並非如此。即使這樣,也可能有一組有限的人員被授權向網站提交新內容,這將需要使用密碼。
因此,通常情況下,使用 SSL 加密是一個好主意。請參閱驅動程式的文件,瞭解如何執行此操作(這始終是一個簡單的連線選項)。
SSL 不僅會加密包含使用者密碼的網路流量,還可以使用證書驗證網站是否為正確網站。一種可能的攻擊是建立了一個看起來像受害者網站的網站,試圖讓您提交使用者名稱和密碼。
透過使用持久連線,我們保持與伺服器的連線開啟,以便可以執行多個查詢,而無需每次執行指令碼時關閉和重新開啟連線的開銷。
請注意,這並不總是最佳最佳化。試想一下,如果每個託管網站都只使用持久連線,伺服器的 RAM 應該儲存多少持久連線:一次會有太多連線。
持久連線可以透過許多語言使用。
當您執行查詢時,您會獲得一個記錄集並將它放入一個變數中。當您不再需要它時將其保留在記憶體中是浪費 RAM。這就是為什麼通常您應該儘快釋放記憶體。如果只有在指令碼結束前幾行才有可能,那麼這樣做就沒有意義。但在某些情況下,這樣做是好的。
許多 API 支援兩種提取行的方法:您可以將它們放入普通陣列、物件或關聯陣列中。將行放入物件是最慢的方式,而將它們放入普通陣列是最快的方式。如果您每行檢索一個值,那麼將其放入陣列可能是一個好主意。
通常,API 支援一些建立 SQL 語句並將其傳送到 MySQL 伺服器的方法。您可以透過手動建立語句來獲得相同的效果,但它是一種較慢的方式。API 的方法通常更最佳化。
- 一些指令碼使用兩個查詢來提取透視表。
客戶端/伺服器通訊通常是瓶頸,因此您應該嘗試只使用一個 JOIN 而不是多個 JOIN。
- 如果需要使用多個查詢,則應儘可能只使用一個連線。
- 只檢索您真正需要的欄位。
- 儘量不要在 SQL 命令中包含太多無意義的字元(空格、製表符、註釋...)。
當您從現有表建立新表時,您應該使用 CREATE ... SELECT。當您想從查詢填充現有表時,您應該使用 INSERT ... SELECT 或 REPLACE ... SELECT。這樣,您將告訴伺服器透過僅傳送一個 SQL 語句來執行所有需要的操作。
許多指令碼不檢查 INSERT 是否成功。如果是這種情況,您應該使用 INSERT DELAYED 而不是 INSERT。這樣,客戶端在繼續操作之前不會等待來自伺服器的確認。
如果您執行一個 DELETE 然後是一個 INSERT,您需要將兩個 SQL 命令傳送到伺服器。也許您可能想使用 REPLACE 而不是 DELETE 和 INSERT。可能的話,使用 REPLACE DELAYED。
有時,會話資料儲存在資料庫中。這需要每次使用者載入頁面時至少進行一次 UPDATE 和一次 SELECT。透過將會話資料儲存在 cookie 中可以避免這種情況。
瀏覽器允許使用者不接受 cookie,但如果他們不接受 cookie,他們將無法訪問許多重要的現代網站。
唯一不能安全地儲存在 cookie 中的資料是密碼。您可以為 cookie 設定一個簡短的有效期,這樣使用者的隱私就不會受到您的 cookie 的嚴重損害。或者,您可以執行以下操作
- 當用戶成功登入您的網站時,使用 CURRENT_TIMESTAMP() 和隨機 ID 建立一個記錄;
- 使用該 ID 設定一個 cookie;
- 當用戶嘗試執行某些操作時,檢查他是否已登入
SELECT FROM `access` WHERE `id`=id_from_cookie AND `tstamp`>=CURRENT_TIMESTAMP() - login_lifetime
- 更新 tstamp
當用戶瀏覽文章或其他動態內容(即儲存在資料庫中的內容)時,需要生成一個 HTML 文件。通常,頁面沒有可變內容,只有在插入一次後很少(或從不)更新的內容。文章或連結列表就是一個很好的例子。
因此,建立一個程式,當文章插入到資料庫時,生成一個靜態 HTML 頁面,可能是一個好主意。如果文章更新,頁面可以被刪除並重新生成。這可以節省大量 SQL 語句和 DBMS 的工作。
當然,這需要您可能沒有的某些許可權。如果您使用的是託管服務,您可能需要與技術支援團隊討論此事。
PHP 具有以下適用於 MySQL 的官方驅動程式
- mysql - 較舊,因此仍被許多 Web 應用程式使用;它是一個過程式的 PHP 模組
- mysqli - 更快;可以用作一組類或普透過程式庫
- PDO(PHP 資料物件) - 使用 PDO,一個用於與資料庫互動的抽象層,它具有用於 MySQL 和 ODBC 的驅動程式。
- PDO_MYSQL 支援一些高階 MySQL 功能,並在不存在時模擬這些功能。
上述驅動程式中的函式是擴充套件呼叫 C API 中的方法。它們可以使用 MySQL 客戶端庫或 mysqlnd,一個適用於 PHP 的原生驅動程式。
有時,啟用 mysql 和 mysqli 都會導致一些問題;因此,如果您只使用其中一個,您應該停用另一個。
此外,PHP 還具有一個可以與 MySQL 一起使用的 ODBC 擴充套件。
PEAR 是一組重要的 PHP 類,支援 MySQL。
PHP 具有一個名為 register_globals 的環境變數。從 PHP 4.2 開始,預設情況下它被設定為 false,您不應該設定它。在 PHP 5.3 中,此變數也被棄用,在 PHP 6 中已被刪除。
然而,如果您的 PHP 版本支援 `register_globals`,您可以透過呼叫 `ini_get()` 函式來驗證它是否設定為 `true`。但是,如果它為 `true`,您就無法使用 `ini_set()` 修改它。有兩種方法可以將其關閉:
- 編輯 `php.ini` 檔案
(如果您使用的是託管服務,則無法進行此操作)
- 在 `.htaccess` 檔案中新增一行程式碼
php_flag register_globals off
(有時在託管服務中可能有效)
原因是,如果 `register_globals` 為 `true`,使用者可以透過以下方式呼叫變數,從而向您的指令碼中任意新增變數:
your_script.php?new_variable=new_value
您永遠不應使用 `$_REQUEST` 超級全域性陣列。它可以用來從以下地方檢索變數:
- $_ENV
- $_GET
- $_POST
- $_COOKIE
- $_SERVER
這是 PHP 遵循的順序(可能由 `variables_order` 環境變數修改)。這意味著,如果您的指令碼設定了一個名為 "userid" 的伺服器變數,而您嘗試透過 `$_REQUEST` 讀取它,使用者可以透過在查詢字串中新增一個變數來阻止您這樣做。
此外,您永遠不應該盲目信任 HTTP 變數的有效性。