添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
无邪的海豚  ·  Serilog vs log4net vs ...·  1 年前    · 
阳刚的青蛙  ·  K8S ...·  1 年前    · 
直爽的柑橘  ·  IF...ELSE ...·  2 年前    · 

PHP中的CSRF保护

跨站请求伪造被简写为CSRF。它是一种黑客攻击行为,黑客会逼迫你对你目前已经登录的网站做一些事情。反CSRF的实施减少了网站的脆弱性。

有了这种保护,网站就会拒绝那些发送没有CSRF标记或错误CSRF标记的请求的恶意访问。下图显示了针对CSRF攻击的用户请求验证。

如果一个真正的用户用适当的令牌发布表单,服务器会处理该请求。否则,在没有CSRF令牌参数的情况下,它将拒绝。

本教程将展示一个带有CSRF保护的PHP联系表单的例子。有了这种保护,它将在处理请求之前确保其真实性。

此外,我们将在PHP中创建一个服务来处理针对CSRF攻击的安全验证。服务器将拒绝没有令牌或无效令牌的用户的请求。

在本指南结束时,读者将了解以下内容。

  • CSRF攻击的概述。
  • 如何防止CSRF攻击。
  • PHP令牌管理会话活动的设置。
  • 在HTML联系表单中添加反CSRF令牌。
  • 要完成本教程,你将需要。

  • PHP的基础知识
  • 你选择的文本编辑器
  • 这段代码可以保护一个PHP联系表单免受CSRF攻击。首先,它创建了一个联系表单。然后,这个表单的后处理程序检查用户请求的CSRF攻击。最后,当登陆页面被加载时,PHP脚本会生成CSRF令牌。

    这个令牌将是表单页脚中的一个隐藏字段。它也会在PHP会话中照顾到这个令牌。当表单提交时,PHP代码将检查CSRF令牌参数。如果会话中的令牌被发现,它将被验证。

    如果用户发送的请求没有包括CSRF令牌,服务器将拒绝该请求。如果令牌与会话中的令牌不匹配,服务器也将拒绝该请求。

    如果CSRF令牌被成功验证,服务器将向目标地址发送联系邮件。下图显示了这个例子的文件结构。

    让我们开始吧!

    第1步:创建一个PHP会话并生成一个CSRF令牌

    着陆页上的表单页脚脚本调用 SecurityService 。这个类在PHP中生成一个CSRF令牌。它将令牌保存在一个PHP会话中,以便以后使用。它将帮助处理表单提交后的CSRF验证。

    表单页脚是一个框架文件,用生成的令牌加载一个隐藏字段。例如,下面的代码摘录来自 SecurityService.php 文件,生成了一个CSRF令牌。

    本文的下一节将介绍服务类的完整代码。

  • SecurityService.php (生成CSRF令牌的代码)
  • * Generate, store, and return the CSRF token * @return string[] public function getCSRFToken ( ) if ( empty ( $this ->session[ $this ->sessionTokenLabel])) { $this ->session[ $this ->sessionTokenLabel] = bin2hex ( openssl_random_pseudo_bytes ( 32 )); if ( $this ->hmac_ip !== false ) { $token = $this -> hMacWithIp ( $this ->session[ $this ->sessionTokenLabel]); } else { $token = $this ->session[ $this ->sessionTokenLabel]; return $token ;

    第2步:用CSRF令牌渲染联系表格

    这是一个带有姓名、电子邮件、主题和信息等常规字段的HTML联系表。此外,还有一个隐藏字段csrf-token,里面有生成的令牌。

    提交动作在将参数发布到PHP之前处理jQuery表单验证。

    客户端验证脚本处理提交时的基本验证。它对每个字段进行非空检查。

  • index.php (HTML模板)
  • <!DOCTYPE html>
    <html lang="en">
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <!-- title -->
        <title>PHP CSRF PROTECTION</title>
        <!-- Default stylesheet -->
        <link rel="stylesheet" href="css/style.css">
        <!-- JQuery -->
        <script src="vendor/jquery/jquery-3.2.1.min.js"></script>
        <!-- styles -->
        <style>
            .error-field {
                border: 1px solid #d96557;
            .send-button {
                cursor: pointer;
                background: #3cb73c;
                border: #36a536 1px solid;
                color: #FFF;
                font-size: 1em;
                width: 100px;
        </style>
    </head>
        <!-- main container -->
        <div class="container">
            <!-- header -->
            <h3>PHP CSRF PROTECTION</h3>
            <!-- POST form -->
            <form action="" method="post" id="frm-contact" onsubmit="return validateContactForm()">
                <!-- row userName-->
                <div class="row">
                    <!-- userName label -->
                    <div class="label">
                        Name: <span id="userName-info" class="validation-message"></span>
                    </div>
                    <input type="text" name="userName" id="userName" class="frm-input" value="<?php if (!empty($_POST['userName'])&& $type == 'error') {
        echo $_POST['userName'];
    }?>">
                <!-- row userName ends here-->
                <!-- row email-->
                <div class="row">
                    <!-- email label -->
                    <div class="label">
                        Email: <span id="email" class="validation-message"></span>
                    <input type="email" name="email" id="email" class="frm-input" value="<?php if (!empty($_POST['email'])&& $type == 'error') {
        echo $_POST['email'];
    }?>">
                <!-- row email ends here-->
                <!-- row userName-->
                <div class="row">
                    <!-- subject label -->
                    <div class="label">
                        Subject: <span id="subject-info" class="validation-message"></span>
                    <input type="text" name="subject" id="subject" class="frm-input" value="<?php if (!empty($_POST['subject'])&& $type == 'error') {
        echo $_POST['userName'];
    }?>">
                <!-- row subject ends here-->
                <!-- row message starts here-->
                <div class="row">
                    <div class="label">
                        Message: <span id="userMessage-info" class="validation-message"></span>
                    <textarea name="content" id="content" class="phppot-input" cols="60" rows="6"></textarea>
                <!-- row message ends here-->
                <!-- submit/send info -->
                <div class="row">
                    <input type="submit" name="send" class="send-button" value="Send" />
            </form>
        <script src="assets/js/validate.js"></script>
    </body>
    </html>
    

    表单页脚脚本触发服务处理程序来生成令牌。insertHiddenToken() 编写HTML代码,将csrf令牌字段加载到表单中。

  • view/framework/form-footer.php
  • require_once __DIR__ . '/../../lib/SecurityService.php'; $antiCSRF = new SecurityService\securityService(); $antiCSRF->insertHiddenToken();

    第3步:在PHP中进行反跨站请求伪造(CSRF)验证

    提交嵌入令牌的联系表单时,表单动作会执行以下脚本。SecurityService的validate() 函数将提交的令牌与存储在会话中的令牌进行比较。

    如果发现匹配,它将进一步发送联系电子邮件。否则,它将向用户提供一个错误信息。

  • index.php (PHP CSRF验证和表单处理)
  • use MailService; session_start(); if (!empty($_POST['send'])) { require_once __DIR__ . '/lib/SecurityService.php'; $antiCSRF = new SecurityService\securityService(); $csrfResponse = $antiCSRF->validate(); if (!empty($csrfResponse)) { require_once __DIR__ . '/lib/MailService.php'; $mailService = new MailService(); $response = $mailService->sendContactMail($_POST); if (!empty($response)) { $message = "Hi, we have received your message. Thank you."; $type = "success"; } else { $message = "Unable to send email."; $type = "error"; } else { $message = "Security alert: Unable to process your request."; $type = "error";

    第4步:生成、插入、验证CSRF令牌的安全服务

    这个在PHP中创建的服务类包括处理CSRF保护相关操作的方法。它定义了一个类属性来设置表单令牌字段名,会话索引。此外,它有方法生成令牌并将其写入HTML和PHP会话中。

    此外,它使用XSS缓解,同时用令牌写入表单页脚。此外,它还可以从验证过程中排除一些URLs。

    被排除的URL会绕过CSRF验证过程。相反,代码从PHP SERVER变量中获取当前请求的URL。

    然后将其与排除的URL数组进行比较,跳过验证。

    <? php
    namespace SecurityService;
    class securityService
        private $formTokenLabel = 'eg-csrf-token-label';
        private $sessionTokenLabel = 'EG_CSRF_TOKEN_SESS_IDX';
        private $post = [];
        private $session = [];
        private $server = [];
        private $excludeUrl = [];
        private $hashAlgo = 'sha256';
        private $hmac_ip = true;
        private $hmacData = 'ABCeNBHVe3kmAqvU2s7yyuJSF2gpxKLC';
        public function __construct($excludeUrl = null, &$post = null, &$session = null, &$server = null)
            if (! \is_null($excludeUrl)) {
                $this->excludeUrl = $excludeUrl;
            if (! \is_null($post)) {
                $this->post = & $post;
            } else {
                $this->post = & $_POST;
            if (! \is_null($server)) {
                $this->server = & $server;
            } else {
                $this->server = & $_SERVER;
            if (! \is_null($session)) {
                $this->session = & $session;
            } elseif (! \is_null($_SESSION) && isset($_SESSION)) {
                $this->session = & $_SESSION;
            } else {
                throw new \Error('No session available for persistence');
        public function insertHiddenToken()
            $csrfToken = $this->getCSRFToken();
            echo "<!--\n--><input type=\"hidden\"" . " name=\"" . $this->xssafe($this->formTokenLabel) . "\"" . " value=\"" . $this->xssafe($csrfToken) . "\"" . " />";
        public function xssafe($data, $encoding = 'UTF-8')
            return htmlspecialchars($data, ENT_QUOTES | ENT_HTML401, $encoding);
        public function getCSRFToken()
            if (empty($this->session[$this->sessionTokenLabel])) {
                $this->session[$this->sessionTokenLabel] = bin2hex(openssl_random_pseudo_bytes(32));
            if ($this->hmac_ip !== false) {
                $token = $this->hMacWithIp($this->session[$this->sessionTokenLabel]);
            } else {
                $token = $this->session[$this->sessionTokenLabel];
            return $token;
        private function hMacWithIp($token)
            $hashHmac = \hash_hmac($this->hashAlgo, $this->hmacData, $token);
            return $hashHmac;
        private function getCurrentRequestUrl()
            $protocol = "http";
            if (isset($this->server['HTTPS'])) {
                $protocol = "https";
            $currentUrl = $protocol . "://" . $this->server['HTTP_HOST'] . $this->server['REQUEST_URI'];
            return $currentUrl;
        public function validate()
            $currentUrl = $this->getCurrentRequestUrl();
            if (! in_array($currentUrl, $this->excludeUrl)) {
                if (! empty($this->post)) {
                    $isAntiCSRF = $this->validateRequest();
                    if (! $isAntiCSRF) {
                        // CSRF attack attempt
                        // CSRF attempt is detected. Need not reveal that information
                        // to the attacker, so just failing without info.
                        // Error code 1837 stands for CSRF attempt and this is for
                        // our identification purposes.
                        return false;
                    return true;
        public function isValidRequest()
            $isValid = false;
            $currentUrl = $this->getCurrentRequestUrl();
            if (! in_array($currentUrl, $this->excludeUrl)) {
                if (! empty($this->post)) {
                    $isValid = $this->validateRequest();
            return $isValid;
        public function validateRequest()
            if (!isset($this->session[$this->sessionTokenLabel])) {
                // CSRF Token not found
                return false;
            if (!empty($this->post[$this->formTokenLabel])) {
                // Let's pull the POST data
                $token = $this->post[$this->formTokenLabel];
            } else {
                return false;
            if (is_string($token)) {
                return false;
            // Grab the stored token
            if ($this->hmac_ip !== false) {
                $expected = $this->hMacWithIp($this->session[$this->sessionTokenLabel]);
            } else {
                $expected = $this->session[$this->sessionTokenLabel];
            return \hash_equals($token, $expected);
         * removes the token from the session
        public function unsetToken()
            if (! empty($this->session[$this->sessionTokenLabel])) {
                unset($this->session[$this->sessionTokenLabel]);
    

    这个MailService.php 使用PHP核心mail() 函数来发送联系邮件。你可以用SMTP通过电子邮件发送脚本来代替它。检查这个以使用PHP获得一个IP地址。它可能对记录用户的IP地址很有用。

  • MailService.php
  • namespace csrfProtection; class MailService function sendContactMail($postValues) $name = $postValues["userName"]; $email = $postValues["userEmail"]; $subject = $postValues["subject"]; $content = $postValues["content"]; $toEmail = "ADMIN EMAIL"; $mailHeaders = "From: " . $name . "(" . $email . ")\r\n"; $response = mail($toEmail, $subject, $content, $mailHeaders); return $response;

    输出:来自服务器的CSRF验证响应

    下面的图片显示了通常的联系表单。我们之前在很多联系表单教程中都看到过这个输出。在表单界面下面,图片显示了红色的安全警报信息。它确认了那些使用错误或空令牌发送请求的用户。

    这样我们就在PHP联系表单中实现了反CSRF保护。我希望这个例子的代码是有用的,你能得到我们在这里讨论的实施过程。

    我们已经在PHP中创建了一个SecurityService类来处理CSRF保护。在你需要启用CSRF保护的地方,它可以在多个应用程序中重复使用。返回响应信息的PHP代码正确地确认了用户。

    私信