4.7 重定向相关的安全隐患
Web 应用中有时会重定向至外界指定的 URL。典型案例为,在登录页面的参数中指定 URL,登录成功后再重定向至该 URL。比如使用以下 URL 登录 Google 后,就会重定向到 continue= 指定的 URL(此处为 Gmail)40 。
40 写作本书时已进行过确认,但将来可能会有所更改。
https://www.google.com/accounts/ServiceLogin?continue=https://mail.google.com/mail/
重定向处理时产生的安全隐患有如下几种,而且它们都会招致被动攻击。
- 自由重定向漏洞
- HTTP 消息头注入漏洞
接下来,本节将对以上两种安全隐患进行详细说明。
4.7.1 自由重定向漏洞
概要
刚才已经提到,有些 Web 应用中提供了能够重定向到参数指定的 URL 的功能,该重定向功能就被称为重定向器(Redirector)。
其中,能够重定向至任意域名的重定向器叫作自由重定向(Open Redirect)。自由重定向可能会导致用户在不知情的情况下被带到其他域名的网站,从而遭到钓鱼式攻击(Phishing)。
自由重定向示例
http://example.jp/?continue=http://trap.example.com/
通过以上 URL 跳转至 http://trap.example.com/
钓鱼式攻击的常见手段为,将用户带到伪装成著名网站的恶意网站,并诱使用户输入个人信息。
如果用户信赖的网站存在自由重定向漏洞,用户就可能会在不知不觉中被诱导到恶意网站,却自以为还在浏览自己信赖的网站。此时,即便是戒心很重的用户也会比较轻易地输入自己的个人信息等重要内容。而自由重定向漏洞就常被用于此类狡猾的钓鱼式攻击。
另外,如果软件或设备驱动程序的下载网站存在自由重定向漏洞,就有可能被不法分子利用来散布恶意软件(非法程序)。
为了防范自由重定向漏洞,应该重新评估“外界能够指定重定向目标 URL”的功能是否真的不可或缺,并尽可能将重定向的目标固定。如果实在不能固定重定向的目标,就需要将重定向的目标限制在允许的域名范围内。
自由重定向漏洞总览
攻击手段与影响
接下来我们就来看一下针对自由重定向漏洞的典型攻击模式及其影响。下面是具备重定向功能的密码认证的示例脚本。
代码清单 /47/47-001.php
<?php
$url = @$_GET['url'];
if (! isset($url)) {
$url = 'http://example.jp/47/47-003.php';
}
?>
<html>
<head><title> 请登录 </title></head>
<body>
<form action="47-002.php" method="POST">
用户名 <input type="text" name="id"><br>
密码 <input type="password" name="pwd"><br>
<input type="hidden" name="url"
value="<?php echo htmlspecialchars($url, ENT_COMPAT, 'UTF-8') ?>">
<input type="submit" value=" 登录 ">
</form>
</body>
</html>
代码清单 /47/47-002.php
<?php
$id = isset($_POST['id']) ? $_POST['id'] : '';
$pwd = isset($_POST['pwd']) ? $_POST['pwd'] : '';
$url = isset($_POST['url']) ? $_POST['url'] : '';
// 只要输入了用户名和密码就能成功登录
if ($id != '' && $pwd != '') {
// 重定向至指定的 URL
header('Location: ' . $url);
exit();
}
// 以下为登录失败的情况
?>
<body>
用户名或密码错误
<a href="47-001.php"> 重新登录 </a>
</body>
代码清单 /47/47-003.php
<html>
<head><title> 认证成功 </title></head>
<body>
登录成功
</body>
</html>
47-001.php、47-002.php、47-003.php 为极度简化后的登录脚本。由于仅用于演示,47-002.php 中没有检验用户名和密码。登录认证成功后会重定向至 POST 参数 url
所指定的 URL。重定向处理的内容即为输出 Location 消息头。图 4-60 展示了页面的跳转。
图 4-60 重定向范例的页面跳转
正常情况下,重定向目标应该为 47-003.php。但是,如果攻击者精心准备了能跳转到恶意网站的 URL 来让用户点击会怎样呢?
假设此处恶意网站的 URL 为 http://trap.example.com/47/47-900.php 。以下为 47-900.php 的源代码。
代码清单 /47/47-900.php
<html>
<head><title> 登录错误 </title></head>
<body>
用户名或密码错误。请再次登录。
<form action="47-901.php" method="POST">
用户名 <input type="text" name="id"><br>
密码 <input type="password" name="pwd"><br>
<input type="submit" value=" 登录 ">
</form>
</body>
</html>
攻击者会给用户发送邮件或在用户的博客中发表评论,想方设法地使用户浏览以下 URL。
http://example.jp/47/47-001.php?url=http://trap.example.com/47/47-900.php
由于域名没有问题,并且 HTTPS 的情况下证书也没有出错 41 ,因此多数用户都会毫无防备地输入用户名和密码。这时,应用程序在 47-002.php 认证成功后,就会跳转到图 4-61 所示的恶意网页。
41 本例中没有涉及 HTTPS。
图 4-61 恶意网页
虽然用户输入的确实是正确的用户名和密码,但看到这个页面后还是不免会产生疑惑而再次输入。由于用户已经进入到了恶意网站,因此,点击登录按钮后用户名和密码就会被发送给恶意网站,而如果随后又能跳转至正规页面(47-003.php),那么用户就在毫不知情的情况下被窃取了重要信息。
安全隐患的产生原因
自由重定向漏洞产生的原因有以下两点。
- 重定向的目标 URL 能够由外界指定
- 没有对重定向的目标域名进行校验
以上两点是 AND 条件,也就是说只有同时满足这两点时才会形成自由重定向漏洞,因此,只要使其中一项无法满足也就消除了安全隐患。
- 允许自由重定向的情况
上面讲述的都是自由重定向导致安全隐患的情况,但并非所有的自由重定向都会造成安全隐患。例如,满足以下两个条件时就不会造成安全隐患。
- 根据应用的需求本来就应该跳转至外部域名
- 用户自己清楚会跳转至外部域名
满足上述条件的一个重定向的例子就是横幅广告。虽然多数横幅广告都使用了应用内部的重定向功能,但只要用户能分辨出自己点击的是广告,那么即使有自由重定向功能也不会造成安全隐患。
对策
自由重定向漏洞的根本性防范策略有下列三项,实施时任选其一即可。
1. 固定重定向的目标 URL
2. 使用编号指定重定向的目标 URL
3. 校验重定向的目标域名
下面我们就来依次解说。
- 固定重定向的目标 URL
重新评估应用的需求,探讨是否能够固定 URL 的跳转去向,而不是由外界指定。只要能够固定重定向的目标,就能成功根除自由重定向漏洞。
- 使用编号指定重定向的目标 URL
由于某些原因而不得不采用可变的重定向目标时,可以采用“页面编号”的形式来指定目标 URL。页面编号和 URL 的对应表应该保存在外界无法访问的脚本源码或文件、数据库中。
使用此方法后外界就无法任意指定域名,因此也就消除了自由重定向漏洞。
- 校验重定向的目标域名
如果使用编号来指定重定向目标的方法也行不通,那么就只能通过校验重定向目标来防止跳转至任意域名了。然而,由于该校验处理陷阱重重,因此推荐尽量使用上面两种方法。
首先我们来看一个校验 URL 的失败案例。
失败例 1
if (mb_ereg('example\.jp', $url)) { // 校验通过
该例子虽然确保了 URL 中包含 example.jp,但是还远远不够。比如,以下包含了 example.jp 的 URL 就成功通过了验证,并使攻击得以成功。
混过校验的 URL
http://trap.example.com/example.jp .php
失败例 2
if (mb_ereg('^/', $url)) { // 校验通过
该例子确保了 URL 以
/
开头。换言之,该校验的思路为,如果只允许指定相对 URL,就能杜绝重定向至外部域名。但是,以下 URL 就能够通过该校验。
混过校验的 URL
//trap.example.com/47/47-900.php
以
//
开头的 URL 被称为“网络路径引用”,这种形式的 URL 指定主机名(FQDN)以下的内容。也就是说,该校验无法完全禁止跳转至外部域名 42 。失败例 3
if (mb_ereg('^http://example\.jp/', $url)) { // 校验通过
第 3 个失败例的正则表达式使用前方一致匹配来确保 URL 以 http://example.jp 开头。但是,如果仅进行该校验的话就有可能会招致 HTTP 消息头注入攻击。而通过 HTTP 消息头攻击,有时还能够重定向至其他域名,因此,该方法也不能完全杜绝自由重定向漏洞。
关于 HTTP 消息头注入的详情请参考下一小节。
推荐写法
if (mb_ereg('\Ahttps?://example\.jp/[-_.!~*\'();\/?:@&=+\$,%#a-zA-Z0-9]*\z', $url)) { // 校验通过
推荐写法中确保了 URL 以 http://example.jp/ 开头,并且还保证了后面仅包含能被用于 URL(URI)的字符。另外,如 4.2 节中讲述的一样,此处使用了
\A
和\z
匹配字符串的开头和结尾。而正则表达式https?
则是为了能够同时匹配 http 和 https。
42 虽然 HTTP/1.1 的规格 RFC2616 中规定了 Location 消息头中指定的 URL(URI)必须为绝对 URL(10.30 项),但主流浏览器都允许相对 URL 的形式。
专栏:警告页面
在拍卖网站及社交网站等用户输入的 URL 会以链接形式显示的网站中,攻击者通常会利用这个特性将用户诱导至钓鱼网站。
而为了防止该攻击手段,可以添加一个叫作警告页面的网页,使用户无法直接跳转至外部域名的网站。在警告页面上提醒用户即将跳转至外部网站,以此来防止钓鱼式攻击。下图为雅虎拍卖网站的警告页面。通过显示该页面让用户提高警惕,然后再跳转至外部网站。
图 4-62 雅虎拍卖网站的警告页面
重定向中也能使用警告页面。而即使是在允许重定向至外部网站的情况下,也不建议直接跳转,而是应该考虑是否能插入警告页面来防止钓鱼式攻击。
此外,由于警告页面还能够防止会话 ID 泄漏,因此在面向手机的应用中也有着广泛的应用。详情请参考 7.4 节。
4.7.2 HTTP 消息头注入
本节讲述 HTTP 消息头注入。HTTP 消息头注入漏洞除了会发生在重定向处理中,在 Cookie 输出等所有输出 HTTP 响应头的处理中也都有可能发生。
概要
HTTP 消息头注入漏洞是指在重定向或生成 Cookie 等基于外部传入的参数输出 HTTP 响应头时所产生的安全隐患。输出响应消息头时,攻击者通过在参数中插入换行符,就可以在受害人的浏览器上实现下列操作。
- 任意添加响应消息头
- 伪造响应消息体
而针对 HTTP 消息头注入漏洞实施的攻击就叫作 HTTP 消息头注入攻击。
响应头中的换行符有特殊意义,如果在输出过程中没有对外界指定的换行符进行处理,就会导致 HTTP 消息头注入漏洞产生。
Web 应用中若存在 HTTP 消息头注入漏洞,就会造成如下影响。
- 生成任意 Cookie
- 重定向至任意 URL
- 更改页面显示内容
- 执行任意 JavaScript 而造成与 XSS 同样的损害
为了防范 HTTP 消息头注入漏洞,建议不要手动生成 HTTP 消息头的输出部分,而是利用专门用于输出消息头的程序库或 API。并且还要校验组成响应消息头的字符串中是否包含换行符,如果有换行符就报错并终止处理。
HTTP 消息头注入漏洞总览
攻击手段与影响
接下来就让我们来看一下针对 HTTP 消息头注入漏洞的攻击手段及其影响。这里我们以执行重定向处理的 Perl 脚本为例进行说明。之所以用 Perl,是因为 PHP 实施了一些 HTTP 消息头注入的防范策略,很难用一个简单的例子将漏洞重现。但是,使用 PHP 同样会遭受 HTTP 消息头注入攻击,相关信息及防范策略将在本节的最后介绍。
以下 CGI 脚本的作用为接收查询字符串中 url
的值,并重定向至 url
所指定的 URL。这里还针对 URL 实施了与前面介绍的“失败例 3”同样的域名校验。
代码清单 /47/47-020.cgi
#!/usr/bin/perl
use utf8; # 指定 Perl 源码的字符编码为 UTF-8
use strict; # 指定严格的变量定义方式
use CGI qw/-no_xhtml :standard/; # 使用 CGI 模块
my $cgi = new CGI;
my $url = $cgi->param('url'); # 取得查询字符串 url
# 通过前方一致校验 URL 来防范自由重定向(不充分的防范策略)
if ($url =~ /^http:\/\/example\.jp\//) {
print "Location: $url\n\n";
exit 0;
}
## URL 不正确时的错误消息
print <<END_OF_HTML;
Content-Type: text/html; charset=UTF-8
<body>
Bad URL
</body>
END_OF_HTML
正常情况下的画面跳转如下图所示。
图 4-63 示例画面跳转
- 重定向至外部域名
下面我们使用以下 URL 执行此 CGI 脚本,首先请启动 Fiddler。这个 URL 很长,不想手动输入的话也可以从 http://example.jp/47/ 的菜单中点击“4.47-020:CGI 重定向(跳转至恶意网站)”链接。
http://example.jp/47/47-020.cgi?url=http://example.jp/%0D%0ALocation:+http://trap.example.com/47/47-900.php
这样执行之后,浏览器就会跳转到恶意网站。请注意看地址栏。
图 4-64 恶意网页
不可思议的是,明明已经对重定向的 URL 进行了前方一致的校验,为什么还会出现这种结果呢?为了查明真相,我们来使用 Fiddler 查看 HTTP 响应内容。
图 4-65 使用 Fiddler 确认 HTTP 响应
如下所示,Location 消息头指向了恶意网站,而原来的 Location 消息头却不见了。
Location: http://trap.example.com/47/47-900.php
其实,造成这个谜题的关键为,CGI 脚本里面指定的查询字符串
url
中包含了换行符(%0D%0A)。该换行符使得 CGI 脚本输出了 2 行 Location 消息头,如下所示。Location: http://example.jp/ Location: http://trap.example.com/47/47-900.php
Apache 从 CGI 脚本中接收的消息头中如果有多个 Location 消息头,Apache 就会只将最后的 Location 消息头作为响应返回,因此,原来的重定向目标就会作废,而被换行符后面指定的 URL 取而代之。
像这样,通过在参数中插入换行符而添加新的 HTTP 响应头的攻击手段就是 HTTP 消息头注入攻击,而招致 HTTP 消息头注入攻击的漏洞就叫 HTTP 消息头注入漏洞。有时为了侧重攻击手法或现象,也会将其称为 CrLf 注入攻击或 HTTP 响应截断攻击。
专栏:HTTP 响应截断攻击
HTTP 响应截断攻击(HTTP Response Splitting Attack)的攻击手段为,通过 HTTP 消息头注入生成多个 HTTP 响应,使缓存服务器(代理服务器)将伪造内容进行缓存。
HTTP/1.1 能够在一次连接中发送多个请求,而且响应也会在一个连接中被返回。于是,攻击者就会在执行 HTTP 消息头注入攻击所使用的 HTTP 请求(第 1 请求)后面,加上使服务器缓存伪造内容的 URL 所对应的 HTTP 请求(第 2 请求)。
这时,通过对第 1 请求进行 HTTP 消息头注入攻击,在 HTTP 响应消息体中插入伪造内容,缓存服务器就会将这个伪造内容误认为第 2 请求的响应而将其缓存。由于此攻击能够使用伪造物来污染缓存中的内容,因此也被称为缓存污染。
虽然单独使用 HTTP 消息头注入攻击也能达到改变页面的效果,但是那种情况下只有被攻击的用户才会受到短暂的影响。与此相对,污染缓存则可以增加受影响的用户群,并且还能够延长受影响的时间,从而使攻击的威力大增。
HTTP 响应截断的产生原因与对策与 HTTP 消息头注入相同,因此这里就不再进行详述。如果有兴趣,可以参考独立行政法人信息处理推进机构发表的《安全的 Web 网站构建方法》43 的“1.7 HTTP 消息头注入”中的“缓存服务器的缓存污染”。
- 生成任意 Cookie
这里我们依然使用 47-020.cgi 来看看 HTTP 消息头注入造成的其他影响。首先,使用以下 URL 启动 CGI 脚本,或者从菜单( http://example.jp/47/ )中点击“5. 47-020:CGI 的重定向(设置 Cookie)”链接。
http://example.jp/47/47-020.cgi?url=http://example.jp/47/47-003.php%0D%0ASet-Cookie:+SESSID=ABCD123
此时,HTTP 响应如下图的 Fiddler 界面所示。
图 4-66 通过 Fiddler 确认 HTTP 响应
将图中箭头所指的地方放大,如下所示。
Set-Cookie: SESSID=ABCD123 Location: http://example.jp/47/47-003.php
可以看出 HTTP 消息头注入攻击中添加的 Set-Cookie 消息头生效了。而随后的 HTTP 请求则如图 4-67 所示。
图 4-67 通过 Fiddler 确认随后的 HTTP 请求
同样将图中箭头所指的地方放大,如下所示。可以看出前面生成的 Cookie 确实被设置到了浏览器中。
Cookie: SESSID=ABCD123
而一旦外界能够随意生成 Cookie 值,就能配合 4.6 节介绍的会话固定攻击来针对用户发动伪装攻击。
- 显示伪造页面
通过 HTTP 消息头注入攻击还能够显示伪造页面。由于针对重定向处理页面的攻击不太容易实现 44 ,因此,这里我们选择以生成 Cookie 的 CGI 脚本为例,来示范如何显示伪造页面。
代码清单 /47/47-021.cgi
#!/usr/bin/perl use utf8; use strict; use CGI qw/-no_xhtml :standard/; use Encode qw(encode decode); my $cgi = new CGI; my $pageid = $cgi->param('pageid'); # encode 通过 encode 函数将编码转换为 UTF-8 后输出 print encode('UTF-8', <<END_OF_HTML); Content-Type: text/html; charset=UTF-8 Set-Cookie: PAGEID=$pageid <body> 已设置 Cookie 值 </body> END_OF_HTML
这段脚本中接收了名为
pageid
的查询字符串,并将其原封不动地生成了名为PAGEID
的 Cookie。首先,为了确认脚本在正常情况下的执行结果,使用以下 URL 启动脚本。
http://example.jp/47/47-021.cgi?pageid=P123
此时 Fiddler 的界面显示如下。
图 4-68 通过 Fiddler 确认 HTTP 响应
能看到这里生成了 PAGEID=P123 的 Cookie 值。
下面我们就来尝试攻击该 CGI 脚本,以使其显示伪造页面。使用以下 URL 执行脚本,如果不想手动输入,可以在 http://example.jp/47/ 中点击“7. 47-021:CGI 中设置 Cookie(伪造页面)”链接。
http://example.jp/47/47-021.cgi?pageid=P%0D%0A%0D%0A %e2%97%8b%e2%97%8b%e9%8a%80%e8%a1%8c%e3%81%af%e7%a0%b4%e7%94%a3%e3%81%97%e3%81%be%e3%81%97%e3%81%9f
下图即为执行后的页面显示。
图 4-69 伪造画面
此时,HTTP 消息如下图所示。
图 4-70 通过 Fiddler 确认 HTTP 响应
在 Set-Cookie 消息头后面连续输出两个换行时,后面的数据就会被视为消息体。
如果不加修饰,这里就依然能够看到原来的页面,但正如 4.3.1 节所介绍的那样,通过 CSS 等手段是能将原来的页面隐藏的。
另外,虽然本例中只是在页面上显示了某银行破产的谣言,但如果更进一步的话,通过制作伪造的表单来窃取个人信息的钓鱼式攻击、或通过执行 JavaScript 来窃取 Cookie 值等都是能够实现的。换言之,HTTP 消息头注入造成的页面被篡改,能够造成与 XSS 同样的影响。
43 原标题为“安全なウェブサイトの作り方”。URL : http://www.ipa.go.jp/security/vuln/websecurity.html 。
44 CGI 脚本中一旦生成 Location 消息头,HTTP 状态码就会被自动设置成 302。而要成功显示伪造页面,就必须在 CGI 脚本中将状态码强制更改为 200,但这在现在的 Apache 中是很难做到的。
安全隐患的产生原因
HTTP 响应头信息能够以文本格式逐行定义消息头,也就是说消息头之间互相以换行符相隔。而如果攻击者恶意利用该特性,在指定重定向目标 URL 或 Cookie 值的参数中插入换行符,且该换行符又被直接作为响应输出的话,就会产生 HTTP 消息头注入漏洞。
专栏:HTTP 消息头与换行
URL 和 Cookie 中本身可不可以包含换行符呢?首先,标准规格中规定了 URL 不能包含换行符。因为查询字符串中包含换行符时会被百分号编码为 %0D%0A,而重定向处理中传递 URL 时照理已经执行过了百分号编码,因此 URL 中有换行符是不正常的。
另一方面,Cookie 值中有时则需要加入换行符。而由于 Cookie 值中除了不能有换行符,也不能包含空格、逗号或分号,因此习惯对 Cookie 值进行百分号编码 45 。百分号编码后,换行符被编码为 %0D%0A,也就不会产生 HTTP 消息头注入漏洞了。
45 Netscape 公司的 Cookie 规格中有如下记载:This string is a sequence of characters excluding semi-colon, comma and white space. If there is a need to place such data in the name or value, some encoding method such as URL style %XX encoding is recommended, though no encoding is defined or required.
对策
针对 HTTP 消息头注入漏洞,最可靠的对策就是不将外界传入的参数 46 作为 HTTP 响应消息头输出。
46 外界传入的参数的一个典型的例子就是 HTTP 请求中的值,除此之外,也包括经过电子邮件或数据库等从外部发送过来的参数。
- 对策 1:不将外界参数作为 HTTP 响应消息头输出
绝大多数情况下,经过重新进行设计评估后,都能够做到不将外界参数作为 HTTP 响应消息头输出。Web 应用中会用到输出 HTTP 响应消息头的典型功能为重定向和生成 Cookie,而只要遵循以下方针,就能大幅减少直接将外界参数作为消息头输出的机会。
- 不直接使用 URL 指定重定向目标,而是将其固定或通过编号等方式来指定
- 使用 Web 应用开发工具中提供的会话变量来移交 URL
因此,在设计阶段就应该尽量不把外界参数作为 HTTP 响应消息头输出。而如果无论如何都必须将外界参数输出到 HTTP 响应消息头中的话,可以参考以下对策。
- 对策 2:执行以下两项内容
- 由专门的 API 来进行重定向或生成 Cookie 的处理
- 校验生成消息头的参数中的换行符
下面我们就来对这两项内容进行详细解说。
- 由专门的 API 来进行重定向或生成 Cookie 的处理
CGI 脚本中能够使用
print
等语句直接记述 HTTP 响应消息头,但是使用这种方法需要严格遵守 HTTP 和 Cookie 等的标准规格,否则就可能会导致安全隐患等 Bug 的产生。Perl 语言的 CGI 模块或 PHP 等 Web 应用开发语言或程序库中提供了功能丰富的函数,通过使用这些函数输出 HTTP 消息头,原则上就能够防范安全隐患。表 4-16 归纳了各语言中提供的输出 HTTP 消息头的功能。但需要注意的是,应当尽量利用生成 Cookie 以及重定向功能的程序库,程序库中未提供的情况下才使用输出响应消息头的功能。
表 4-16 各语言中提供的输出 HTTP 响应消息头的功能
语言 生成 Cookie 重定向 输出响应消息头 PHP setcookie / setrowcookie 无(利用 header) header Perl+CGI.pm CGI::Cookie redirect header Java Servlet HttpServletResponse#addCookie HttpServletResponse#sendRedirect HttpServletResponse#setHeader ASP.NET Response.Cookies.Add Response.Redirect Response.AppendHeader 使用了这些程序库后,理想状态下就能消除 HTTP 消息头注入漏洞了。然而遗憾的是,现实中即使利用了上面这些功能,有时也无法完全杜绝安全隐患。
因此,我们需要同时实施以下对策。
- 检验生成消息头的参数中的换行符
HTTP 响应消息头相关的 API 中很多都没有检验换行符。而在笔者看来,之所以出现这种情况,大概是因为业界就究竟该由 API(程序库)还是应用方面来负责 HTTP 消息头注入这一问题还没有达成共识。虽然笔者的观点是应该由 API 方面负责,但是由于目前 API 方面做的还不够充分,因此,为了保护自己,我们就不得不在应用方面多下功夫。
针对换行符的处理方法有如下两种。
- URL 中含有换行符时就报错
- 将 Cookie 中的换行符进行百分号编码
如果程序库中已经对 Cookie 值进行了百分号编码,那么应用中就可以省去这一操作。PHP 的
setcookie
函数和 Perl 的CGI::Cookie
模块会在程序库中对 Cookie 值进行百分号编码。使用其他的语言或程序库时,请事先调查 Cookie 值是否会被百分号编码。接下来就让我们看一下通过 PHP 的
header
函数来实现包含字符种类校验功能的重定向函数的示例。代码清单 /47/47-030.php
<?php // 定义重定向函数 function redirect($url) { // URL 中含有非法字符时就报错并中止处理 if (! mb_ereg("\\A[-_.!~*'();\\/?:@&=+\\$,%#a-zA-Z0-9]+\\z", $url)) { die('Bad URL'); } header('Location: ' . $url); } // 调用示例 $url = isset($_GET['url']) ? $_GET['url'] : ''; redirect($url); ?>
这段脚本定义了名为
redirect
的函数,函数中会校验 URL 的字符种类,只在校验通过的情况下才能使用header
函数执行重定向操作。但是,
redirect
函数内仅进行了字符种类的校验,并没有校验 URL 的格式是否正确。另外,此处的字符种类校验规则比 RFC3986 还要严格,指定 IPv6 的 IP 地址时[
和]
都会被报错。因此,在进行操作时,应该根据实际用途来调整具体的校验规则。
专栏:PHP 的 header 函数中进行的换行符校验
根据 PHP 的官方文档 47 ,4.4.2 以及 5.1.2 版本的
header
函数的更新日志中有如下记载:“为了防范消息头注入攻击,该函数不能一次发送多个消息头”。但是,作为消息头注入攻击的应对策略,这个方法并不充分。PHP 中校验换行符时仅校验了 LF(0x0A),而没有校验 CR(0x0D)(确认于 PHP 5.3.5)。因此,在部分用户的浏览器上,仅使用 CR 换行符的 HTTP 消息头注入攻击仍然是有效的。
笔者调查后发现,针对 Internet Explorer、Google Chrome、Opera 这 3 种浏览器,实施仅使用 CR 换行符的 HTTP 消息头注入攻击都能够取得成功,而在 Firefox 和 Apple Safari 中攻击则没有奏效。
而从以上事实中也能够看出,仅依靠 PHP 的
header
函数中的校验来实现重定向处理是存在危险的。
47 http://us2.php.net/manual/zh/function.header.php
4.7.3 重定向相关的安全隐患总结
重定向处理中产生的典型安全隐患为自由重定向漏洞和 HTTP 消息头注入漏洞。
针对这两个漏洞的对策可归纳如下。
- 重定向处理尽量使用专门的 API(程序库函数)
- 以下任选其一
- 固定重定向目标(推荐)
- 重定向目标 URL 由外界指定时,务必校验字符种类和域名
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论