移动端使用JavaScript自动写入剪贴板

背景

近期风靡的“吱口令”红包在互联网上铺天盖地,用户复制一段“吱口令”,打开支付宝就可以直接进入抢红包的页面。研究发现,支付宝是通过读取用户剪贴板数据,提取出吱口令,之后加载抢红包页面。

如果可以实现在用户打开某个网页的时候就写入剪贴板,那么用户会在不知不觉打开支付宝时,就会直接进入领取页面。因此,我们尝试了js自动写入剪贴板是否可行。

遍历js事件

PC端似乎需要用户主动点击才能实现写入剪贴板,我们首先写一段写入剪贴板的代码

1
2
3
4
5
6
7
8
<input type="text" id="website" value="tobecopydata" />
<script>
function copy(e) {

website.select()
document.execCommand('copy');
}
</script>

之后通过遍历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
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
// event handler
document.body.addEventListener('blur', copy, true);
document.body.addEventListener('error', copy, true);
document.body.addEventListener('focus', copy, true);
document.body.addEventListener('load', copy, true);
document.body.addEventListener('resize', copy, true);
document.body.addEventListener('scroll', copy, true);
document.body.addEventListener('afterprint', copy, true);
document.body.addEventListener('beforeprint', copy, true);
document.body.addEventListener('beforeunload', copy, true);
document.body.addEventListener('hashchange', copy, true);
document.body.addEventListener('languagechange', copy, true);
document.body.addEventListener('message', copy, true);
document.body.addEventListener('messageerror', copy, true);
document.body.addEventListener('offline', copy, true);
document.body.addEventListener('online', copy, true);
document.body.addEventListener('pagehide', copy, true);
document.body.addEventListener('pageshow', copy, true);
document.body.addEventListener('popstate', copy, true);
document.body.addEventListener('rejectionhandled', copy, true);
document.body.addEventListener('storage', copy, true);
document.body.addEventListener('unhandledrejection', copy, true);
document.body.addEventListener('unload', copy, true);
document.body.addEventListener('abort', copy, true);
document.body.addEventListener('cancel', copy, true);
document.body.addEventListener('canplay', copy, true);
document.body.addEventListener('canplaythrough', copy, true);
document.body.addEventListener('change', copy, true);
document.body.addEventListener('click', copy, true);
document.body.addEventListener('close', copy, true);
document.body.addEventListener('contextmenu', copy, true);
document.body.addEventListener('cuechange', copy, true);
document.body.addEventListener('dblclick', copy, true);
document.body.addEventListener('drag', copy, true);
document.body.addEventListener('dragend', copy, true);
document.body.addEventListener('dragenter', copy, true);
document.body.addEventListener('dragleave', copy, true);
document.body.addEventListener('dragover', copy, true);
document.body.addEventListener('dragstart', copy, true);
document.body.addEventListener('drop', copy, true);
document.body.addEventListener('durationchange', copy, true);
document.body.addEventListener('emptied', copy, true);
document.body.addEventListener('ended', copy, true);
document.body.addEventListener('input', copy, true);
document.body.addEventListener('invalid', copy, true);
document.body.addEventListener('keydown', copy, true);
document.body.addEventListener('keypress', copy, true);
document.body.addEventListener('keyup', copy, true);
document.body.addEventListener('loadeddata', copy, true);
document.body.addEventListener('loadedmetadata', copy, true);
document.body.addEventListener('loadstart', copy, true);
document.body.addEventListener('mousedown', copy, true);
document.body.addEventListener('mouseenter', copy, true);
document.body.addEventListener('mouseleave', copy, true);
document.body.addEventListener('mousemove', copy, true);
document.body.addEventListener('mouseout', copy, true);
document.body.addEventListener('mouseover', copy, true);
document.body.addEventListener('mouseup', copy, true);
document.body.addEventListener('mousewheel', copy, true);
document.body.addEventListener('pause', copy, true);
document.body.addEventListener('play', copy, true);
document.body.addEventListener('playing', copy, true);
document.body.addEventListener('progress', copy, true);
document.body.addEventListener('ratechange', copy, true);
document.body.addEventListener('reset', copy, true);
document.body.addEventListener('seeked', copy, true);
document.body.addEventListener('seeking', copy, true);
document.body.addEventListener('select', copy, true);
document.body.addEventListener('stalled', copy, true);
document.body.addEventListener('submit', copy, true);
document.body.addEventListener('suspend', copy, true);
document.body.addEventListener('timeupdate', copy, true);
document.body.addEventListener('toggle', copy, true);
document.body.addEventListener('volumechange', copy, true);
document.body.addEventListener('waiting', copy, true);
document.body.addEventListener('wheel', copy, true);
document.body.addEventListener('auxclick', copy, true);
document.body.addEventListener('gotpointercapture', copy, true);
document.body.addEventListener('lostpointercapture', copy, true);
document.body.addEventListener('pointerdown', copy, true);
document.body.addEventListener('pointermove', copy, true);
document.body.addEventListener('pointerup', copy, true);
document.body.addEventListener('pointercancel', copy, true);
document.body.addEventListener('pointerover', copy, true);
document.body.addEventListener('pointerout', copy, true);
document.body.addEventListener('pointerenter', copy, true);
document.body.addEventListener('pointerleave', copy, true);
document.body.addEventListener('beforecopy', copy, true);
document.body.addEventListener('beforecut', copy, true);
document.body.addEventListener('beforepaste', copy, true);
document.body.addEventListener('copy', copy, true);
document.body.addEventListener('cut', copy, true);
document.body.addEventListener('paste', copy, true);
document.body.addEventListener('search', copy, true);
document.body.addEventListener('selectstart', copy, true);
document.body.addEventListener('webkitfullscreenchange', copy, true);
document.body.addEventListener('webkitfullscreenerror', copy, true);

通过实验,我们得出以下结论:

  1. 移动端包括Android和iOS都需要点击一下屏幕,无法实现网页加载自动复制。注意是点击一下,而不是滑动。
  2. PC端其实不需要点击,只需要鼠标移动,即可实现复制

既然移动端必须要点一下,我们研究实现了再屏幕上任意位置点击,即可写入剪贴板

Android实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<input type="text" id="website" value="http://www.sitepoint.com/" />
<button data-copytarget="#website" id="aaa">copy</button>


<script type="text/javascript">
document.body.addEventListener('mouseup', copy, true);
// event handler
function copy(e) {

website.select()
document.execCommand('copy');

}

</script>

iOS实现

iOS不支持execCommand,这里用一个叫clipboard.js(https://clipboardjs.com/)的库,将一个div占满屏幕,然后用户点击任意位置就可以触发点击事件了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<body>
<!-- 1. Define some markup -->
<div id="btn" style="position:absolute;width:100%;height:100%;" data-clipboard-text="KjABgP05ll">
<span>Copy</span>
</div>

<!-- 2. Include library -->
<script src="https://cdn.jsdelivr.net/npm/clipboard@1/dist/clipboard.min.js"></script>

<!-- 3. Instantiate clipboard by passing a HTML element -->
<script>
var btn = document.getElementById('btn');
var clipboard = new Clipboard(btn);
clipboard.on('success', function(e) {
console.log(e);
});
clipboard.on('error', function(e) {
console.log(e);
});
</script>
</body>

利用微信实现

  • devicemotion事件

html5提供了几个新的DOM事件来获得设备物理方向及运动的信息,包括:陀螺仪、罗盘及加速计等,devicemotion就是其中一种,例如我们常用的摇一摇功能就是利用了该事件。注册了devicemotion事件后,只要用户轻轻动一下手机,就会触发事件代码。

  • 微信写入剪贴板的伪协议

微信Android客户端注册了一个伪协议:weixin://webview/copy/xxxxxxx,对该协议发起请求后,微信会自动将xxxxxxx写入剪贴板。

结合上述两个特性,很容易实现只要打开网页,就会自动写入剪贴板,而微信一定程度上成为了自动传播吱口令的“帮凶”,具体实现如下:
网页注册devicemotion事件,触发后新建一个iframe发起对伪协议的请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script>
if (window.DeviceMotionEvent) {
window.addEventListener('devicemotion', handler, false);
} else {
alert('not support devicemotion');
}

function handler(e) {
var b = document.createElement("iframe");
b.style.cssText = "display:none;width:0px;height:0px;";
b.src = "weixin://webview/copy/" + "m4bln";
document.body.appendChild(b)
}

</script>

利用apple设备的“通用剪贴板”

apple设备的“通用剪贴板”,可以在一台 Apple 设备上拷贝文本、图像、照片和视频,然后在另一台 Apple 设备上粘贴该内容。利用通用剪贴板,当用户在PC上点击恶意网页时,吱口令会自动写入到PC的剪贴板,同时“通用剪贴板”机制也会把吱口令写入到手机的剪贴板,此时,用户打开手机上的支付宝,就会进入红包页面。

使用“通用剪贴板”必须满足设备“连续互通”的要求(https://support.apple.com/zh-cn/HT204689)

  • 所有设备均使用同一 Apple ID 登录 iCloud。
  • 所有设备均已开启蓝牙。
  • 所有设备均已开启 Wi-Fi。

但实际测试似乎只要满足登录同一个Apple ID即可。