如何防范XSS
翻译
原文:https://portswigger.net/web-security/cross-site-scripting/preventing
- name: 翻译
desc: 原文:https://portswigger.net/web-security/cross-site-scripting/preventing
bgColor: '#F0DFB1'
textColor: 'green'
2
3
4
# 如何防范XSS
在本节中,我们将介绍一些防范跨站脚本 (opens new window)漏洞的一般原则,以及使用各种常见技术来防范XSS (opens new window)攻击的方法。
跨站脚本防护 通常可以通过两层防御来实现:
你可以使用 Burp Scanner 扫描你的网站,以查找包括 XSS 在内的众多安全漏洞。Burp 的尖端扫描逻辑复刻了熟练的攻击者行为,并能够实现相应的高覆盖率 XSS 漏洞。你可以使用 Burp Scanner 来确认你所做的 XSS 防御是否有效。
了解有关Burp Scanner的更多信息 (opens new window)
# 1对输出数据进行编码
在将用户可控的数据写入页面之前,应该直接应用编码,你将要写入的上下文决定了需要使用哪种编码。例如,JavaScript 字符串中的值需要与 HTML 上下文中的值 使用不同类型的转义。
在 HTML 上下文中,你应该将非白名单的值转换为 HTML 实体:
<
转换为:<
>
转换为:>
在 JavaScript 字符串上下文中,非字母数字值应该是 Unicode 转义的:
<
转换为:\u003c
>
转换为:\u003e
有时,你需要以正确的顺序应用多层编码。例如,在事件处理程序中,若要安全地嵌入用户输入,需要同时处理 JavaScript 上下文和 HTML 上下文。首先,你需要对输入进行 Unicode 转义,然后对它进行 HTML 编码:
<a href="#" onclick="x='这个字符串需要两层转义'">test</a>
# 2在接收时验证输入
编码可能是 XSS 防御中最重要的一条线,但它并不足以在每个上下文中防止 XSS 漏洞。你还应该在首次接收用户输入时,尽可能严格地验证输入。
输入验证的示例包括:
- 如果用户提交了将在响应中返回的 URL,则验证它是否以安全的协议(如 HTTP 和 HTTPS)开头。否则,可能会有人使用有害协议(如
javascript
或data
)来利用你的网站。 - 如果用户提供了一个预期为数值的值,则验证该值是否包含实际的整数。
- 验证输入是否仅包含一组预期的字符集。
理想情况下,验证输入的有效方法是——通过阻止无效输入来实现。另一种方法——尝试清理无效输入,但这种方法更容易出错,应尽可能避免使用。
# 2.1白名单vs黑名单
验证输入 通常应该使用白名单,而不是黑名单。例如,与其列出所有的有害协议(javascript
,data
等),不如简单地列出安全协议(HTTP,HTTPS)并禁止列表中未列出的任何内容。这将确保你的防御 在出现新的有害协议时不会中断,这使其不容易受到 试图混淆无效值 以逃避黑名单的攻击。
# 3允许“安全”的HTML
尽可能不允许用户提交 HTML 标签,但有时这是业务需求。例如,博客网站可能允许发布某些包含有限 HTML 标签的评论。
经典的方法,尝试过滤掉潜在有害的标签和 JavaScript。你可以尝试使用安全标签和属性的白名单来实现这一点,但由于浏览器解析引擎的差异 和 变种 XSS 等怪异载荷,这种方法极难安全地实现。
最好的选择,使用某些 JavaScript 库,这些库可以在用户浏览器中执行过滤和编码,例如 DOMPurify (opens new window)。一些库允许用户以 markdown 格式提供内容,并将 markdown 转换为 HTML。但不幸的是,所有这些库时不时都会出现 XSS 漏洞,因此这不是一个完美的解决方案。如果你确实使用了其中一个库,则应密切监控该库的安全更新。
# 4如何使用模板引擎防范XSS
许多现代网站使用服务器端模板引擎(如 Twig 和 Freemarker)在 HTML 中嵌入动态内容。它们通常定义自己的转义功能。例如,在 Twig 中,你可以使用e()
过滤器,并使用一个参数来定义上下文:
{{ user.firstname | e('html') }}
其他一些模板引擎,如 Jinja 和 React,默认情况下会转义动态内容,这有效地防止了大多数 XSS 的出现。
我们建议你,在评估是否使用给定的模板引擎或框架时,仔细检查转义特性。
笔记
如果你直接将用户输入拼接到模板字符串中,则容易受到服务器端模板注入 (opens new window)的攻击,这通常比 XSS 更严重。
# 5如何在PHP中防范XSS
在 PHP 中,有一个名为htmlentities
的内置函数可以用来编码实体。在 HTML 上下文中时,应调用此函数来转义输入。该函数具有三个参数:
- 你的输入字符串。
ENT_QUOTES
,这是一个标志,用于指定所有引号都应该编码。- 字符集,在大多数情况下应为 UTF-8。
例如:
<?php echo htmlentities($input, ENT_QUOTES, 'UTF-8');?>
在 JavaScript 字符串上下文中时,你需要对输入进行 Unicode 转义,就像前面提到的那样。不幸的是,PHP 没有提供对字符串进行 Unicode 转义的内置 API。以下是一些在 PHP 中执行此操作的示例代码:
<?php
function jsEscape($str) {
$output = '';
$str = str_split($str);
for($i=0;$i<count($str);$i++) {
$chrNum = ord($str[$i]);
$chr = $str[$i];
if($chrNum === 226) {
if(isset($str[$i+1]) && ord($str[$i+1]) === 128) {
if(isset($str[$i+2]) && ord($str[$i+2]) === 168) {
$output .= '\u2028';
$i += 2;
continue;
}
if(isset($str[$i+2]) && ord($str[$i+2]) === 169) {
$output .= '\u2029';
$i += 2;
continue;
}
}
}
switch($chr) {
case "'":
case '"':
case "\n";
case "\r";
case "&";
case "\\";
case "<":
case ">":
$output .= sprintf("\\u%04x", $chrNum);
break;
default:
$output .= $str[$i];
break;
}
}
return $output;
}
?>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
下面是如何在 PHP 中使用jsEscape
函数的方法:
<script>x = '<?php echo jsEscape($_GET['x'])?>';</script>
或者,你可以改为使用模板引擎。
# 6如何在客户端JavaScript中防范XSS
要在 JavaScript 的 HTML 上下文中转义用户输入,你需要自己的 HTML 编码器,因为 JavaScript 不提供编码 HTML 的内置 API。下面是一些将字符串转换为 HTML 实体的 JavaScript 示例代码:
function htmlEncode(str){
return String(str).replace(/[^\w. ]/gi, function(c){
return '&#'+c.charCodeAt(0)+';';
});
}
2
3
4
5
然后,你将按如下方式使用此函数:
<script>document.body.innerHTML = htmlEncode(untrustedValue)</script>
如果你的输入是在 JavaScript 字符串中,则需要一个执行 Unicode 转义的编码器。下面是一个示例 Unicode 编码器:
function jsEscape(str){
return String(str).replace(/[^\w. ]/gi, function(c){
return '\\u'+('0000'+c.charCodeAt(0).toString(16)).slice(-4);
});
}
2
3
4
5
然后,你将按如下方式使用此函数:
<script>document.write('<script>x="'+jsEscape(untrustedValue)+'";<\/script>')</script>
# 7如何在jQuery中防范XSS
jQuery 中最常见的 XSS 形式是将用户输入传递给 jQuery 选择器。Web 开发人员经常使用location.hash
并将其传递给选择器,这将导致 XSS,因为 jQuery 会呈现 HTML。
jQuery 官方意识到了这个问题,并修补了他们的选择器逻辑,以检查输入是否以 hash 开头。现在,jQuery 只会在第一个字符是<
时呈现 HTML。如果你想将不受信任的数据传递给 jQuery 选择器,请确保使用上面的jsEscape
函数正确地转义了该值。
# 8使用内容安全策略(CSP)缓解XSS
内容安全策略 (opens new window)(CSP)是防范跨站脚本攻击的最后一道防线。如果前面执行的 XSS 防护都失败了,则可以使用 CSP 限制攻击者的行为来缓解 XSS。
CSP 允许你控制各种内容,例如 是否可以加载外部脚本 以及是否执行内联脚本。若要部署 CSP,需要包含一个名为Content-Security-Policy
的 HTTP 响应标头,其具有一个包含策略的值。
CSP 示例如下:
default-src 'self'; script-src 'self'; object-src 'none'; frame-src 'none'; base-uri 'none';
此策略指定了 只能从与主页相同的源加载图像和脚本等资源。因此,即使攻击者可以成功注入 XSS 有效负载,他们也只能从当前页面加载资源。这大大降低了攻击者利用 XSS 漏洞的机会。
如果你需要加载外部资源,请确保你所加载的脚本,不会帮助攻击者利用站点。例如,如果你将某些域列入白名单,则攻击者可以从这些域加载任何脚本。如果可以,请在你自己的域中托管资源。
如果无法做到这一点,则可以使用 基于哈希或随机数 的策略来允许不同域上的脚本。nonce 是一个随机字符串,被作为脚本或资源的属性进行添加,仅当随机字符串与服务器生成的字符串匹配时才会执行。攻击者无法猜测随机化的字符串,因此无法调用含有 有效随机数的脚本或资源,因此资源将不会被执行。