XSS 跨站脚本攻击介绍

XSS

XSS (Cross Site Script),跨站脚本攻击,因为缩写和 CSS (Cascading Style Sheets) 重叠,所以只能叫 XSS。

XSS 的原理是恶意攻击者往 Web 页面里插入恶意可执行网页脚本代码,当用户浏览该页之时,嵌入其中 Web 里面的脚本代码会被执行,从而可以达到攻击者盗取用户信息或其他侵犯用户安全隐私的目的。XSS 的攻击方式千变万化,但还是可以大致细分为几种类型。

XSS分类

非持久型 XSS

非持久型 XSS 漏洞,也叫反射型 XSS 漏洞,一般是通过给别人发送带有恶意脚本代码参数的 URL,当 URL 地址被打开时,特有的恶意代码参数被 HTML 解析、执行。

举一个例子,比如你的 Web 页面中包含有以下代码:

<select>
    <script>
        document.write(''
            + '<option value=1>'
            +     location.href.substring(location.href.indexOf('default=') + 8)
            + '</option>'
        );
        document.write('<option value=2>English</option>');
    </script>
</select>

攻击者可以直接通过 URL (类似:[https://xx.com/xx?default=<script>alert(document.cookie)</script>](https://xx.com/xx?default=%3Cscript%3Ealert(document.cookie)%3C/script%3E)) 注入可执行的脚本代码。

非持久型 XSS 漏洞攻击特征:

即时性,不经过服务器存储,直接通过 HTTP 的 GET 和 POST 请求就能完成一次攻击,拿到用户隐私数据。

攻击者需要诱骗点击;

反馈率低,所以较难发现和响应修复;

盗取用户敏感保密信息。

持久型 XSS

持久型 XSS 漏洞,也被称为存储型 XSS 漏洞,一般存在于 Form 表单提交等交互功能,如发帖留言,提交文本信息等,黑客利用的 XSS 漏洞,将内容经正常功能提交进入数据库持久保存,当前端页面获得后端从数据库中读出的注入代码时,恰好将其渲染执行。

主要注入页面方式和非持久型 XSS 漏洞类似,只不过持久型的不是来源于 URL,refferer,forms 等,而是来源于后端从数据库中读出来的数据。持久型 XSS 攻击不需要诱骗点击,黑客只需要在提交表单的地方完成注入即可,但是这种 XSS 攻击的成本相对还是很高。

持久型 XSS 特点:

持久性,植入在数据库中

危害面广,甚至可以让用户机器变成 DDoS 攻击的肉鸡。

盗取用户敏感私密信息

基于字符集的 XSS

其实现在很多的浏览器以及各种开源的库都专门针对了 XSS 进行转义处理,尽量默认抵御绝大多数 XSS 攻击,但是还是有很多方式可以绕过转义规则,让人防不胜防。比如「基于字符集的 XSS 攻击」就是绕过这些转义处理的一种攻击方式,比如有些 Web 页面字符集不固定,用户输入非期望字符集的字符,有时会绕过转义过滤规则。

以基于 utf-7 的 XSS 为例

utf-7 是可以将所有的 unicode 通过 7bit 来表示的一种字符集 (但现在已经从 Unicode 规格中移除)。

这个字符集为了通过 7bit 来表示所有的文字, 除去数字和一部分的符号,其它的部分将都以 base64 编码为基础的方式呈现。

<script>alert("xss")</script>
可以被解释为:
+ADw-script+AD4-alert(+ACI-xss+ACI-)+ADw-/script+AD4-

可以形成「基于字符集的 XSS 攻击」的原因是由于浏览器在 meta 没有指定 charset 的时候有自动识别编码的机制,所以这类攻击通常就是发生在没有指定或者没来得及指定 meta 标签的 charset 的情况下。

基于字符集的 XSS预防:

记住指定 <meta charset="utf-8">
XML 中不仅要指定字符集为 utf-8,而且标签要闭合

基于 Flash 的跨站 XSS

基于 Flash 的跨站 XSS 也是属于反射型 XSS 的一种,虽然现在开发 ActionScript 的产品线几乎没有了,但还是提一句吧,AS 脚本可以接受用户输入并操作 cookie,攻击者可以配合其他 XSS(持久型或者非持久型)方法将恶意 swf 文件嵌入页面中。主要是因为 AS 有时候需要和 JS 传参交互,攻击者会通过恶意的 XSS 注入篡改参数,窃取并操作cookie。

避免方法:

严格管理 cookie 的读写权限
对 Flash 能接受用户输入的参数进行过滤 escape 转义处理

未经验证的跳转 XSS

有一些场景是后端需要对一个传进来的待跳转的 URL 参数进行一个 302 跳转,可能其中会带有一些用户的敏感(cookie)信息。如果服务器端做302 跳转,跳转的地址来自用户的输入,攻击者可以输入一个恶意的跳转地址来执行脚本。

预防措施:

对待跳转的 URL 参数做白名单或者某种规则过滤
后端注意对敏感信息的保护, 比如 cookie 使用来源验证。

XSS危害

1、Cookie劫持

恶意脚本通过document.cookie读取浏览器的cookie信息,从而发起“Cookie劫持”攻击,如直接登录进用户的账户。

2、Dos攻击

注入脚本,构造GET、POST请求,对某个连接进行攻击。

3、XSS钓鱼

利用JavaScript在当前页面上“画出”一个伪造的登录框,当用户在登录框中输入用户名与密码后,其密码将被发送到黑客的服务器上。

4、使用js或css破坏⻚面正常的结构与样式

5、流量劫持

通过访问某段具有window.location.href定位到其他页面

6、获取用户隐私信息

识别用户浏览器:通过XSS读取浏览器的UserAgent对象;根据各个浏览器之间存在的实现差异,不同的浏览器会各自实现一些独特的功能,从而写代码识别出不同的浏览器。

识别用户安装的软件:举例:IE中,可以通过判断ActiveX控件的某个classid是否存在,来推测用户是否安装了该软件。FireFox的插件列表存放在一个DOM对象中,通过查询DOM可以遍历出所有的插件。Chrome中,通过检测扩展的图标,来判断某个特定的扩展是否存在。

CSS History Hack:就是通过CSS,来发现一个用户曾经访问过的网站。其原理就是利用style的visited属性——如果用户曾经访问过某个链接,那么这个链接的颜色会变得与众不同。

获取用户的真实IP地址

XSS防御

  • 1、对重要的cookie设置“httpOnly”, 防⽌客户端通过document.cookie读取cookie。服务端可以将不同cookie选择性设置httpOnly。

  • 2、Web 页面渲染的所有内容或者渲染的数据都必须来自于服务端。

  • 3、尽量不要从 URL,document.referrer,document.forms 等这种 DOM API 中获取数据直接渲染。

  • 4、尽量不要使用 eval, new Function()document.write()document.writeln(),window.setInterval()window.setTimeout()innerHTMLdocument.creteElement() 等可执行字符串的方法。

  • 5、后端在入库前应该选择不相信任何前端数据,将所有的字段统一进行转义处理。再严格一点就把输⼊的⼀些不合法的东西都过滤掉,从⽽保证安全性。如移除⽤户上传的DOM属性,如onerror,移除用户上传的Style节点,<iframe><script><base><form> 节点等。对于富文本要特别考虑这个。

  • 6、后端在输出给前端数据统一进行转义处理。

  • 7、前端在渲染页面 DOM 的时候应该选择不相信任何后端数据,任何字段都需要做escape转义处理。escape 转义的目的是将一些构成 HTML 标签的元素转义,比如 <>,空格 等。

PHP 语言

对于 PHP语言可以使用 ezyang/htmlpurifier 库对用户输入进行过滤、转义

1、安装 HTML Purifier

composer require ezyang/htmlpurifier

2、新建中间件

php artisan make:middleware XSS

3、添加中间件 app/Http/Kernel.php

protected $routeMiddleware = [
    'xss' => \App\Http\Middleware\XSS::class,
];

4、XSS 中间件规则的 DEMO

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

/**
 * 防 xss 攻击中间件
 */
class XSS
{
    public function handle(Request $request, Closure $next)
    {
       //过滤XSS方法,这里是去除输入的html标签
       $userInput = $request->all();
       $config = \HTMLPurifier_Config::createDefault();
       # 过滤掉所有html标签
       $config->set('HTML.Allowed', '');
       $config->set('Cache.SerializerPath', storage_path('app/purify'));
       //    只允许图片标签 img 及其链接和描述
       //    $config->set('HTML.Allowed', 'img[src|alt]');
       $purifier = new \HTMLPurifier($config);
       foreach ($userInput as &$value) {
          if (is_string($value)) {
             $value = $purifier->purify($value);
          } elseif (is_array($value)) {
             $value = $purifier->purifyArray($value);
          }
       }
       $request->merge($userInput);
       return $next($request);
    }
}

5、路由添加 XSS 中间件

# 发表动态
$router->middleware(['xss'])->post('/dynamic/doPublish', [DynamicController::class, 'doPublish']);

引用链接

[1] https://xx.com/xx?default=: https://xx.com/xx?default=%3Cscript%3Ealert(document.cookie
[2] ezyang/htmlpurifier: https://github.com/ezyang/htmlpurifier