Rumah  >  Artikel  >  pembangunan bahagian belakang  >  Lindungi borang anda dengan kunci borang

Lindungi borang anda dengan kunci borang

WBOY
WBOYasal
2023-09-07 21:49:141291semak imbas

Keselamatan adalah topik hangat. Mengamankan tapak web anda adalah sangat penting untuk mana-mana aplikasi web. Malah, 70% masa saya dihabiskan untuk mendapatkan permohonan. Salah satu perkara paling penting yang perlu kita lindungi ialah borang. Hari ini, kami akan menyemak cara untuk menghalang XSS (skrip merentas tapak) dan pemalsuan permintaan merentas tapak pada borang anda.

Kenapa?

POST data boleh dihantar dari satu laman web ke laman web yang lain. Kenapa ini teruk? Adegan ringkas...

Seorang pengguna yang log masuk ke tapak web anda melawat tapak web lain semasa sesi mereka. Tapak web akan dapat menghantar data POST ke tapak web anda - contohnya, menggunakan AJAX. Oleh kerana pengguna log masuk ke tapak anda, tapak lain juga boleh menghantar data siaran ke borang selamat yang hanya boleh diakses jika log masuk.

Kita juga mesti melindungi halaman kita daripada serangan menggunakan cURL

Bagaimana kita nak selesaikan masalah ini?

Dengan kunci borang! Kami akan menambah cincang khas (kunci borang) pada setiap borang untuk memastikan bahawa data hanya diproses apabila dihantar dari tapak web anda. Setelah borang diserahkan, skrip PHP kami akan mengesahkan kunci borang yang diserahkan terhadap kunci borang yang kami tetapkan dalam sesi.

Apa yang mesti kita lakukan:

  1. Tambahkan kunci borang pada setiap borang.
  2. Simpan kunci borang dalam sesi.
  3. Sahkan kunci borang selepas menyerahkan borang.

Langkah 1: Bentuk mudah

Pertama, kita memerlukan borang mudah untuk demonstrasi. Salah satu borang paling penting yang perlu kita lindungi ialah borang log masuk. Borang log masuk terdedah kepada serangan kekerasan. Cipta fail baharu dan simpan sebagai index.php dalam direktori akar web anda. Tambahkan kod berikut dalam teks:

	<!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>

Kini kami mempunyai halaman XHTML yang ringkas dengan borang log masuk. Jika anda ingin menggunakan kekunci borang di tapak web anda, anda boleh menggantikan skrip di atas dengan halaman log masuk anda sendiri. Sekarang, mari kita beralih kepada tindakan sebenar.

Langkah 2: Buat kelas

Kami akan mencipta kelas PHP untuk kunci borang. Kerana setiap halaman hanya boleh mengandungi satu kekunci borang, kami boleh mencipta satu tunggal untuk kelas kami untuk memastikan kelas kami digunakan dengan betul. Memandangkan mencipta singleton ialah topik OOP yang lebih maju, kami akan melangkau bahagian ini. Cipta fail baharu bernama formkey.class.php dan letakkan dalam direktori akar web anda. Sekarang kita perlu memikirkan fungsi yang kita perlukan. Pertama, kita memerlukan fungsi untuk menjana kunci borang supaya kita boleh memasukkannya ke dalam borang. Letakkan kod berikut dalam fail PHP anda:

<?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()
	{
		
	}
}
?>

Di atas, anda melihat kelas dengan tiga bahagian: dua pembolehubah dan fungsi. Kami menjadikan fungsi ini peribadi kerana ia hanya akan digunakan oleh fungsi output yang akan kami buat kemudian. Dalam dua pembolehubah ini kita akan menyimpan kekunci borang. Ia juga peribadi kerana ia hanya boleh digunakan oleh fungsi dalam kelas kami.

Kini, kita perlu mencari jalan untuk menjana kunci borang. Oleh kerana kunci borang kami mestilah unik (jika tidak, kami tidak mempunyai keselamatan), kami mengikat kunci kepada pengguna menggunakan gabungan alamat IP pengguna, menggunakan mt_rand() untuk menjadikannya unik dan menggunakan fungsi uniqid() untuk menjadikannya lebih unik. Kami juga menyulitkan maklumat ini menggunakan md5() untuk mencipta nilai cincang unik, yang kemudian kami masukkan ke dalam halaman kami. Kerana kami menggunakan md5(), pengguna tidak dapat melihat perkara yang kami gunakan untuk menjana kunci. Keseluruhan ciri:

//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);
}

Masukkan kod di atas ke dalam fail formkey.class.php anda. Gantikan fungsi dengan fungsi baharu.

Langkah 3: Masukkan kunci borang ke dalam borang

Untuk langkah ini, kami mencipta fungsi baharu yang mengeluarkan medan HTML tersembunyi menggunakan kekunci borang. Fungsi ini terdiri daripada tiga langkah:

  1. Jana kunci borang menggunakan fungsi generateKey() kami.
  2. Simpan kunci borang dalam pembolehubah dan sesi $formKey.
  3. Medan HTML Keluaran.

Kami menamakan fungsi outputKey() dan menjadikannya umum kerana kami perlu menggunakannya di luar kelas. Fungsi kami akan memanggil fungsi peribadi generateKey() untuk menjana kunci borang baharu dan menyimpannya dalam sesi setempat. Akhirnya, kami mencipta kod XHTML. Sekarang tambahkan kod berikut dalam kelas PHP kami:

//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."' />";
}

Kini kami akan menambah kunci borang pada borang log masuk kami untuk memastikan ia selamat. Kita mesti memasukkan kelas ini dalam fail index.php. Kami juga perlu memulakan sesi kerana kelas kami menggunakan sesi untuk menyimpan kunci yang dijana. Untuk melakukan ini, kami menambah kod berikut di atas doctype dan teg kepala:

<?php
//Start the session
session_start();
//Require the class
require('formkey.class.php');
//Start the class
$formKey = new formKey();
?>

Kod di atas cukup jelas. Kami memulakan sesi (kerana kami menyimpan kunci borang) dan memuatkan fail kelas PHP. Selepas itu, kami memulakan kelas menggunakan new formKey(), yang akan mencipta kelas kami dan menyimpannya dalam $formKey. Sekarang kita hanya perlu mengedit borang supaya ia termasuk kunci borang:

<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(); ?> 现在只需查看网页的源代码,您就可以看到表单上附加了一个表单密钥。剩下的唯一步骤是验证请求。

第 4 步:验证

我们不会验证整个表单;只有表单键。验证表单是基本的 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 变量(如果已设置)。

Lindungi borang anda dengan kunci borang

如果您启动服务器并转到index.php,您将看到我们的表单和消息“无错误”。当您提交表单时,您将看到消息“无表单键错误”,因为它是有效的 POST 请求。现在尝试重新加载页面并在浏览器请求再次发送 POST 数据时接受。您将看到我们的脚本触发了一条错误消息:“表单键错误!”现在,您的表单可以免受来自其他网站的输入和页面重新加载错误的影响!刷新后也会显示该错误,因为我们提交表单后生成了新的表单密钥。这很好,因为现在用户不会意外地将表单发布两次。

完整代码

以下是完整的 PHP 和 HTML 代码:

index.php


	



	
	Securing forms with form keys


	
outputKey(); ?>

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 开发教程和文章。

Atas ialah kandungan terperinci Lindungi borang anda dengan kunci borang. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn