前端安全保障:加密/混淆/反调试/加壳/自定义虚拟机—必要吗

起初个人认为在加了https的情况下前端加密完全没有必要。前端无论是传输内容加密还是代码加密,都是增加一丁点破解难度而已,却带来性能的天坑。轮子哥说:人家黑客又不是非得用你的网站来使用你的服务,你客户端加密又有什么用呢,人家可以直接把加密后的截取下来发到服务器去,等于没加密。Mark说:现在几乎所有大公司代码都是进过审核的,怎么可能随便让一个程序员打印出密码(参考银行)。如果代码中可能植入后门这点成立,前端同样可以植入后门,内鬼同样可以把用户密码跨域发送给某个地址。 假设不可以前端植入后门,内鬼在后端获取hash后的密码。内鬼同样可以使用脚本使用hash后的密码发包,实现用户登录。综上,前端加密完全没有意义

虽然聊胜于无,但是,.01与 0.001不等于0。前端加密还是阻挡一些比较懒觉得不值得花时间分析代码的人。但是所有安全措施的目的不都是提高破解成本么?前端加密不是决定性的保护措施,但却是一种有意义的低成本安全增强方案

前端接口参数加密的必要性

前端加密务必要。但是个人觉得这也是要分情况的。

  • 传输加密(对抗链路破解)
  • 数据加密(对抗协议破解)
  • 代码加密(隐藏算法、反调试、防黑盒…)

Web 前端密码加密是否有意义? - 大宽宽的回答:

密码在前端被别人盗走

比如在Web页面的cookie里,localstorage里等地方存储了名文的密码,并且被XSS了。又或者app将密码存到了沙箱的文件系统,但是很不幸app被篡改了,或者手机彻底被root了。

应对方案是:

  • 前端压根就不要保存密码,存到后端去
  • 使用http only cookie这种技术,不允许前端代码触碰密码数据
  • 保证前端存在某种类似于iOS的keychain这种机制做存储

密码输入被盗走

用户通过UI的控件输入密码,在还没传输给服务器之前,攻击者盗走。这是可以通过弹出虚拟键盘,输入内如加密处理。

密码在传输时被别人盗走

这个大家都有共识了,上SSL(对于http就是https了)。如果特定领域因为某种原因无上ssl,就必须弄一个大概等价于SSL的简化方案。本质上这个传输就是一边加密,一边解密,同时保证密钥本身不会在传输时泄漏。

如果传输因为各种原因无法使用SSL,或者走客户端代理,而代理者拥有SSL的私钥(比如大型企业内部的代理用户监控员工行为),那么客户端加密就非常非常的重要了。

  • 客户端代理:通常用户是不知道代理的存在,比如企业为了监控员工https流量,一定会在员工电脑上下手脚,这样企业的网管完全可以看到员工的https明文流量,其中也包含用户的明文密码。
  • 服务器代理:通常有服务器的数字证书私钥,可以与客户端建立https加密通信,自然就可以看到用户的https明文流量,其中也包含用户的明文密码。

所谓根证书是神马?证书的信任链条是环环相扣的,根证书就是一开始就被信任的证书。比方说,我相信A,A告诉我B是安全可靠的,B又担保告诉我,C是OK的,那么我就相信C了。根证书就是这里的A,当然A和C之间可以有多个证书链条,任何数量的B。    根证书是被严格限制和确认的,根证书的颁发者被称之为Certificate Authority,简称就是CA,其实信任的根证书不如说是信任CA罢了。操作系统里都会内置一份可信的根证书列表,(Firefox的根证书列表是独立于操作系统之外的),这个列表里的证书会被严格的审核以确保安全与可靠。

具体可以查看:HTTPS 可能被这样劫持吗?

https被劫持情景

一旦前端加密了用户密码,即使有代理的存在,依然无法获得用户的明文密码。

此外,https的一个额外用处是将证书和域名结合起来,而域名又被DNS控制。这仨在一起可以避免钓鱼攻击(但依然有办法,见下文)。

密码在服务器被别人盗走

密码存在数据库里,然后被拖库。应对方法是,内网防范——避免数据库被攻破。但谁也不能打这个包票。因此更好的办法是,压根就不要存原始的明文密码,甚至不能存储可以很容易被反向回明文的密码hash。常用的技术是对每一个密码加不同的盐。

密码被后台工作人员拿到。像mega.nz dropbox这些网盘就是端对端加密。比如区块链、智能合约,在前端实现。都是需要前端加密

高仿真钓鱼

如果靠直接在浏览器里输入url的方式进入某个网站其实很不靠谱的。钓鱼网站很容易让自己长得非常像真的网站(包括域名在内),并且证书也相对容易做不经任何审查的申请。因此,尽量从搜索引擎等地方来进入。由这些入口来保证你进入的真的是你想进入的那个网站。在移动端,这个入口一般是app store保证的。app store保证给你的app是正牌的,没篡改过的,非李鬼的app。(如果你经常使用盗版app做一些和钱相关的事情)。

社会工程学

冒名顶替打电话给一个想攻破的人要密码。花言巧语,总能骗到一些人。这种除了教育,任何时候不要泄露密码之外,并没有什么更好的办法了。也许用一些硬件,比如U盾之类的,可以部分解决这个问题。

可以看到安全是一个整体,任何地方出漏洞整个体系就出问题。此外,安全是分级别的,比如工具类服务安全性可以要求稍微低一些,于是做好前两个能解决大部分问题。而金融类服务就要所有都做才行。

在上面这个体系下,可以看到“前端加密”的重要性和是否能使用安全的传输关系很大。如果能使用https,前端加密起的作用就是用户在前端输入密码,然后进入https传输之前的一个加密。如果能经常换换密钥,并且加密是单向的,尽管能被人看到,也是“有意义的”,只不过这个意义从全局看很小而已。简单说就是性价比不高。但如果不能使用安全的传输,前端加密其实起到的就是安全传输的作用

要进行前端加密比较好的方式是,用户一输入密码,就用公钥加密,然后再用传输,然后服务器用私钥解密,之后在加盐存储。正如上文,这其实是在仿照ssl。

另外一种方式是,对用户密码在前端hash,然后传输到后端后再第这个hash做一次加盐。但是hash使用的算法相对容易破解,如MD5,SHA1等。

前端代码混淆与加密的必要性

压缩(compression)混淆(obfuscation)、加密(encryption),其目的一般就是为了js代码的不可读、不可被分析、不可还原,还可以加上 禁用命令行输出、反调试、反代码美化、字符串加密(在代码里面搜索,找到关键节点)

代码压缩

这一操作的目的,是让最终代码传输量 (不代表代码量, 也不代表文件体积)尽可能小。压缩js的工具,常见的有:YUI Compressor、UglifyJS、Google Closure Compiler 等。

通常在代码压缩的过程中,只改变代码的语法,代码的语义和控制流不会有太大改变

常见做法是把局部变量缩短化,把一些运算进行等价替换等。代码压缩对于代码保护有一些帮助,但由于语义和控制流基本没变,起不了太大作用

在压缩层面上,代码不可读只是一种附带伤害,不是最终目的。

代码混淆

这一操作的目的,是让代码尽可能地不可读,主要用作代码保护。

让代码不可读,增加分析的难度,这是唯一目的。混淆过后文件体积变大一倍也没关系,代码量变多也没关系,运算慢50% 也没关系。

常见的做法有:分离常量、打乱控制流、增加无义代码、检查运行环境如果不对就罢工,等等。

在混淆层面上,代码不可读是最终目的。

代码混淆混淆方法
  • 通过AST语法树 混淆,如 语法混淆(破坏原始语法) 运算混淆(重载运算符) 动态执行(运行时动态决定运算符,干扰静态分析)
  • 通过 控制流 混淆,如将流程划分成多个 块,通过 状态机 驱动
  • 通过 虚拟机 混淆,如加编译器,加壳
JS AST语法树混淆图示

值得一提的是,Google Closure Compiler 的 Advance Level Compression 会压缩类和对象的成员,其压缩结果很难分析,也可以认为是一种混淆,但兼容性不太好。

代码加密

说实话我很难对加密做一个定义,因为加密在Web界有太多歧义了。

有加密就有解密,意味着加密操作可逆,密文可以明文化

就这样看来,在Web界,可以称之为加密的东西包括:HTTPS传输、JavaScript实现对称加密或者不对称加密等等。

这样看来,不可逆的代码压缩和混淆就不能列入加密这个范畴了。

非要找一个可以称之为加密,又经常被人误解为压缩和混淆的东西,Dean Edwards 的 Dean Packer/Unpacker 可以拿来做个例子。具体实现原理:

  1. 去除尽可能多的 有意义信息
  2. 删除 注释、空格、换行、冗余符号 等
  3. 变量重命名,变成 a、b、c 等
  4. 属性重命名,变成 a.a、a.b() 等
  5. 无用代码移除

反代调试

而断点也是代码调试中最基本的了。很多商业产品会在代码中定义一个无限循环的debugger指令,不过某些浏览器会屏蔽这种代码,而有些则不会。这种方法的主要目的就是让那些想要调试你代码的人感到厌烦,因为无限循环意味着代码会不断地弹出窗口来询问你是否要继续运行脚本代码:

代码语言:javascript
复制
setTimeout(function(){while (true) {eval("debugger")
(function() {var a = new Date(); debugger; return new Date() - a > 100;}())

《js经验分享 JavaScript反调试技巧》、《突破前端反调试--阻止页面不断debugger》

但是,不管这么样,下载js到本地,去除debugger代码,使用抓包工具fiddler或者charles,刷新网页的时候抓到这个js,返回的内容改成去除debuger后的代码

WebAssembly重新编译代码

为了解决Javascript的性能问题,Mozilla提出了一套新的面相底层的Javascript语法子集 -- asm.js,其从JIT友好的角度出发,使得Javascript的整体运行性能有了很大的提升。后续Mozilla与其他厂商进行相关的标准化,产出了WebAssembly标准。

WebAssembly重新编译的代码,你去逆向还原,只有去反编译研究了。如果这么说来,其实没有啥代码是安全的了——wasm的标准规范都是完全公开的,因此对于asm.js/wasm标准实现良好反编译器来说,完全可以尽可能的产出阅读性较强的代码从而分析出其中的核心算法代码。。

目前看起来WebAssembly是目前最理想的前端核心代码保护的方案了,我们可以使用C/C++编写相关的代码,使用Emscripten相关工具链编译为asm.js和wasm,根据不同的浏览器的支持情况选择使用asm.js还是wasm。

参考链接:

JAVASCRIPT加密方法,JS加密解密综述(7种) https://www.cnblogs.com/top5/archive/2009/08/07/1540860.html

当我们在谈论前端加密时,我们在谈些什么 https://zhuanlan.zhihu.com/p/22289839

求助前端JS都是用什么加密的? - 鲁小夫的回答:https://www.zhihu.com/question/28468459/answer/41622094

前端加密与混淆,意义、理论、实现。 https://zhuanlan.zhihu.com/p/60890847

私有CA服务器的搭建 https://www.jianshu.com/p/9142d9d186e2

当我们在谈论前端加密时,我们在谈些什么 https://zhuanlan.zhihu.com/p/22289839

前端核心代码保护技术面面观 https://zhuanlan.zhihu.com/p/61651310

转载本站文章《前端安全保障:加密/混淆/反调试/加壳/自定义虚拟机—必要吗》, 请注明出处:https://www.zhoulujun.cn/html/webfront/SGML/web/2017_1221_8168.html