如何防止 HTML/PHP 的 XSS?

How can I prevent XSS with HTML/PHP?

提问人:TimTim 提问时间:1/4/2010 最后编辑:Peter MortensenTimTim 更新时间:8/3/2023 访问量:333907

问:

如何防止仅使用 HTML 和 PHP 的 XSS(跨站点脚本)?

我看过很多关于这个主题的其他帖子,但我还没有找到一篇清晰简洁地说明如何实际防止 XSS 的文章。

PHP XSS的

评论

3赞 Michael Mior 5/17/2011
请注意,这并不能解决您可能希望将用户输入用作 HTML 属性的情况。例如,图像的源 URL。这不是一个常见的情况,但很容易被遗忘。
0赞 baptx 7/20/2019
@MichaelMior这里是防止 XSS in 或 HTML 属性的解决方案:stackoverflow.com/questions/19047119/...hrefsrc
0赞 XCore 4/15/2020
这里有一篇很好的文章解释了XSS以及如何用不同的语言(包括.PHP)来防止它。

答:

20赞 James Kolpack 1/4/2010 #1

最重要的步骤之一是在处理和/或呈现回浏览器之前清理任何用户输入。PHP有一些可以使用的“过滤器”功能。

XSS 攻击通常具有的形式是插入指向某些异地 JavaScript 代码的链接,该代码包含对用户的恶意意图。在这里阅读更多关于它的信息。

您还需要测试您的网站。看起来Easy XSS现在是要走的路。

评论

1赞 TimTim 1/4/2010
我需要什么来确保我准确地清理输入。有没有一个特定的字符/字符串是我必须注意的?
36赞 zombat 1/4/2010
@TimTim - 没有。所有用户输入都应始终被视为本质上是敌对的。
0赞 Samuel Dauzon 10/4/2018
此外,内部数据(员工、系统管理员等)可能不安全。您应该识别和监视(带有日志日期和用户)显示的解释数据。
348赞 Alix Axel 1/4/2010 #2

基本上,每当你想在 HTML 上下文中向浏览器输出一些东西时,你都需要使用函数 htmlspecialchars()。

使用此函数的正确方法是这样的:

echo htmlspecialchars($string, ENT_QUOTES, 'UTF-8');

Google Code University 也有这些关于网络安全的非常有教育意义的视频:

评论

12赞 Alix Axel 1/4/2010
@TimTim:在大多数情况下,是的。但是,当您需要允许 HTML 输入时,事情会变得有点棘手,如果是这种情况,我建议您使用类似 htmlpurifier.org
0赞 TimTim 1/4/2010
@Alix Axel,那么你的答案是使用 htmlspecialchars 还是使用 htmlpurifier.org
4赞 Alix Axel 1/4/2010
如果需要接受 HTML 输入,请使用 HTML Purifier,如果没有,请使用 .htmlspecialchars()
9赞 kiranvj 11/16/2012
htmlspecialchars 还是 htmlentities ?点击这里查看 stackoverflow.com/questions/46483/...
4赞 bronze man 5/30/2014
大多数时候它是正确的,但事实并非如此简单。您应该考虑将不受信任的字符串放入 HTML、Js、Css 中,并考虑将不受信任的 HTML 放入 HTML。看看这个:owasp.org/index.php/......
12赞 Scott Arciszewski 7/30/2015 #3

按优先顺序:

  1. 如果您使用的是模板引擎(例如 Twig、Smarty、Blade),请检查它是否提供上下文相关转义。我从经验中知道 Twig 确实如此。{{ var|e('html_attr') }}
  2. 如果要允许 HTML,请使用 HTML Purifier。即使你认为你只接受 Markdown 或 ReStructuredText,你仍然希望净化 HTML 这些标记语言的输出。
  3. 否则,请使用并确保文档的其余部分使用与 相同的字符集。在大多数情况下,是所需的字符集。htmlentities($var, ENT_QUOTES | ENT_HTML5, $charset)$charset'UTF-8'

此外,请确保在输出时转义,而不是在输入时转义

评论

0赞 Lakshminarayanan Guptha 4/14/2021
此注释在输出上是否仍然有效,而不是在输入上?考虑到跨应用程序堆栈处理输入的多种技术,您不认为输入可能是恶意的吗?
0赞 Scott Arciszewski 4/19/2021
是的,它仍然有效。您应该按原样存储它,然后在显示它时转义。如果需要更新输出转义代码以缓解漏洞,最好存储未更改、未损坏的输入以更新单元测试。
3赞 Abdo-Host 10/2/2015 #4
<?php
function xss_clean($data)
{
// Fix &entity\n;
$data = str_replace(array('&amp;','&lt;','&gt;'), array('&amp;amp;','&amp;lt;','&amp;gt;'), $data);
$data = preg_replace('/(&#*\w+)[\x00-\x20]+;/u', '$1;', $data);
$data = preg_replace('/(&#x*[0-9A-F]+);*/iu', '$1;', $data);
$data = html_entity_decode($data, ENT_COMPAT, 'UTF-8');

// Remove any attribute starting with "on" or xmlns
$data = preg_replace('#(<[^>]+?[\x00-\x20"\'])(?:on|xmlns)[^>]*+>#iu', '$1>', $data);

// Remove javascript: and vbscript: protocols
$data = preg_replace('#([a-z]*)[\x00-\x20]*=[\x00-\x20]*([`\'"]*)[\x00-\x20]*j[\x00-\x20]*a[\x00-\x20]*v[\x00-\x20]*a[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2nojavascript...', $data);
$data = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*v[\x00-\x20]*b[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2novbscript...', $data);
$data = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*-moz-binding[\x00-\x20]*:#u', '$1=$2nomozbinding...', $data);

// Only works in IE: <span style="width: expression(alert('Ping!'));"></span>
$data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?expression[\x00-\x20]*\([^>]*+>#i', '$1>', $data);
$data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?behaviour[\x00-\x20]*\([^>]*+>#i', '$1>', $data);
$data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:*[^>]*+>#iu', '$1>', $data);

// Remove namespaced elements (we do not need them)
$data = preg_replace('#</*\w+:\w[^>]*+>#i', '', $data);

do
{
    // Remove really unwanted tags
    $old_data = $data;
    $data = preg_replace('#</*(?:applet|b(?:ase|gsound|link)|embed|frame(?:set)?|i(?:frame|layer)|l(?:ayer|ink)|meta|object|s(?:cript|tyle)|title|xml)[^>]*+>#i', '', $data);
}
while ($old_data !== $data);

// we are done...
return $data;
}

评论

7赞 CrabLab 3/12/2017
你不应该使用它对你的输入。owasp.org/index.php/PHP_Security_Cheat_Sheet#Code_Injectionpreg_replaceeval
3赞 chris 11/23/2015 #5

您还可以通过以下方式设置一些与 XSS 相关的 HTTP 响应标头:header(...)

X-XSS-保护“1;mode=block”

可以肯定的是,浏览器 XSS 保护模式已启用。

Content-Security-Policy “默认 src 'self';..."

以启用浏览器端内容安全性。有关内容安全策略 (CSP) 的详细信息,请参阅此内容:

内容安全策略参考

特别是将 CSP 设置为阻止内联脚本和外部脚本源有助于对抗 XSS。

有关 Web 应用程序安全性的一般 HTTP 响应标头,请查看 OWASPhttps://www.owasp.org/index.php/List_of_useful_HTTP_headers

评论

0赞 Peter Mortensen 8/3/2023
最后一个链接已断开 (404)。
-2赞 Pablo 5/3/2016 #6

在 PHP 中使用 htmlspecialchars()。在 HTML 中,尽量避免使用:

element.innerHTML = “…”; element.outerHTML = “…”; document.write(…); document.writeln(…);

where 由用户控制var

显然也尽量避免. 如果你必须使用它们中的任何一个,那么请尝试 JavaScript 转义它们,HTML 转义它们,你可能需要做更多的事情,但对于基础知识来说,这应该足够了。eval(var)

19赞 Matt S 8/5/2017 #7

我将其作为即将离线的 SO 文档测试版的综合参考进行交叉发布。

问题

跨站点脚本是 Web 客户端意外执行远程代码。如果任何 Web 应用程序从用户那里获取输入并将其直接输出到网页上,则它可能会将自己暴露给 XSS。如果输入包含 HTML 或 JavaScript,则当 Web 客户端呈现此内容时,可以执行远程代码。

例如,如果第三方网站包含 JavaScript 文件:

// http://example.com/runme.js
document.write("I'm running");

PHP应用程序直接输出一个传递给它的字符串:

<?php
echo '<div>' . $_GET['input'] . '</div>';

如果未选中的 GET 参数包含,则 PHP 脚本的输出将为:<script src="http://example.com/runme.js"></script>

<div><script src="http://example.com/runme.js"></script></div>

第三方 JavaScript 代码将运行,用户将在网页上看到“我正在运行”。

溶液

作为一般规则,永远不要相信来自客户端的输入。每个 GET 参数、POSTPUT 内容以及 cookie 值都可以是任何值,因此应进行验证。输出这些值中的任何一个时,请对它们进行转义,这样它们就不会以意外的方式被评估。

请记住,即使在最简单的应用程序中,数据也可以四处移动,并且很难跟踪所有来源。因此,最佳做法是始终对输出进行转义。

PHP 提供了几种根据上下文转义输出的方法。

筛选函数

PHP 的过滤函数允许以多种方式清理或验证 PHP 脚本的输入数据。它们在保存或输出客户端输入时很有用。

HTML 编码

htmlspecialchars() 会将任何“HTML 特殊字符”转换为它们的 HTML 编码,这意味着它们不会被处理为标准 HTML。要使用此方法修复我们之前的示例,请执行以下操作:

<?php
echo '<div>' . htmlspecialchars($_GET['input']) . '</div>';
// or
echo '<div>' . filter_input(INPUT_GET, 'input', FILTER_SANITIZE_SPECIAL_CHARS) . '</div>';

将输出:

<div>&lt;script src=&quot;http://example.com/runme.js&quot;&gt;&lt;/script&gt;</div>

标签内的所有内容都不会被浏览器解释为 JavaScript 标签,而是被解释为一个简单的文本节点。用户将安全地看到:<div>

<script src="http://example.com/runme.js"></script>

URL 编码

当输出动态生成的 URL 时,PHP 提供了 urlencode() 函数来安全地输出有效的 URL。因此,例如,如果用户能够输入成为另一个 GET 参数一部分的数据:

<?php
$input = urlencode($_GET['input']);
// or
$input = filter_input(INPUT_GET, 'input', FILTER_SANITIZE_URL);
echo '<a href="http://example.com/page?input="' . $input . '">Link</a>';

任何恶意输入都将转换为编码的 URL 参数。

使用专门的外部库或 OWASP AntiSamy 列表

有时您需要发送 HTML 或其他类型的代码输入。您需要维护授权词(白名单)和未经授权(黑名单)列表。

您可以在 OWASP AntiSamy 网站上下载标准列表。每个列表都适合特定类型的交互(eBay API、TinyMCE 等)。它是开源的。

有一些库可以过滤 HTML 并防止 XSS 攻击,适用于一般情况,并且至少与 AntiSamy 列表一样好,并且非常易于使用。 例如,您有 HTML Purifier

评论

0赞 Flemming Lemche 1/2/2023
是否正确理解,只有当您立即将数据从表单输出到页面而没有任何验证或清理时,才会执行 xss 攻击?如果你在 php 代码中运行 $input = htmlspecialchars($_POST['input']),你可以安全地输出 $input ?
13赞 webaholik 12/18/2017 #8

许多框架以各种方式帮助处理 XSS。当您自己推出或存在一些 XSS 问题时,我们可以利用 filter_input_array(在 PHP 5 >= 5.2.0、PHP 7 中可用。 我通常会将此代码片段添加到我的 SessionController 中,因为所有调用都会在任何其他控制器与数据交互之前通过该代码段。通过这种方式,所有用户输入都会在 1 个中心位置进行清理。如果这是在项目开始时或数据库中毒之前完成的,则在输出时应该不会有任何问题...阻止垃圾输入,垃圾输出。

/* Prevent XSS input */
$_GET   = filter_input_array(INPUT_GET, FILTER_SANITIZE_STRING);
$_POST  = filter_input_array(INPUT_POST, FILTER_SANITIZE_STRING);
/* I prefer not to use $_REQUEST...but for those who do: */
$_REQUEST = (array)$_POST + (array)$_GET + (array)$_REQUEST;

以上将删除所有 HTML 和脚本标签。如果您需要基于白名单的允许安全标签的解决方案,请查看 HTML Purifier


如果你的数据库已经中毒,或者你想在输出时处理XSS,OWASP建议为创建一个自定义的包装函数,并在输出用户提供的值的任何地方使用它:echo

//xss mitigation functions
function xssafe($data,$encoding='UTF-8')
{
   return htmlspecialchars($data,ENT_QUOTES | ENT_HTML401,$encoding);
}
function xecho($data)
{
   echo xssafe($data);
}

评论

1赞 Nicky Kouffeld 12/30/2022
有趣的解决方案,以这种方式覆盖所有参数。感谢分享!
-1赞 Marco Concas 11/18/2019 #9

保护 HTML 输出的最佳方法是使用 htmlentities() 函数。

例:

htmlentities($target, ENT_QUOTES, 'UTF-8');