安全是一個熱門話題。確保您的網站安全對於任何 Web 應用程式都極為重要。事實上,我 70% 的時間都花在保護應用程式上。我們必須保護的最重要的事情之一是表單。今天,我們將回顧一種防止表單上的 XSS(跨站腳本)和跨站請求偽造的方法。
POST 資料可以從一個網站傳送到另一個網站。為什麼這樣不好?一個簡單的場景...
登入您網站的使用者在其會話期間造訪另一個網站。網站將能夠將 POST 資料傳送到您的網站 - 例如,使用 AJAX。由於使用者已登入您的網站,因此其他網站也能夠將發布資料傳送到只有登入後才能存取的安全表單。
我們還必須保護我們的頁面免受使用 cURL 的攻擊
帶有表單鍵!我們將向每個表單添加一個特殊的雜湊值(表單金鑰),以確保資料僅在從您的網站發送時才會被處理。提交表單後,我們的 PHP 腳本將根據我們在會話中設定的表單金鑰驗證提交的表單金鑰。
首先,我們需要一個簡單的表單來進行示範。我們必須保護的最重要的表單之一是登入表單。登入表單容易受到暴力攻擊。建立一個新文件,並將其儲存為 Web 根目錄中的 index.php。在正文中加入以下程式碼:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <meta http-equiv="content-type" content="text/html;charset=UTF-8" /> <title>Securing forms with form keys</title> </head> <body> <form action="" method="post"> <dl> <dt><label for="username">Username:</label></dt> <dd><input type="text" name="username" id="username" /></dd> <dt><label for="username">Password:</label></dt> <dd><input type="password" name="password" id="password" /></dd> <dt></dt> <dd><input type="submit" value="Login" /></dd> </dl> </form> </body> </html>
現在我們有了一個帶有登入表單的簡單 XHTML 頁面。如果您想在網站上使用表單鍵,您可以將上面的腳本替換為您自己的登入頁面。現在,讓我們繼續真正的行動。
我們將為表單鍵建立一個 PHP 類別。因為每個頁面只能包含一個表單鍵,所以我們可以為我們的類別建立一個單例,以確保我們的類別被正確使用。因為建立單例是一個更高級的 OOP 主題,所以我們將跳過這一部分。建立一個名為 formkey.class.php 的新檔案並將其放置在您的 Web 根目錄中。現在我們必須考慮我們需要的功能。首先,我們需要一個函數來產生表單金鑰,以便我們可以將其放入表單中。在您的 PHP 檔案中放置以下程式碼:
<?php //You can of course choose any name for your class or integrate it in something like a functions or base class class formKey { //Here we store the generated form key private $formKey; //Here we store the old form key (more info at step 4) private $old_formKey; //Function to generate the form key private function generateKey() { } } ?>
在上面,您看到一個包含三個部分的類別:兩個變數和一個函數。我們將該函數設為私有,因為該函數將僅由我們稍後將建立的輸出函數使用。在這兩個變數中,我們將儲存表單鍵。它們也是私有的,因為它們只能由我們類別內的函數使用。
現在,我們必須想辦法產生表單金鑰。因為我們的表單金鑰必須是唯一的(否則我們沒有任何安全性),所以我們使用用戶IP 位址的組合將金鑰綁定到用戶,使用mt_rand() 使其唯一,並使用uniqid() 函數使其更加獨特。我們還使用 md5() 加密此資訊以建立唯一的雜湊值,然後將其插入到我們的頁面中。因為我們使用了 md5(),所以使用者無法看到我們用來產生金鑰的內容。整個功能:
//Function to generate the form key private function generateKey() { //Get the IP-address of the user $ip = $_SERVER['REMOTE_ADDR']; //We use mt_rand() instead of rand() because it is better for generating random numbers. //We use 'true' to get a longer string. //See http://www.php.net/mt_rand for a precise description of the function and more examples. $uniqid = uniqid(mt_rand(), true); //Return the hash return md5($ip . $uniqid); }
將上面的程式碼插入到您的 formkey.class.php 檔案中。用新函數取代該函數。
對於這一步驟,我們建立一個新函數,使用表單鍵輸出隱藏的 HTML 欄位。此函數由三個步驟組成:
我們將函數命名為 outputKey() 並將其公開,因為我們必須在類別之外使用它。我們的函數將呼叫私有函數generateKey()來產生新的表單金鑰並將其保存在本機會話中。最後,我們建立 XHTML 程式碼。現在在我們的 PHP 類別中加入以下程式碼:
//Function to output the form key public function outputKey() { //Generate the key and store it inside the class $this->formKey = $this->generateKey(); //Store the form key in the session $_SESSION['form_key'] = $this->formKey; //Output the form key echo "<input type='hidden' name='form_key' id='form_key' value='".$this->formKey."' />"; }
現在,我們將把表單金鑰新增到我們的登入表單中以確保其安全。我們必須將該類別包含在 index.php 檔案中。我們還必須啟動會話,因為我們的類別使用會話來儲存產生的金鑰。為此,我們在 doctype 和 head 標記上方添加以下程式碼:
<?php //Start the session session_start(); //Require the class require('formkey.class.php'); //Start the class $formKey = new formKey(); ?>
上面的程式碼非常不言自明。我們啟動會話(因為我們儲存表單金鑰)並載入 PHP 類別檔案。之後,我們使用new formKey()啟動該類,這將建立我們的類別並將其儲存在$formKey中。現在我們只需編輯表單,使其包含表單鍵:
<form action="" method="post"> <dl> <?php $formKey->outputKey(); ?> <dt><label for="username">Username:</label></dt> <dd><input type="text" name="username" id="username" /></dd> <dt><label for="username">Password:</label></dt> <dd>input type="password" name="password" id="password" /></dd> <dl> </form>
仅此而已!因为我们创建了函数 outputKey(),所以我们只需将它包含在表单中即可。我们可以在每个表单中使用表单键,只需添加 outputKey(); ?> 现在只需查看网页的源代码,您就可以看到表单上附加了一个表单密钥。剩下的唯一步骤是验证请求。
我们不会验证整个表单;只有表单键。验证表单是基本的 PHP 操作,并且可以在网络上找到教程。让我们验证表单密钥。因为我们的“generateKey”函数会覆盖会话值,所以我们向 PHP 类添加一个构造函数。创建(或构造)我们的类时将调用构造函数。在我们创建新密钥之前,构造函数会将前一个密钥存储在类中;所以我们将始终拥有以前的表单密钥来验证我们的表单。如果我们不这样做,我们将无法验证表单密钥。将以下 PHP 函数添加到您的类中:
//The constructor stores the form key (if one exists) in our class variable. function __construct() { //We need the previous key so we store it if(isset($_SESSION['form_key'])) { $this->old_formKey = $_SESSION['form_key']; } }
构造函数应始终命名为__construct()。当调用构造函数时,我们检查是否设置了会话,如果是,我们将其本地存储在 old_formKey 变量中。
现在我们可以验证表单密钥了。我们在类中创建一个基本函数来验证表单密钥。这个函数也应该是公共的,因为我们将在类之外使用它。该函数将根据表单键的存储值验证表单键的 POST 值。将此函数添加到 PHP 类中:
//Function that validated the form key POST data public function validate() { //We use the old formKey and not the new generated version if($_POST['form_key'] == $this->old_formKey) { //The key is valid, return true. return true; } else { //The key is invalid, return false. return false; } }
在index.php中,我们使用刚刚在类中创建的函数来验证表单密钥。当然,我们仅在 POST 请求后进行验证。在 $formKey = new formKey(); 后添加以下代码
$error = 'No error'; //Is request? if($_SERVER['REQUEST_METHOD'] == 'post') { //Validate the form key if(!isset($_POST['form_key']) || !$formKey->validate()) { //Form key is invalid, show an error $error = 'Form key error!'; } else { //Do the rest of your validation here $error = 'No form key error!'; } }
我们创建了一个变量$error来存储我们的错误消息。如果已发送 POST 请求,我们将使用 $formKey->validate() 验证表单密钥。如果返回 false,则表单键无效,并且我们会显示错误。请注意,我们仅验证表单密钥 - 您需要自己验证表单的其余部分。
在 HTML 中,您可以放置以下代码来显示错误消息:
<div><?php if($error) { echo($error); } ?></div>
这将回显 $error 变量(如果已设置)。
如果您启动服务器并转到index.php,您将看到我们的表单和消息“无错误”。当您提交表单时,您将看到消息“无表单键错误”,因为它是有效的 POST 请求。现在尝试重新加载页面并在浏览器请求再次发送 POST 数据时接受。您将看到我们的脚本触发了一条错误消息:“表单键错误!”现在,您的表单可以免受来自其他网站的输入和页面重新加载错误的影响!刷新后也会显示该错误,因为我们提交表单后生成了新的表单密钥。这很好,因为现在用户不会意外地将表单发布两次。
以下是完整的 PHP 和 HTML 代码:
index.php
Securing forms with form keys fomrkey.class.php
<?php //You can of course choose any name for your class or integrate it in something like a functions or base class class formKey { //Here we store the generated form key private $formKey; //Here we store the old form key (more info at step 4) private $old_formKey; //The constructor stores the form key (if one excists) in our class variable function __construct() { //We need the previous key so we store it if(isset($_SESSION['form_key'])) { $this->old_formKey = $_SESSION['form_key']; } } //Function to generate the form key private function generateKey() { //Get the IP-address of the user $ip = $_SERVER['REMOTE_ADDR']; //We use mt_rand() instead of rand() because it is better for generating random numbers. //We use 'true' to get a longer string. //See http://www.php.net/mt_rand for a precise description of the function and more examples. $uniqid = uniqid(mt_rand(), true); //Return the hash return md5($ip . $uniqid); } //Function to output the form key public function outputKey() { //Generate the key and store it inside the class $this->formKey = $this->generateKey(); //Store the form key in the session $_SESSION['form_key'] = $this->formKey; //Output the form key echo "<input type='hidden' name='form_key' id='form_key' value='".$this->formKey."' />"; } //Function that validated the form key POST data public function validate() { //We use the old formKey and not the new generated version if($_POST['form_key'] == $this->old_formKey) { //The key is valid, return true. return true; } else { //The key is invalid, return false. return false; } } } ?>结论
将此代码添加到您网站上的每个重要表单中将显着提高表单的安全性。它甚至会停止刷新问题,正如我们在步骤 4 中看到的那样。由于表单密钥仅对一个请求有效,因此不可能进行双重发布。
这是我的第一个教程,希望您喜欢它并使用它来提高您的安全性!请通过评论让我知道您的想法。有更好的方法吗?让我们知道。
进一步阅读
- WordPress 还使用表单键(将其命名为 Nonce):Wordpress Nonce
- 编写安全 PHP 应用程序的七个习惯
- 在 Twitter 上关注我们,或订阅 NETTUTS RSS Feed 以获取更多日常 Web 开发教程和文章。
以上是使用表單金鑰保護您的表單的詳細內容。更多資訊請關注PHP中文網其他相關文章!
陳述:本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn