ChatGPT解决这个技术问题 Extra ChatGPT

如何使用 PHP 清理用户输入?

是否有一个包罗万象的功能可以很好地清理用户输入以进行 SQL 注入和 XSS 攻击,同时仍然允许某些类型的 HTML 标记?

使用 PDO 或 MySQLi 是不够的。如果您使用不受信任的数据(例如 select * from users where name='$name')构建 SQL 语句,那么使用 PDO 或 MySQLi 或 MySQL 都没有关系。你仍然处于危险之中。您必须使用参数化查询,或者,如果必须,对您的数据使用转义机制,但这不太可取。
@AndyLester 您是否暗示有人在没有准备好的陈述的情况下使用 PDO? :)
我是说“使用 PDO 或 MySQLi”的信息不足以向新手解释如何安全地使用它们。你和我都知道准备好的陈述很重要,但我不认为阅读这个问题的每个人都会知道。这就是为什么我添加了明确的说明。
安迪的评论是完全正确的。我最近将我的 mysql 网站转换为 PDO,认为我现在在某种程度上可以免受注入攻击。直到在这个过程中我才意识到我的一些 sql 语句仍然是使用用户输入构建的。然后我使用准备好的语句修复了这个问题。对于一个完整的新手来说,并不完全清楚两者之间的区别,因为许多专家抛出了关于使用 PDO 的评论,但没有具体说明需要准备好的语句。假设这是显而易见的。但对新手来说不是。
@Christian:GhostRider 和 AndyLester 是对的。让这成为沟通的一个教训。我曾经是个新手,这很糟糕,因为专家完全不知道如何沟通。

Y
Your Common Sense

用户输入可以被过滤是一个常见的误解。 PHP 甚至有一个(现已弃用)“功能”,称为 magic-quotes,它建立在这个想法之上。这是胡说八道。忘记过滤(或清洁,或任何人们所说的)。

为了避免问题,您应该做的事情非常简单:每当您将一段数据嵌入到外部代码中时,您必须根据该代码的格式规则来处理它。但是您必须了解,此类规则可能过于复杂,无法尝试手动遵循它们。例如,在 SQL 中,字符串、数字和标识符的规则都是不同的。为方便起见,在大多数情况下,此类嵌入都有专用工具。例如,当您需要在 SQL 查询中使用 PHP 变量时,您必须使用准备好的语句,这将负责所有正确的格式/处理。

另一个示例是 HTML:如果您在 HTML 标记中嵌入字符串,则必须使用 htmlspecialchars 对其进行转义。这意味着每个 echoprint 语句都应使用 htmlspecialchars

第三个示例可能是 shell 命令:如果要将字符串(例如参数)嵌入到外部命令中,并使用 exec 调用它们,则必须使用 escapeshellcmdescapeshellarg

此外,一个非常引人注目的例子是 JSON。规则如此繁多和复杂,以至于您永远无法手动遵循它们。这就是为什么您永远不应该手动创建 JSON 字符串,而是始终使用专用函数 json_encode() 来正确格式化每一位数据。

等等等等 ...

您需要主动过滤数据的唯一情况是您接受预格式化的输入。例如,如果您让您的用户发布您计划在网站上显示的 HTML 标记。但是,您应该明智地不惜一切代价避免这种情况,因为无论您过滤它的效果如何,它始终是一个潜在的安全漏洞。


“这意味着每个 echo 或 print 语句都应该使用 htmlspecialchars” - 当然,您的意思是“每个 ... 语句输出用户输入”; htmlspecialchars()-ifying "echo 'Hello, world!';"会疯的;)
在一种情况下,我认为过滤是正确的解决方案:UTF-8。您不希望在整个应用程序中都使用无效的 UTF-8 序列(您可能会根据代码路径获得不同的错误恢复),并且可以轻松过滤(或拒绝)UTF-8。
@jbyrd - 不,LIKE 使用专门的正则表达式语言。您将不得不对输入字符串进行两次转义 - 一次用于正则表达式,一次用于 mysql 字符串编码。它是代码中代码中的代码。
目前 mysql_real_escape_string 已被弃用。现在,使用 prepared statements 来防止 SQL 注入被认为是一种很好的做法。所以切换到 MySQLi 或 PDO。
因为你限制了攻击面。如果您尽早清理(输入时),您必须确定应用程序中没有其他漏洞可以让不良数据进入。而如果你迟到了,那么你的输出函数就不必“相信”它得到了安全的数据——它只是假设一切都是不安全的。
s
scorpiodawg

不要试图通过清理输入数据来防止 SQL 注入。

相反,不允许在创建 SQL 代码时使用数据。使用使用绑定变量的预处理语句(即在模板查询中使用参数)。这是保证免受 SQL 注入的唯一方法。

有关防止 SQL 注入的更多信息,请参阅我的网站 http://bobby-tables.com/


或访问 official documentation 并了解 PDO 和准备好的语句。很小的学习曲线,但是如果您对 SQL 非常了解,那么您将毫无困难地适应。
对于 SQL Injection 的具体情况,这才是正确答案!
请注意,准备好的语句不会增加任何安全性,参数化查询可以。它们恰好很容易在 PHP 中一起使用。
它不是唯一有保证的方式。十六进制输入和查询中的 unhex 也会阻止。如果您使用正确的十六进制攻击,也无法进行十六进制攻击。
如果您输入的是专门的内容,例如电子邮件地址或用户名,该怎么办?
D
Daniel Papasian

不,你不能在没有任何上下文的情况下过滤数据。有时您希望将 SQL 查询作为输入,有时您希望将 HTML 作为输入。

您需要过滤白名单上的输入——确保数据符合您期望的某些规范。然后,您需要在使用它之前对其进行转义,具体取决于您使用它的上下文。

为 SQL 转义数据的过程 - 以防止 SQL 注入 - 与为 (X)HTML 转义数据以防止 XSS 的过程非常不同。


L
Liam

PHP 现在有了新的不错的 filter_input 函数,例如,现在有一个内置的 FILTER_VALIDATE_EMAIL 类型,可以让您从寻找“终极电子邮件正则表达式”中解放出来

我自己的过滤器类(使用 JavaScript 突出显示错误字段)可以通过 ajax 请求或普通表单发布来启动。 (见下面的例子)

/**
 *  Pork.FormValidator
 *  Validates arrays or properties by setting up simple arrays. 
 *  Note that some of the regexes are for dutch input!
 *  Example:
 * 
 *  $validations = array('name' => 'anything','email' => 'email','alias' => 'anything','pwd'=>'anything','gsm' => 'phone','birthdate' => 'date');
 *  $required = array('name', 'email', 'alias', 'pwd');
 *  $sanitize = array('alias');
 *
 *  $validator = new FormValidator($validations, $required, $sanitize);
 *                  
 *  if($validator->validate($_POST))
 *  {
 *      $_POST = $validator->sanitize($_POST);
 *      // now do your saving, $_POST has been sanitized.
 *      die($validator->getScript()."<script type='text/javascript'>alert('saved changes');</script>");
 *  }
 *  else
 *  {
 *      die($validator->getScript());
 *  }   
 *  
 * To validate just one element:
 * $validated = new FormValidator()->validate('blah@bla.', 'email');
 * 
 * To sanitize just one element:
 * $sanitized = new FormValidator()->sanitize('<b>blah</b>', 'string');
 * 
 * @package pork
 * @author SchizoDuckie
 * @copyright SchizoDuckie 2008
 * @version 1.0
 * @access public
 */
class FormValidator
{
    public static $regexes = Array(
            'date' => "^[0-9]{1,2}[-/][0-9]{1,2}[-/][0-9]{4}\$",
            'amount' => "^[-]?[0-9]+\$",
            'number' => "^[-]?[0-9,]+\$",
            'alfanum' => "^[0-9a-zA-Z ,.-_\\s\?\!]+\$",
            'not_empty' => "[a-z0-9A-Z]+",
            'words' => "^[A-Za-z]+[A-Za-z \\s]*\$",
            'phone' => "^[0-9]{10,11}\$",
            'zipcode' => "^[1-9][0-9]{3}[a-zA-Z]{2}\$",
            'plate' => "^([0-9a-zA-Z]{2}[-]){2}[0-9a-zA-Z]{2}\$",
            'price' => "^[0-9.,]*(([.,][-])|([.,][0-9]{2}))?\$",
            '2digitopt' => "^\d+(\,\d{2})?\$",
            '2digitforce' => "^\d+\,\d\d\$",
            'anything' => "^[\d\D]{1,}\$"
    );
    private $validations, $sanatations, $mandatories, $errors, $corrects, $fields;
    

    public function __construct($validations=array(), $mandatories = array(), $sanatations = array())
    {
        $this->validations = $validations;
        $this->sanitations = $sanitations;
        $this->mandatories = $mandatories;
        $this->errors = array();
        $this->corrects = array();
    }

    /**
     * Validates an array of items (if needed) and returns true or false
     *
     */
    public function validate($items)
    {
        $this->fields = $items;
        $havefailures = false;
        foreach($items as $key=>$val)
        {
            if((strlen($val) == 0 || array_search($key, $this->validations) === false) && array_search($key, $this->mandatories) === false) 
            {
                $this->corrects[] = $key;
                continue;
            }
            $result = self::validateItem($val, $this->validations[$key]);
            if($result === false) {
                $havefailures = true;
                $this->addError($key, $this->validations[$key]);
            }
            else
            {
                $this->corrects[] = $key;
            }
        }
    
        return(!$havefailures);
    }

    /**
     *
     *  Adds unvalidated class to thos elements that are not validated. Removes them from classes that are.
     */
    public function getScript() {
        if(!empty($this->errors))
        {
            $errors = array();
            foreach($this->errors as $key=>$val) { $errors[] = "'INPUT[name={$key}]'"; }

            $output = '$$('.implode(',', $errors).').addClass("unvalidated");'; 
            $output .= "new FormValidator().showMessage();";
        }
        if(!empty($this->corrects))
        {
            $corrects = array();
            foreach($this->corrects as $key) { $corrects[] = "'INPUT[name={$key}]'"; }
            $output .= '$$('.implode(',', $corrects).').removeClass("unvalidated");';   
        }
        $output = "<script type='text/javascript'>{$output} </script>";
        return($output);
    }


    /**
     *
     * Sanitizes an array of items according to the $this->sanitations
     * sanitations will be standard of type string, but can also be specified.
     * For ease of use, this syntax is accepted:
     * $sanitations = array('fieldname', 'otherfieldname'=>'float');
     */
    public function sanitize($items)
    {
        foreach($items as $key=>$val)
        {
            if(array_search($key, $this->sanitations) === false && !array_key_exists($key, $this->sanitations)) continue;
            $items[$key] = self::sanitizeItem($val, $this->validations[$key]);
        }
        return($items);
    }


    /**
     *
     * Adds an error to the errors array.
     */ 
    private function addError($field, $type='string')
    {
        $this->errors[$field] = $type;
    }

    /**
     *
     * Sanitize a single var according to $type.
     * Allows for static calling to allow simple sanitization
     */
    public static function sanitizeItem($var, $type)
    {
        $flags = NULL;
        switch($type)
        {
            case 'url':
                $filter = FILTER_SANITIZE_URL;
            break;
            case 'int':
                $filter = FILTER_SANITIZE_NUMBER_INT;
            break;
            case 'float':
                $filter = FILTER_SANITIZE_NUMBER_FLOAT;
                $flags = FILTER_FLAG_ALLOW_FRACTION | FILTER_FLAG_ALLOW_THOUSAND;
            break;
            case 'email':
                $var = substr($var, 0, 254);
                $filter = FILTER_SANITIZE_EMAIL;
            break;
            case 'string':
            default:
                $filter = FILTER_SANITIZE_STRING;
                $flags = FILTER_FLAG_NO_ENCODE_QUOTES;
            break;
             
        }
        $output = filter_var($var, $filter, $flags);        
        return($output);
    }
    
    /** 
     *
     * Validates a single var according to $type.
     * Allows for static calling to allow simple validation.
     *
     */
    public static function validateItem($var, $type)
    {
        if(array_key_exists($type, self::$regexes))
        {
            $returnval =  filter_var($var, FILTER_VALIDATE_REGEXP, array("options"=> array("regexp"=>'!'.self::$regexes[$type].'!i'))) !== false;
            return($returnval);
        }
        $filter = false;
        switch($type)
        {
            case 'email':
                $var = substr($var, 0, 254);
                $filter = FILTER_VALIDATE_EMAIL;    
            break;
            case 'int':
                $filter = FILTER_VALIDATE_INT;
            break;
            case 'boolean':
                $filter = FILTER_VALIDATE_BOOLEAN;
            break;
            case 'ip':
                $filter = FILTER_VALIDATE_IP;
            break;
            case 'url':
                $filter = FILTER_VALIDATE_URL;
            break;
        }
        return ($filter === false) ? false : filter_var($var, $filter) !== false ? true : false;
    }       
    


}

当然,请记住,您还需要根据您使用的数据库类型进行 sql 查询转义(例如,mysql_real_escape_string() 对于 sql server 是无用的)。您可能希望在适当的应用程序层(如 ORM)自动处理此问题。另外,如上所述:要输出到 html,请使用其他 php 专用函数,例如 htmlspecialchars ;)

为了真正允许带有类似剥离类和/或标签的 HTML 输入,取决于专用的 xss 验证包之一。不要编写自己的正则表达式来解析 HTML!


这看起来可能是一个用于验证输入的方便脚本,但它与问题完全无关。
我不同意使用 ORM ,它已经超过了工程 imo。
@PHP >= 8.0 给出错误 Parse error: syntax error, unexpected '->' (T_OBJECT_OPERATOR)
@Reham Fahmy:此代码来自 2008 年。现在是 2022 年。不要使用这个。使用框架。
Y
Your Common Sense

不,那里没有。

首先,SQL 注入是一个输入过滤问题,而 XSS 是一个输出转义问题——因此您甚至不会在代码生命周期中同时执行这两个操作。

基本经验法则

SQL查询,绑定参数

使用 strip_tags() 过滤掉不需要的 HTML

使用 htmlspecialchars() 转义所有其他输出,并注意此处的第二个和第三个参数。


因此,当您知道输入具有要分别删除或转义的 HTML 时,您只使用 strip_tags() 或 htmlspecialchars() - 您没有将其用于任何安全目的,对吧?此外,当您进行绑定时,它对 Bobby Tables 之类的东西有什么作用? "Robert'); DROP TABLE Students;--" 它只是逃避引号吗?
如果您有将进入数据库并稍后显示在网页上的用户数据,那么它通常不是读取比写入更多吗?对我来说,在存储之前过滤一次(作为输入)更有意义,而不是每次显示时都必须过滤它。我是否遗漏了什么,或者是否有一群人在这个和公认的答案中投票支持不必要的性能开销?
对我来说最好的答案。如果您问我,它很短,可以很好地解决问题。是否可以通过 $_POST 或 $_GET 以某种方式通过注入来攻击 PHP,或者这是不可能的?
哦,是的,$post 和 $get 数组接受所有字符,但是如果允许在发布的 php 页面中枚举字符,则其中一些字符可以用来对付你。因此,如果您不逃避封装字符(如“、'和`),它可能会打开一个攻击向量。` 字符经常被遗漏,可用于形成命令行执行黑客。卫生将防止用户输入黑客攻击,但不会帮助您进行 Web 应用程序防火墙黑客攻击。
Y
Your Common Sense

要解决 XSS 问题,请查看 HTML Purifier。它是相当可配置的,并且有不错的记录。

对于 SQL 注入攻击,解决方案是使用准备好的语句。 PDO library 和 mysqli 扩展支持这些。


没有“最好的方法”来做一些像清理输入这样的事情。使用一些库,html 净化器很好。这些库已被多次抨击。所以它比你自己想出的任何东西都更防弹
wordpress 的问题在于它不一定是导致数据库泄露的 php-sql 注入攻击。错过存储 xml 查询揭示秘密的数据的编程插件更成问题。
n
nik7

PHP 5.2 引入了 filter_var 函数。

它支持大量的 SANITIZEVALIDATE 过滤器。


M
Mark Martin

使用 PHP 清理用户输入的方法:

使用现代版本的 MySQL 和 PHP。

显式设置字符集: $mysqli->set_charset("utf8");manual $pdo = new PDO('mysql:host=localhost;dbname=testdb;charset=UTF8', $user, $password);manual $pdo-> exec("set names utf8");manual $pdo = new PDO("mysql:host=$host;dbname=$db", $user, $pass, array( PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO ::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8" ) );manual mysql_set_charset('utf8') [在 PHP 5.5.0 中弃用,在 PHP 7.0.0 中删除]。

$mysqli->set_charset("utf8");手册

$pdo = new PDO('mysql:host=localhost;dbname=testdb;charset=UTF8', $user, $password);manual

$pdo->exec("设置名称 utf8");manual

$pdo = new PDO("mysql:host=$host;dbname=$db", $user, $pass, array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8" ) );手动的

mysql_set_charset('utf8') [在 PHP 5.5.0 中弃用,在 PHP 7.0.0 中删除]。

使用安全字符集:选择 utf8、latin1、ascii..,不要使用易受攻击的字符集 big5、cp932、gb2312、gbk、sjis。

选择 utf8、latin1、ascii..,不要使用易受攻击的字符集 big5、cp932、gb2312、gbk、sjis。

使用空间化函数: MySQLi 准备语句: $stmt = $mysqli->prepare('SELECT * FROM test WHERE name = ? LIMIT 1'); $param = "' OR 1=1 /*"; $stmt->bind_param('s', $param); $stmt->执行(); PDO::quote() - 在输入字符串周围放置引号(如果需要)并转义输入字符串中的特殊字符,使用适合底层驱动程序的引用样式: $pdo = new PDO('mysql:host=localhost;dbname =testdb;charset=UTF8', $user, $password);显式设置字符集 $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);禁用模拟准备好的语句以防止回退到模拟 MySQL 无法准备的语句本机(以防止注入) $var = $pdo->quote("' OR 1=1 /*"); 不仅转义文字,而且还引用它(在单引号 ' 字符中) $stmt = $pdo- >query("SELECT * FROM test WHERE name = $var LIMIT 1"); PDO Prepared Statements:vs MySQLiprepared statements支持更多的数据库驱动和命名参数:$pdo = new PDO('mysql:host=localhost;dbname=testdb;charset=UTF8', $user, $password);显式设置字符集$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);禁用模拟准备好的语句以防止回退到模拟 MySQL 无法本地准备的语句(以防止注入) $stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1'); $stmt->execute(["' OR 1=1 /*"]); mysql_real_escape_string [在 PHP 5.5.0 中弃用,在 PHP 7.0.0 中删除]。 mysqli_real_escape_string 转义字符串中的特殊字符以用于 SQL 语句,同时考虑连接的当前字符集。但是推荐使用Prepared Statements,因为它们不是简单的转义字符串,一条语句会拿出一个完整的查询执行计划,包括它会使用哪些表和索引,这是一种优化的方式。在查询中的变量周围使用单引号 (' ')。

MySQLi 准备好的语句: $stmt = $mysqli->prepare('SELECT * FROM test WHERE name = ? LIMIT 1'); $param = "' OR 1=1 /*"; $stmt->bind_param('s', $param); $stmt->执行();

PDO::quote() - 在输入字符串周围放置引号(如果需要)并转义输入字符串中的特殊字符,使用适合底层驱动程序的引用样式: $pdo = new PDO('mysql:host=localhost;dbname =testdb;charset=UTF8', $user, $password);显式设置字符集 $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);禁用模拟准备好的语句以防止回退到模拟 MySQL 无法准备的语句本机(以防止注入) $var = $pdo->quote("' OR 1=1 /*"); 不仅转义文字,而且还引用它(在单引号 ' 字符中) $stmt = $pdo- >query("SELECT * FROM test WHERE name = $var LIMIT 1");

PDO Prepared Statements:vs MySQLiprepared statements支持更多的数据库驱动和命名参数:$pdo = new PDO('mysql:host=localhost;dbname=testdb;charset=UTF8', $user, $password);显式设置字符集$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);禁用模拟准备好的语句以防止回退到模拟 MySQL 无法本地准备的语句(以防止注入) $stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1'); $stmt->execute(["' OR 1=1 /*"]);

mysql_real_escape_string [在 PHP 5.5.0 中弃用,在 PHP 7.0.0 中删除]。

mysqli_real_escape_string 转义字符串中的特殊字符以用于 SQL 语句,同时考虑连接的当前字符集。但是推荐使用Prepared Statements,因为它们不是简单的转义字符串,一条语句会拿出一个完整的查询执行计划,包括它会使用哪些表和索引,这是一种优化的方式。

在查询中的变量周围使用单引号 (' ')。

检查变量是否包含您所期望的:如果您需要一个整数,请使用: ctype_digit — 检查数字字符; $value = (int) $value; $value = intval($value); $var = filter_var('0755', FILTER_VALIDATE_INT, $options);对于字符串使用: is_string() — 查找变量的类型是否为字符串 使用过滤函数 filter_var() — 使用指定过滤器过滤变量: $email = filter_var($email, FILTER_SANITIZE_EMAIL); $newstr = filter_var($str, FILTER_SANITIZE_STRING);更多预定义过滤器 filter_input() — 按名称获取特定外部变量并可选择过滤它: $search_html = filter_input(INPUT_GET, 'search', FILTER_SANITIZE_SPECIAL_CHARS); preg_match() — 执行正则表达式匹配;编写您自己的验证函数。

如果您需要一个整数,请使用: ctype_digit — 检查数字字符; $value = (int) $value; $value = intval($value); $var = filter_var('0755', FILTER_VALIDATE_INT, $options);

对于字符串使用: is_string() — 查找变量的类型是否为字符串 使用过滤函数 filter_var() — 使用指定过滤器过滤变量: $email = filter_var($email, FILTER_SANITIZE_EMAIL); $newstr = filter_var($str, FILTER_SANITIZE_STRING);更多预定义过滤器

filter_input() — 按名称获取特定的外部变量并可选择过滤它: $search_html = filter_input(INPUT_GET, 'search', FILTER_SANITIZE_SPECIAL_CHARS);

preg_match() — 执行正则表达式匹配;

编写您自己的验证函数。


H
Hamish Downer

在您拥有像 /mypage?id=53 这样的页面并且在 WHERE 子句中使用 id 的特定情况下,一个可以帮助您确保 id 绝对是整数的技巧,如下所示:

if (isset($_GET['id'])) {
  $id = $_GET['id'];
  settype($id, 'integer');
  $result = mysql_query("SELECT * FROM mytable WHERE id = '$id'");
  # now use the result
}

但当然,这只消除了一种特定的攻击,所以请阅读所有其他答案。 (是的,我知道上面的代码不是很好,但它显示了具体的防御。)


我改用 $id = intval($id) :)
强制转换整数是确保仅插入数字数据的好方法。
$id = (int)$_GET['id']$que = sprintf('SELECT ... WHERE id="%d"', $id) 也不错
也许 if (isset($_GET['id']) { if !( (int) $_GET['id'] === intval($_GET['id'] ) ) { throw new \InvalidArgumentException('Invalid page id format'); } /* use a prepared statement for insert here */ }; 可能适合您。如果我可以根据传递给它的已知模式确定参数绝对无效,我宁愿根本不进行数据库调用。
n
nik7

没有包罗万象的功能,因为需要解决多个问题。

SQL 注入 - 今天,通常每个 PHP 项目都应该通过 PHP 数据对象 (PDO) 使用准备好的语句作为最佳实践,以防止错误引用以及针对注入的全功能解决方案。它也是访问数据库的最灵活、最安全的方式。

查看(The only proper) PDO tutorial,了解您需要了解的有关 PDO 的几乎所有信息。 (衷心感谢顶级 SO 贡献者 @YourCommonSense 提供有关该主题的大量资源。)

XSS - 在...途中清理数据

HTML Purifier 已经存在了很长时间,并且仍在积极更新中。您可以使用它来清理恶意输入,同时仍然允许大量且可配置的标签白名单。与许多所见即所得的编辑器配合得很好,但对于某些用例来说可能很重。

在其他情况下,我们根本不想接受 HTML/Javascript,我发现这个简单的函数很有用(并且已经通过了针对 XSS 的多次审核): /* 防止 XSS 输入 */ function sanitizeXSS () { $_GET = filter_input_array(INPUT_GET, FILTER_SANITIZE_STRING); $_POST = filter_input_array(INPUT_POST, FILTER_SANITIZE_STRING); $_REQUEST = (array)$_POST + (array)$_GET + (array)$_REQUEST; }

XSS - 在出路时对数据进行清理...除非您保证在将数据添加到数据库之前已对其进行了正确清理,否则您需要在将其显示给用户之前对其进行清理,我们可以利用这些有用的 PHP 函数:

当您调用 echo 或 print 来显示用户提供的值时,请使用 htmlspecialchars,除非数据经过适当的安全清理并允许显示 HTML。

json_encode 是一种将用户提供的值从 PHP 提供到 Javascript 的安全方式

您是使用 exec() 或 system() 函数调用外部 shell 命令,还是使用反引号运算符?如果是这样,除了 SQL 注入和 XSS 之外,您可能还有一个额外的问题需要解决,即用户在您的服务器上运行恶意命令。如果您想转义整个命令或 escapeshellarg 转义单个参数,则需要使用 escapeshellcmd。


可以改用 mb_encode_numericentity 吗?因为它编码了一切?
@drtechno - mb_encode_numericentity 在 #3 XSS 的 htmlspecialchars 链接中讨论
据我所知,XSS 是一个输出问题,而不是输入问题。
@bam - 你是对的,只是不要错过任何一个地方!幸运的是,如果使用得当,大多数框架都会为我们处理。
T
Tony Stark

您在这里描述的是两个不同的问题:

清理/过滤用户输入数据。转义输出。

1) 应始终假定用户输入是错误的。

使用准备好的语句,或/和过滤 mysql_real_escape_string 绝对是必须的。 PHP 还内置了 filter_input,这是一个很好的起点。

2)这是一个很大的话题,它取决于输出数据的上下文。对于 HTML,有一些解决方案,例如 htmlpurifier。作为一个经验法则,总是逃避你输出的任何东西。

这两个问题都太大了,无法在一篇文章中讨论,但是有很多文章会更详细地介绍:

Methods PHP output

Safer PHP output


Y
Your Common Sense

如果您使用的是 PostgreSQL,则可以使用 pg_escape_literal() 转义来自 PHP 的输入

$username = pg_escape_literal($_POST['username']);

documentation

pg_escape_literal() 转义用于查询 PostgreSQL 数据库的文字。它返回 PostgreSQL 格式的转义文字。


pg_escape_literal() 是用于 PostgreSQL 的推荐函数。
C
Community

你永远不会清理输入。

你总是清理输出。

您应用到数据以使其安全地包含在 SQL 语句中的转换与您申请包含在 HTML 中的转换完全不同于您申请包含在 Javascript 中的转换与您申请包含在 LDIF 中的转换完全不同是与您申请包含在 CSS 中的完全不同 与您申请包含在电子邮件中的完全不同......

无论如何validate input - 决定您是否应该接受它以进行进一步处理或告诉用户它是不可接受的。但是在数据即将离开 PHP 领域之前,不要对数据的表示进行任何更改。

很久以前,有人试图发明一种万能的机制来转义数据,我们最终得到了“magic_quotes”,它不能正确地转义所有输出目标的数据,并导致不同的安装需要不同的代码才能工作。


一个问题是它并不总是数据库攻击,所有用户输入都应该受到系统的保护。不仅仅是一种语言类型。因此,在您的网站上,当您枚举 $_POST 数据时,即使使用绑定,它也可以逃逸到足以执行 shell 甚至其他 php 代码的程度。
“它并不总是数据库攻击”:“您应用到数据以使其安全地包含在 SQL 语句中的转换与那些完全不同......”
“应保护所有用户输入不受系统影响”:不应该保护系统不受用户输入影响。
好吧,我用完了单词,但是是的,需要防止输入影响系统操作。澄清这一点...
输入和输出都应该进行消毒。
J
Jon Winstanley

避免在清理输入和转义数据时出错的最简单方法是使用 PHP 框架,如 SymfonyNette 等或该框架的一部分(模板引擎、数据库层、ORM)。

Twig 或 Latte 等模板引擎默认启用输出转义 - 如果您已根据上下文(网页的 HTML 或 Javascript 部分)正确转义输出,则无需手动解决。

框架会自动清理输入,你不应该直接使用 $_POST、$_GET 或 $_SESSION 变量,而是通过路由、会话处理等机制。

对于数据库(模型)层,有像 Doctrine 这样的 ORM 框架或像 Nette Database 这样的 PDO 包装器。

您可以在此处阅读更多相关信息 - What is a software framework?


u
user138720

只是想在输出转义的主题上添加它,如果您使用 php DOMDocument 来制作您的 html 输出,它将在正确的上下文中自动转义。属性 (value="") 和 <span> 的内部文本不相等。为安全防范 XSS,请阅读以下内容:OWASP XSS Prevention Cheat Sheet


T
Till

有过滤器扩展(howto-linkmanual),它适用于所有 GPC 变量。不过,这不是一件万能的事情,您仍然必须使用它。


A
Anmol Mourya

使用此修剪空白并删除不可打印的字符

$data = trim(preg_replace('/[[:^print:]]/', '', $data));