禁止用户调试网站

禁止用户调试网站

web想禁止调试,没有绝对安全的方式

妙用1 debugger

示例

setInterval执行debugger,打开控制台就会暂停代码

1
2
3
4
5
6
7
8
(() => {
function ban() {
setInterval(() => {
debugger;
}, 50);
}
ban();
})();

妙用2 debugger

示例

效果:打开控制台就进入debugger,如果还像 妙用1 的破解之法解决,浏览器会被卡死

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(() => {
let timeLimit = 50;
setInterval(loop, 1);
function loop() {
let startTime = new Date();
debugger;
if (new Date() - startTime > timeLimit) {
window.stop();
let total = '';
for (let i = 0; i < 1000000; i++) {
total = total + i.toString();
history.pushState(0, 0, total);
}
}
}
})();

分析

下面这一段是导致浏览器卡死的罪魁祸首

1
2
3
4
5
let total = '';
for (let i = 0; i < 1000000; i++) {
total = total + i.toString();
history.pushState(0, 0, total);
}

但是默认应该就会执行,为什么打开控制台才进入这一段呢?答案就在这一段

1
2
3
4
5
let startTime = new Date();
debugger;
if (new Date() - startTime > timeLimit) {
// ...
}

只有进入debugger的情况下,new Date()才会比startTime(上一次的时间)大50ms

也可以这样操作:跳转至空页面

1
2
3
if (new Date() - startTime > timeLimit) {
window.location.href = 'about:blank';
}

还有很多方式,只要避免能调试当前页面即可

第三方库:disable-devtool

https://github.com/theajack/disable-devtool:**0.3.7**

使用方式

1
2
3
4
5
<script
disable-devtool-auto
src='https://cdn.jsdelivr.net/npm/disable-devtool'
stopIntervalTime="false"
></script>

配置含义

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
interface IConfig {
md5?: string; // 绕过禁用的md5值,用于线上,默认不启用绕过禁用
url?: string; // 关闭页面失败时的跳转页面,默认值为localhost
tkName?: string; // 绕过禁用时的url参数名称,默认为 ddtk
ondevtoolopen?(type: DetectorType, next: Function): void; // 开发者面板打开的回调,启用时url参数无效,type 为监测模式,详见3.5, next函数是关闭当前窗口
ondevtoolclose?(): void; // 开发者面板关闭的回调
interval?: number; // 定时器的时间间隔 默认200ms
disableMenu?: boolean; // 是否禁用右键菜单 默认为true
stopIntervalTime?: number; // 在移动端时取消监视的等待时长
clearIntervalWhenDevOpenTrigger?: boolean; // 是否在触发之后停止监控 默认为false, 在使用ondevtoolclose时该参数无效
detectors?: Array<DetectorType>; // 启用的检测器 检测器详情见 3.5 默认为全部,建议使用全部
clearLog?: boolean; // 是否每次都清除log
disableSelect?: boolean; // 是否禁用选择文本 默认为false
disableCopy?: boolean; // 是否禁用复制 默认为false
disableCut?: boolean; // 是否禁用剪切 默认为false
disablePaste: boolean; // 是否禁用粘贴 默认为false
ignore?: (string|RegExp)[] | null | (()=>boolean); // 某些情况忽略禁用
disableIframeParents?: boolean; // iframe中是否禁用所有父窗口
timeOutUrl?: string; // 关闭页面超时跳转的url;
rewriteHTML: string; // 检测到打开之后重写页面
}

enum DetectorType {
Unknown = -1,
RegToString = 0, // 根据正则检测
DefineId, // 根据dom id检测
Size, // 根据窗口尺寸检测
DateToString, // 根据Date.toString 检测
FuncToString, // 根据Function.toString 检测
Debugger, // 根据断点检测,仅在ios chrome 真机情况下有效
Performance, // 根据log大数据性能检测
DebugLib, // 检测第三方调试工具 eruda 和 vconsole
};

关键源码

禁用快捷键

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
const KEY = {J: 74, I: 73, U: 85, S: 83, F12: 123};

// 禁用 ctrl + shift + i/j
const isOpenDevToolKey = IS.macos ?
((e: KeyboardEvent, code: number) => (e.metaKey && e.altKey && (code === KEY.I || code === KEY.J))) :
((e: KeyboardEvent, code: number) => (e.ctrlKey && e.shiftKey && (code === KEY.I || code === KEY.J)));

const isViewSourceCodeKey = IS.macos ?
((e: KeyboardEvent, code: number) => (e.metaKey && e.altKey && code === KEY.U) || (e.metaKey && code === KEY.S)) :
((e: KeyboardEvent, code: number) => (e.ctrlKey && (code === KEY.S || code === KEY.U)));

target.addEventListener('keydown', (e) => {
e = e || target.event;
const keyCode = e.keyCode || e.which;
if (
keyCode === KEY.F12 || // 禁用f12
isOpenDevToolKey(e, keyCode) || // 禁用 ctrl + shift + i
isViewSourceCodeKey(e, keyCode) // 禁用 ctrl + u 和 ctrl + s 查看和保存源码
) {
return preventEvent(target, e);
}
}, true);

target.addEventListener('contextmenu', (e: Event & {pointerType: string}) => {
if (e.pointerType === 'touch') return;
return preventEvent(target, e);
});
addPreventListener(target, 'selectstart');
addPreventListener(target, 'copy');
addPreventListener(target, 'cut');
addPreventListener(target, 'paste');

function addPreventListener (target: Window, name: string) {
target.addEventListener(name, (e: Event) => {
return preventEvent(target, e);
});
}

检测方法

DataToString

利用 不打开控制不会执行console.log,执行了两次toString后视为打开了控制台

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
init () {
this.count = 0;
this.date = new Date();
this.date.toString = () => {
this.count ++;
return '';
};
}

detect () {
this.count = 0;
log(this.date);
clearLog();
if (this.count >= 2) {
this.onDevToolOpen();
}
}

defineProperty

console.log触发 getter 的特性来检测是否打开了开发者工具。当控制台试图渲染 this.div 对象时,触发了 id 属性的 getter,从而调用 this.onDevToolOpen() 来响应检测结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
init () {
this.div = document.createElement('div');
(this.div as any).__defineGetter__('id', () => {
this.onDevToolOpen();
});
Object.defineProperty(this.div, 'id', {
get: () => {
this.onDevToolOpen();
},
});
}

detect () {
log(this.div);
}

Detector

利用FunctiontoString

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
init () {
this.count = 0;
this.func = () => {};
this.func.toString = () => {
this.count ++;
return '';
};
}

detect () {
this.count = 0;
log(this.func);
clearLog();
if (this.count >= 2) {
this.onDevToolOpen();
}
}

performance

通过控制台打印大对象数组所花的大量时间来检测开发者工具是否被打开

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
init () {
this.maxPrintTime = 0;
this.largeObjectArray = createLargeObjectArray();
}

detect () {
const tablePrintTime = calculateTime(() => {table(this.largeObjectArray);});
const logPrintTime = calculateTime(() => {log(this.largeObjectArray);});
this.maxPrintTime = Math.max(this.maxPrintTime, logPrintTime);

clearLog();

if (tablePrintTime === 0 || this.maxPrintTime === 0) return false;

if (tablePrintTime > this.maxPrintTime * 10) {
this.onDevToolOpen();
}
}

debugger

原理同本文开头方式

1
2
3
4
5
6
7
detect () {
const date = now();
(() => {debugger;})();
if (now() - date > 100) {
this.onDevToolOpen();
}
}

resize

通过监测内外窗口尺寸(如outerWidthinnerWidth的差值)是否异常变化来检测开发者工具的打开状态

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
init () {
this.checkWindowSizeUneven();
window.addEventListener('resize', () => {
setTimeout(() => {
this.checkWindowSizeUneven();
}, 100);
}, true);
}

// 检测内外窗口的大小是否因devtool而产生差值
private checkWindowSizeUneven () {
const screenRatio = countScreenZoomRatio();
if (screenRatio === false) { // 如果获取不到屏幕缩放尺寸 则不启用sizeDetector
return true;
}
const widthUneven = window.outerWidth - window.innerWidth * screenRatio > 200; // 调大一点防止误伤
const heightUneven = window.outerHeight - window.innerHeight * screenRatio > 300; // 调大一点防止误伤
if (widthUneven || heightUneven) {
this.onDevToolOpen();
return false;
}
clearDevToolOpenState(this.type);
return true;
}

// 获取像素比
function countScreenZoomRatio () {
if (checkExist(window.devicePixelRatio)) {
return window.devicePixelRatio;
}
const screen = window.screen as any;
if (checkExist(screen)) {
return false;
}
if (screen.deviceXDPI && screen.logicalXDPI) {
return screen.deviceXDPI / screen.logicalXDPI;
}
return false;
};

总结

其实就三种检测类型

  • resize
  • console.log
  • debugger

但产生了多种检测器,从而增大破解的难度

破解之法

破解之法1

仅需停用断点,即可破解

image-20240805010519481

破解之法2

只要一律不在此处暂停(debugger),甚至还可以加断点调试网站

image-20240805223959863

破解之法3

可以添加条件断点

image-20241028010502293

破解之法4

先在一个空页面打开控制台,再进入页面,可以攻击只有debugger的网站

破解之法5

使用浏览器开发者工具替换修改js(Sources面板 –> Overrides),详情可看:在本地替换 Web 内容和 HTTP 响应标头

image-20241028011025582

破解之法5

直接安装油猴脚本即可,或者自己写一个

image-20241028011201356

参考

🔏别想调试我的前端页面代码🔒
⚔️不让我在控制台上调试,哼,休想🛠️

作者

Liang

发布于

2024-10-28

更新于

2024-10-28

许可协议


评论