出处:掘金
原作者:前端微白
为什么需要保护前端代码?
在开始技术细节前,让我们先看一组令人担忧的数据:
- 75% 的现代网站存在敏感逻辑泄露在客户端代码中
- API 密钥泄露是导致数据泄露的主要入口点(占所有泄露事件的 19%)
- 代码窃取导致的年损失超过 400 亿美元
- 一次成功的前端逆向工程平均耗时仅需 15 分钟
- 由于前端页面会调用很多接口,有些接口会被别人爬虫分析,破解后获取数据
使用 CSS 和 JS 保护内容
例如,可以使用 CSS 让文本不可选中,或者使用 JS 定期检查 DOM 的改变,以防止调试
CSS 防止文本被选中:
body {
-webkit-user-select: none; /* Chrome, Safari, Opera */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* Internet Explorer/Edge */
user-select: none; /* 非前缀版本,可以在支持的浏览器中使用 */
}
JS 定期检查 DOM 的改变:
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
console.log('Detected changes:', mutation);
// 可以在这里添加逻辑来处理检测到的DOM变化
});
});
observer.observe(document.body, {
childList: true, // 监听子元素的变化
attributes: true, // 监听属性的变化
subtree: true // 监听所有后代元素的变化
});
绕过方法:禁用 JS 运行
禁用浏览器调试功能
// 开发者工具状态检测函数
const detectDevTools = () => {
const threshold = 160; // 窗口大小变化的阈值
let lastTime = Date.now();
const interval = 1000
setInterval(() => {
const widthThreshold = window.outerWidth - window.innerWidth;
const heightThreshold = window.outerHeight - window.innerHeight;
if (widthThreshold > threshold || heightThreshold > threshold) {
// 开发者工具可能在侧面或底部打开
handleDevToolsOpen();
}
// 性能检测(开发者工具打开会降低性能)
const currentTime = Date.now();
if (currentTime - lastTime > interval + 100) {
// 控制台打开时 Date.now() 调用会变慢
handleDevToolsOpen();
}
lastTime = currentTime;
}, interval);
};
// 检测到开发者工具时的处理
const handleDevToolsOpen = () => {
// 1. 关闭当前窗口
// window.close();
// 2. 清空整个DOM
// document.documentElement.innerHTML = '';
// 3. 重定向到错误页面
// window.location.replace('https://example.com/debugging-forbidden');
// 4. 显示警告信息
document.body.innerHTML = `
<div class="anti-debug-warning">
<h1>⛔ 安全警告</h1>
<p>此页面禁止调试操作,请关闭开发者工具后刷新页面</p>
<p>如您是本网站合法用户,请<a href="javascript:location.reload()">点击此处</a>重试</p>
</div>
`;
};
// 禁用调试快捷键
const disableShortcuts = (e) => {
const forbiddenKeys = {
'F12': true,
'Ctrl+Shift+I': true,
'Ctrl+U': true,
'Ctrl+S': true // 监听 Ctrl+S 是为了禁止保存至本地,避免被 Overrides
};
if (
forbiddenKeys[e.key] ||
(e.ctrlKey && e.shiftKey && e.key === 'I') ||
(e.ctrlKey && e.key === 'u')
) {
e.preventDefault();
e.stopPropagation();
return false;
}
};
// 初始化检测
window.addEventListener('load', () => {
detectDevTools();
// 阻止键盘快捷键
document.addEventListener('keydown', disableShortcuts);
// 阻止鼠标右键
document.addEventListener('contextmenu', e => e.preventDefault());
});
绕过方法:
- 打开开发者工具后再打开网址,可跳过:阻止键盘快捷键、阻止鼠标右键
- 将开发者工具以独立窗口形式打开,可跳过:窗口大小变化监测
使用更完善的库 disable-devtool
该库有以下特性:
- 支持可配置是否禁用右键菜单
- 禁用 F12、Ctrl+Shift+I 等快捷键
- 支持识别从浏览器菜单栏打开开发者工具并关闭当前页面
- 开发者可以绕过禁用(URL 参数使用 TK 配合 MD5 加密)
- 多种监测模式,支持几乎所有浏览器(IE、360、QQ 浏览器、FireFox、Chrome、Edge…)
- 高度可配置、使用极简、体积小巧
- 支持 npm 引用和 script 标签引用(属性配置)
- 识别真移动端与浏览器开发者工具设置插件伪造的移动端,为移动端节省性能
- 支持识别开发者工具关闭事件
- 支持可配置是否禁用选择、复制、剪切、粘贴功能
- 支持识别 eruda 和 vconsole 调试工具
- 支持挂起和恢复探测器工作
- 支持配置 ignore 属性,用以自定义控制是否启用探测器
- 支持配置 iframe 中所有父页面的开发者工具禁用
无限 debuger
基础版
一定要使用匿名函数。否则别人直接在控制台重定义函数就完了,比如:function startDebug() {};
(() => {
function ban() {
setInterval(() => {
debugger;
}, 50);
}
try {
ban();
} catch (err) {}
})();
绕过方法:可以通过控制台中的 Deactivate breakpoints
按钮或者使用快捷键 Ctrl + F8
关闭无限 debugger
进阶版
如果将 setInterval
中的代码写在一行,就能禁止用户断点,即使添加 logpoint
为 false
也无用。当然即使有些人想到用左下角的格式化代码,将其变成多行也是没用的
(() => {
function ban() {
setInterval(() => { debugger; }, 50);
}
try {
ban();
} catch (err) { }
})();
绕过方法:通过添加 add script ignore list
需要忽略执行代码行或文件
终极版
可以通过将 debugger
改写成 Function("debugger")();
的形式来应对。Function
构造器生成的 debugger
会在每一次执行时开启一个临时 js
文件。当然使用的时候,为了更加的安全,最好使用加密后的脚本
(() => {
function ban() {
setInterval(() => {
Function('debugger')();
}, 50);
}
try {
ban();
} catch (err) { }
})();
// 更加的晦涩难懂
(() => {
function block() {
setInterval(() => {
(function () {
return false;
}
['constructor']('debugger')
['call']());
}, 50);
}
try {
block();
} catch (err) { }
})();
绕过方法:
- 替换 js 文件
- 修改
Function
:
Function.prototype.constructor=function(){}
Function = function () {}
压缩、混淆、加密代码
使用工具来混淆 JS 代码,可以帮助隐藏代码的真实意图,并使得代码阅读和修改变得更加困难
实际开发中使用专业工具:
- Obfuscator.io
- JavaScript Obfuscator
- UglifyJS
在线工具:
JS 加密:U 加密
编码式 JS 代码加密,将 JS 代码分割、Unicode 编码化存储到数组中,使代码全部成为密文状态显示,执行时解密、重新编码并 eval
调用运行。加密后的代码中存在大量的”u”字符(Unicode 编码),所以命名为“U 加密”。 U 加密后代码可直接运行,使用方式与功能与加密前无异
在线使用:https://www.jshaman.com/tools/u-jiami.html
“U加密”采用独特的双重安全机制:
- 预处理阶段:将 JS 脚本分解为代码片段
- 加密阶段:对每个片段进行 Unicode 序列化
- 存储阶段:以密文数组形式保存
- 执行阶段:通过动态解码器实时还原其显著的”uXXXX”编码特征既确保了代码不可读性,又形成了独特的技术标识
加密示例:
// JS 源码
(function (){
var domain = "jshaman.com";
var from_year = 2017;
var copyright = function(){
return "(c)" + from_year + "-" + (new Date).getFullYear() + "," + domain;
};
var console_log = console.log;
console_log(copyright())
})();
// 加密后的 JS 代码
var u=[`\u0028\u0066\u0075\u006e\u0063\u0074\u0069\u006f\u006e`,`\u0028\u0029\u007b`,`\u0076\u0061\u0072`,`\u0064\u006f\u006d\u0061\u0069\u006e`,`\u0022\u006a\u0073\u0068\u0061\u006d\u0061\u006e\u002e\u0063\u006f\u006d\u0022\u003b`,`\u0066\u0072\u006f\u006d\u005f\u0079\u0065\u0061\u0072`,`\u0032\u0030\u0031\u0037\u003b`,`\u0063\u006f\u0070\u0079\u0072\u0069\u0067\u0068\u0074`,`\u0066\u0075\u006e\u0063\u0074\u0069\u006f\u006e\u0028\u0029\u007b`,`\u0072\u0065\u0074\u0075\u0072\u006e`,`\u0022\u0028\u0063\u0029\u0022`,`\u0022\u002d\u0022`,`\u0028\u006e\u0065\u0077`,`\u0044\u0061\u0074\u0065\u0029\u002e\u0067\u0065\u0074\u0046\u0075\u006c\u006c\u0059\u0065\u0061\u0072\u0028\u0029`,`\u0022\u002c\u0022`,`\u0064\u006f\u006d\u0061\u0069\u006e\u003b`,`\u0063\u006f\u006e\u0073\u006f\u006c\u0065\u005f\u006c\u006f\u0067`,`\u0063\u006f\u006e\u0073\u006f\u006c\u0065\u002e\u006c\u006f\u0067\u003b`,`\u0063\u006f\u006e\u0073\u006f\u006c\u0065\u005f\u006c\u006f\u0067\u0028\u0063\u006f\u0070\u0079\u0072\u0069\u0067\u0068\u0074\u0028\u0029\u0029`,`\u007d\u0029\u0028\u0029\u003b`];var u2=[0,1,2,3,5,7,9,11,13,14,15,19,21,22,24,26,29,31,32,33];var u3=`u6[0] u6[1]u6[2] u6[3] = u6[5]u6[2] u6[7] = u6[9]u6[2] u6[11] = u6[13]u6[14] u6[15] + u6[7] + u6[19] + u6[21] u6[22] + u6[24] + u6[26]};u6[2] u6[29] = u6[31]u6[32]u6[33]`;for(u5=0; u5<u.length; u5++){u3 = u3.replace(new RegExp("u6\\["+u2[u5]+"\\]","g"), u[u5].replace("`","").replace("`",""));}eval(u3);
JS 加密:欧零加密
适用于需要轻量级保护的场景
“碎片化阵列加密” —— 一种基于词法拆解的 JS 混淆技术
在线使用:https://www.jshaman.com/tools/o0-jiami.html
核心原理:
- 词法解析:将源代码按语法单元(标识符/运算符/字面量等)拆解为碎片
- 密文存储:所有代码片段以乱序形式存入加密数组
- 动态重构:通过
eval
执行时按特定算法重组数组元素元素
技术优势:
- 实现词法级混淆(比普通压缩更安全)
- 保持精确还原性(无执行误差)
- 对抗自动化调试(无法直接断点跟踪)
加密示例:
// JS 源码
(function (){
var domain = "jshaman.com";
var from_year = 2017;
var copyright = function(){
return "(c)" + from_year + "-" + (new Date).getFullYear() + "," + domain;
};
var console_log = console.log;
console_log(copyright())
})();
// 加密后的 JS 代码
(function(){let e = eval;let x = "(_o_____o_2 (){_o__0__o_____o_0 _o_____o_3 = _0__0__o_____o_4._o_____o_5_0__0_;_o_____o_0 _o_____o_6__o_____o_7 = _o_____o_8;_o_____o_0 _o_____o_9 = _o_____o_2(){_o__0__o_____o_10 _0__0_(_o_____o_11)_0__0_ + _o_____o_6__o_____o_7 + _0__0_-_0__0_ + (_o_____o_12 _o_____o_13)._o_____o_14() + _0__0_,_0__0_ + _o_____o_3;};_o_____o_0 _o_____o_1__o_____o_15 = _o_____o_1._o_____o_15;_o_____o_1__o_____o_15(_o_____o_9())_o__0_})();";let z = "";[["_9__6_","\\"] , ["_o__0_","\n"] , ["_0__0_","\""] , ["_0___o_","'"]].map(function(z){ x = x.replace(RegExp(z[0], "g"), z[1]);});z = "var,console,function,domain,jshaman,com,from,year,2017,copyright,return,c,new,Date,getFullYear,log".split(",");let y = z.length - 1;while(y > -1){ x = x.replace(RegExp("_o_____o_" + y, "g"), z[y]); y--;}e(x);}())
JS 加密:32 进制加密
“进制编码加密” —— 一种基于数值进制转换的 JS 关键字混淆技术
在线使用:https://www.jshaman.com/tools/base32-encode.html
加密原理:
- 字符转换:将 JS 关键字(如
eval
/alert
)每个字母转换为 ASCII 码 - 进制编码:使用
(数字).toString(32)
将 ASCII 码转为 32 进制字符串符串 - 动态拼接:运行时重组这些进制编码字符串还原原始函数名
技术特点:
- 关键字隐藏:核心 API 名称被替换为数值表达式
- 抗格式化:无法通过代码美化还原原始名称
- 可控强度:可调整进制基数(16/32/36 等)
加密示例:
// JS 源码
eval(alert(1));
// 加密后的 JS 代码(32 进制编码)
eval(
(10).toString(32) + // a
(21).toString(32) + // l
(14).toString(32) + // e
(27).toString(32) + // r
(29).toString(32) // t
+ "(1)"
);
HTML 加密
将 HTML 源码转化成 Unicode 编码形式,以此实现源码加密,而功能保持正常
在线使用:https://www.jshaman.com/tools/html-jiami.html
<html>
<head><title>测试</title></head>
<body>
<h1>Html源码加密</h1>
<script>
alert("加密测试");
</script>
</body>
</html>
加密:
<script>
function decodeUnicodeEntities(encodedStr){
return encodedStr.replace(/&#x([0-9a-fA-F]+);/g, function(match, hexValue){
return String.fromCharCode(parseInt(hexValue, 16));
});
}
let encodedString = "<html> <head><title>测试</title></head> <body> <h1>Html源码加密</h1> <script> alert("加密测试"); </script> </body> </html> ";
let decodedString = decodeUnicodeEntities(encodedString);
document.write(decodedString);
</script>
JSON 加密
- JSON 对像 Key 值标准化
- 字符串 Unicode 化
- 数值字面量转二元表达式
- 布尔字面量转一元表达
JS 代码中的 JSON 对像,或单独外部 JSON 文件,都可进行加密。加密后的 JSON,可以直接使用,与加密前一样
{
key1: [true, false, null],
//comment
"\u006B\u0065\u0079\u0032": {
"key2Sub": [1, 1.2, 2, "3", 1e10, 1e-3]
},
"key3": false,
"key4": "jshaman.com"
}
加密:
{"\u006B\u0065\u0079\u0031":[!![],![],null],"\u006B\u0065\u0079\u0032":{"\u006B\u0065\u0079\u0032\u0053\u0075\u0062":[456093^456092,1.2,950527^950525,"\u0033",1e10,1e-3]},"\u006B\u0065\u0079\u0033":![],"\u006B\u0065\u0079\u0034":"\u006A\u0073\u0068\u0061\u006D\u0061\u006E\u002E\u0063\u006F\u006D"}
其他
JS 语法标准化
将前端浏览器特有的语法进行标准化、统一化处理
例如:alert
、console
转化为 window.alert
、window.console
为什么要这样做?
- 统一规范化之后,源码更工整、易维护
- 利于 JS 代码混淆加密
- 例如
alert(1)
,如仅对此一句代码加密,由于它会被示例全局顶层函数,出于代码稳定性考虑,通常是无法进行加密的,它不利于混淆加密 - 显示的将其写为
window.alert(1)
,如此它便成了window
的成员函数调用方式,便可进行加密
- 例如
转 Unicode
将字串转化为 Unicode
转义序列形式。可用来 eval
等语句结合,实现代码加密、反调试
例如 var key = 123
转化为 Unicode 形式为:
\u0076\u0061\u0072\u0020\u006b\u0065\u0079\u0020\u003d\u0020\u0031\u0032\u0033\u003b\u000a
用 eval
可以执行
转 Base64
将代码进行 Base64 编码后,用 eval
、atob
执行
new 表达式加密
“构造器隐写加密” —— 一种针对 new
操作符的轻量级混淆方案方案
技术实现:
// 原始代码
const now = new Date();
// 加密变体 A(数组索引式)
const now = [Date, Object, RegExp][0]();
// 加密变体 B(逗号表达式式)
const now = (RegExp, Object, Date)();
核心逻辑保护
WebAssembly
将关键算法移植到 WebAssembly,前端调用方式:
WebAssembly.instantiateStreaming(fetch('security.wasm'))
.then(obj => {
const protectLogic = obj.instance.exports.protectLogic;
// 调用受保护的逻辑
const result = protectLogic(123, 456);
console.log('安全计算结果:', result);
});
使用服务器端渲染
将网页的渲染过程放在服务器端,只返回最终渲染结果给客户端,隐藏源代码和逻辑
定制浏览器和客户端
对于极其敏感的业务场景,考虑开发定制浏览器或客户端应用作为替代方案,将关键代码从浏览器环境中解放出来
不可避免的局限性
- 完全防护是不可能的:客户端代码最终都在用户设备执行
- 性能成本:安全措施会增加资源消耗
- 误报风险:可能影响合法用户
- 对抗升级:存在专业反调试工具可绕过大多数保护
“前端安全的本质是增加攻击成本,而不是追求绝对防御” —— 安全专家