专栏名称: 前端早读课
我们关注前端,产品体验设计,更关注前端同行的成长。 每天清晨五点早读,四万+同行相伴成长。
目录
相关文章推荐
前端早读课  ·  【招聘】北京Dine团队招聘前端工程师 ·  2 天前  
前端大全  ·  2024 年 12 个最佳 ... ·  5 天前  
前端大全  ·  没想到!海外巨头们把小程序玩得风生水起 ·  4 天前  
前端充电宝  ·  前端的本质 ·  3 天前  
前端充电宝  ·  前端的本质 ·  3 天前  
前端早读课  ·  【早阅】使用 Fabric.js v6 ... ·  3 天前  
51好读  ›  专栏  ›  前端早读课

【第3366期】JavaScript 混淆:2024年的终极指南

前端早读课  · 公众号  · 前端  · 2024-09-04 08:00

正文

前言

介绍了 JavaScript 代码的混淆技术,包括混淆的定义、目的、技术、指标以及如何在软件开发生命周期中应用。今日前端早读课文章由 @飘飘翻译分享。

正文从这开始~~

JavaScript 混淆实用指南 2024 提供了一种逐步探讨关键问题的方法,具体包括:

  • 什么是混淆代码?

  • 为什么要对 JavaScript 代码进行混淆处理?

  • 什么是 JavaScript 混淆,它是如何工作的?

  • JavaScript 中的混淆技术和指标有哪些?

  • JavaScript 混淆的示例是什么?

  • 为什么 JavaScript 的混淆技术在某些情况下并不能完全解决问题?

对 JavaScript 和 npm 有一定了解会有帮助,但不必具备这些知识也能深入本指南。让我们开始吧!

第一章:代码混淆是什么?

代码混淆是一种技术,旨在将普通的、易于阅读的代码转换为难以理解和逆向工程的新版本 —— 无论是对人类还是机器而言。

把混淆想象成这样:你打电话给朋友约好一会儿喝咖啡。朋友可能会回复:“你好!抱歉,今天不行,我得照看孩子。明天同一时间怎么样?”。

但假设你的朋友决定把这段话混淆一下,用莎士比亚风格的语言回复你:“早安。我深感歉意,今天无法赴约。明日同一时间可好?需照顾孩子,特此致歉。保重。”

【第3296期】别再用花哨的技巧来编写“优雅”的代码了!

好吧,这听起来有点复杂。如果你仔细看看朋友的这段回复,很明显整段话变得不必要地复杂了。解读这段信息要花费更长的时间,而且还存在一些冗余。再加上你的朋友添加了一些无关紧要的细节。当然,你可以忍受一次解读这些废话。但如果这事情成为常态,你还会继续打电话给这个朋友吗?

虽然这个例子听起来有点荒谬,但它包含了代码混淆中使用的一些技术逻辑。在下一章中,我们将看到代码混淆的真实例子,希望你能看到其中的相似之处。

尽管(幸好)在日常对话中几乎没有混淆的例子,但代码混淆已经存在很长时间了 —— 在 1972 年的书籍中就有 “代码混淆” 的引用。

混淆技术已经在多种编程语言中被使用,尤其是 C/C++(甚至有一个专门混淆 C 代码的比赛)和 Perl。但在一种语言,混淆技术在开发者和企业主中获得了极大的流行:JavaScript

第二章:JavaScript 混淆

为什么要对 JavaScript 代码进行混淆处理?

JavaScript 已经成为网络领域的主流语言。它几乎为当前所有网站提供动力,随着跨平台 JavaScript 框架(如 React Native 和 Ionic)的兴起,使得开发人员能够使用共享的 JS 代码库来创建移动和桌面应用程序。

随着全球 500 强企业中几乎每一家都在使用 JavaScript 开发应用程序,如今,JavaScript 正被用于各种领域,如移动银行、电子商务和流媒体服务,以开发关键应用。

这引出了一个主要问题:为什么要对 JavaScript 代码进行混淆?

JavaScript 是一种解释性语言,因此客户端的 JavaScript 需要浏览器中的解释器来读取、解释和运行它。这也意味着任何人都可以使用浏览器调试器轻松地查看 JS 代码并随意阅读或修改它。

【第3294期】百度&YY设计稿转代码的探索与实践

在下面的示例中,你可以看到有人轻松访问虚拟键盘的代码逻辑,在这个虚拟键盘中,银行的客户输入他们的密码。

由于客户端 JavaScript 代码的访问如此容易,攻击者几乎可以不费吹灰之力利用这一安全漏洞并攻击任何未受保护的代码。

当我们谈论一个简单的 Web 应用程序时,所有这些似乎都是微不足道的。但是,尤其是对于企业和《财富》500 强公司来说,经常将重要的业务逻辑存储在其应用程序的客户端。

如果你了解应用程序安全的基础知识,你会知道代码机密应始终保存在可信的执行环境中,例如后端服务器。但这是理论优先于实践的案例之一。当公司将这些重要的逻辑存储在客户端时,他们通常是因为无法实际地将其保存在服务器端。

这种情况的一个常见原因是,某些移动应用程序根本就没有后端。另一个例子是某些与用户体验相关的代码(如分析算法)必须在客户端运行。然而,最常见的原因还是性能。服务器调用需要时间,而当你有一个对性能要求极高的服务时,比如流媒体平台或 HTML5 游戏,将所有 JavaScript 代码存储在服务器上是不可行的。

无论如何,公司通常不希望透露他们的专有逻辑。他们绝对不希望暴露代码机密。尤其是当竞争对手可以逆向工程代码并复制专有算法时。

【第3360期】在JavaScript中从外部解决Promise:实际应用场景

除了知识产权盗窃之外,客户端 JavaScript 还可能成为更复杂攻击的目标,如自动化滥用、盗版、作弊和数据泄露(了解更多关于 JavaScript 保护和深入安全性 的信息)。

难怪像 ISO 27001 这样的信息安全标准会做出如下声明:

如果程序源代码没有得到充分的保护,可能会受到攻击,攻击者可以利用它以隐蔽的方式入侵系统。如果程序源代码是业务成功的关键,那么一旦丢失,很快就会摧毁业务价值。

而 OWASP(开放 Web 应用安全项目)在其 移动十大安全风险 指南中明确强化了这一建议:

“为了防止有效的逆向工程,你必须使用一个混淆工具。”

什么是 JavaScript 混淆?

JavaScript 混淆是一种代码转换系列,将普通的、易于阅读的 JS 代码转换为极难理解和逆向工程的修改版本。

与加密不同,你必须提供一个用于解密的密码,而在 JavaScript 混淆中没有解密密钥。如果我们在客户端对 JavaScript 进行加密,那么这是一项徒劳无功的工作 —— 如果我们需要向浏览器提供解密密钥,那么该密钥可能会被泄露,代码也就很容易被访问了。

因此,通过混淆处理,浏览器可以像处理原始未混淆代码一样轻松地访问、读取和解释混淆后的 JavaScript 代码。尽管混淆后的代码看起来完全不同,但在浏览器中它将产生完全相同的输出。

JavaScript 混淆经常与其他技术混淆,例如代码压缩、优化和压缩。让我们快速了解它们之间的区别。

JavaScript 混淆与代码压缩、优化和压缩的区别

代码压缩工具通过删除代码中不必要的字符(空格、换行符、小标识符等)来减少代码的大小 —— 但它们不保护源代码。

代码优化工具主要用于提高代码性能(应用程序的速度和内存使用)。有时它们也可以使代码更难阅读,但这并不提供保护(我们稍后会看到)。

代码压缩和打包工具通过编码和打包技术来减小代码大小,但它们也不保护源代码。

另一个常见的误解是,如果你已经使用 SAST 或 DAST 来查找 JavaScript 代码中的漏洞并修复它们,这就解决了你所有的代码问题。虽然 SAST 或 DAST 对修复漏洞有帮助,但它们无法防止代码篡改和逆向工程,因为篡改代码和逆向工程并不需要漏洞。因此,建议同时使用 SAST 和 DAST 以及 JavaScript 源代码保护。

JavaScript 混淆技术与目标

现在我们已经清楚了 JS 混淆的广义定义,让我们更深入地探讨它对源代码具体做了什么。

混淆的主要目的是隐藏 JavaScript 和代码中可能被攻击者或竞争对手攻击的部分。因此,很容易理解为什么你想要对代码中的任何数据进行混淆。通过隐藏诸如变量对象字符串等内容,你可以让任何人更难理解代码中包含了哪种数据。

旁注

仅仅依靠混淆技术来保护代码中的敏感数据是一种不好的做法,这也是为什么人们常说 “模糊并不等于安全” 的原因。

依您的使用情况,您应该始终在采取良好的安全措施的基础上使用混淆技术。想象一下:如果你想要保护一堆现金,你可能会把它放在一个保险箱里。但你不会把保险箱完全暴露在门廊上,而是会将其藏在某个地方,以减少有人发现并试图打开它的可能性。

但隐藏数据只是 JS 混淆的几个维度之一。高级的混淆技术还会对布局和程序控制流进行混淆,并且包括多个优化技术。通常它会针对以下目标进行混淆:

  • 标识符;

  • 布尔值;

  • 函数;

  • 数字;

  • 断言;

  • 正则表达式;

  • 语句;

  • 程序控制流。

常见的 JavaScript 混淆技术

最常见的 JavaScript 混淆技术包括重新排序编码拆分重命名逻辑隐藏技术。

理解每种技术的细节超出了本指南的范围,但这些技术的名称已经相当自解释了。如果你想了解每种技术的详细信息,请查看有关代码完整性转换资产的文档。

然而,控制流混淆 值得深入解释,因为它是一种特别有用的技术。它通过消除自然条件结构,使程序流程变得更加难以理解,从而增加了代码的复杂性。

从技术角度来看,它将源代码的所有基本块(如函数体、循环和条件分支)拆分,并将它们全部放入一个单一的无限循环中,由一个控制程序流程的 switch 语句控制。它还可以包括:

  1. 克隆(与原始基本块语义相同的副本,可以与其原始基本块互换执行);

  2. 死代码(从不执行的伪造基本块,但它们会模仿并可能与将要执行的代码混淆在一起);

  3. 隐藏步骤(这些步骤混淆了切换变量,使其更难理解下一个将执行的 switch case)。

这些技术的结合增加了混淆代码的整体复杂性。

另一种值得注意的混淆方法是多态性的使用。

多态 JavaScript 混淆 是由 Jscrambler 使用的一种独特技术,它确保每次代码混淆都会产生完全不同的代码。

让我们看一个例子:

假设你每周都在部署混淆后的代码构建。攻击者可能会在你发布新版本后立即开始尝试解混淆代码。

假设他们在你发布新版本之前取得了一些进展,如果新版本的混淆代码与之前的代码相似,攻击者可以利用大部分进展继续他们的逆向工程。然而,使用多态混淆,新版本的代码将完全不同,这意味着之前的大部分(如果不是全部的话)去混淆进展将变得毫无用处。

JavaScript 混淆示例

现在我们暂时搁置理论,直接进入一个实际的 JavaScript 混淆示例

考虑下面的代码片段,这是一个用于向电子商务网站的购物者推荐产品的算法。它基于客户的购买历史为特定客户生成产品推荐列表。

这看起来像是相当普通的代码。但是,假设这是一家公司开发的专有算法,用作示例展示在网站上。如果我们是访问他们网站的竞争对手,我们可以很快找到这段代码,然后为所欲为。

作为这段代码的所有者,我们了解这一风险,并希望保护它。在我们深入探讨实际的 JS 混淆之前,让我们看看压缩会对代码产生什么影响。

乍一看,你可能会说这段代码更难读了。但只需一秒钟你就会意识到,我们的所有函数、对象和变量都清晰地展示在那里。再次强调,压缩并不能提供任何形式的代码保护。

现在让我们看看在添加了单一混淆技术后,代码会变成什么样子。

首先,这看起来根本不像可识别的 JavaScript 代码。它使用了一种叫做控制流扁平化的技术进行混淆,这是一种独特的 JS 代码混淆器变换,它会将程序流扁平化,隐藏掉原本会使代码更易读的每一个自然的条件构造。

上面的代码片段展示了代码的前几行,但整个代码几乎有 700 行。如果我们运行这段代码,浏览器将像原始代码一样运行它。

现在让我们看看一个极端混淆的例子:

这是一段使用了非字母数字混淆的代码,你在现实中不常看到这种情况。对人类来说,这看起来几乎不可能逆向工程。然而,如果我们通过自动化逆向工程工具运行这段代码,我们几乎可以立即获得原始代码。

这种看似极端的混淆实际上是弱混淆的一个极好的例子。

我们如何区分弱加密和强加密呢?首先,我们需要了解加密的指标。

JavaScript 混淆指标

Collberg 等人在他们的论文《混淆转换分类》中提供了对 JS 混淆指标的最清晰解释之一。

正如这些研究人员所说,有三个关键指标:效力弹性成本

指标 1:效力

效力是一个回答了 “在多大程度上会让人类读者感到困惑?” 的问题的指标。

回顾我们前面提到的三个例子,我们可以自信地说,第一个例子(压缩)效力低,而第二个例子效力高,第三个例子效力极高。

你可能会想,“我怎么计算效力指标?”。通常,效力是通过软件复杂性指标(如 Halstead 指标)来衡量的。所以,你通常不会自己计算效力。

换句话说,你可以通过一些特定的特征来更轻松地评估这种转变的效力。因此,高效力的转变通常具有以下特征:

  • 隐藏常量和名称;

  • 使代码执行的顺序难以理解;

  • 使理解相关代码变得困难;

  • 增加整体程序大小并引入新的类和方法;

  • 引入新的断言并重写条件和循环结构;

  • 增加远程变量依赖。

然而,在评估混淆的 JavaScript 代码时,仅考虑其效力是一个关键错误。正如我们之前所见,高效力转换可能非常容易被破解。这就是为什么我们还必须考虑另一个指标:弹性。

指标 2:弹性

弹性指标回答了 “自动去混淆攻击能被抵御到什么程度?” 的问题。

例如,我们可以添加一个 if 语句,在我们的代码中引入一个伪变量。人类可能需要一些时间来识别该代码为伪代码,但去混淆器会立即删除该语句。

这就是为什么弹性是通过考虑两个不同的方面来计算的:

  • 开发能够恢复转换结果的去混淆器所需的时间;

  • 逆向混淆器有效恢复转换所需的执行时间和空间。

这是大多数 混淆工具 无法达到的指标,尤其是免费的 JS 混淆器。它们可能输出看起来高度混淆的代码,但通常很容易使用现成的工具将其去混淆。在比较不同的混淆结果时,我们不能仅仅信任自己的眼睛和感知。

然而,Jscrambler 的转换 是为了尽可能地实现最大弹性而构建的。

具体而言,Jscrambler 包括了一个 代码加固 功能,该功能内置于每个代码混淆中。这个功能为代码提供了对所有自动化逆向工程工具和技术的保证性、最新的弹性。

当这些工具试图逆向 Jscrambler 保护的代码时,它们将超时或挂起,迫使攻击者手动处理,并手动面对令人头疼的高效力转换。

指标 3:成本

最后,我们有成本指标,它代表了转换对转换后的应用程序执行时间的影响,以及对应用程序文件大小的影响。

这很重要,因为你不希望应用程序的性能因为混淆而受到影响,尤其是当你有一个面向客户端的应用程序,如果应用程序运行速度变慢,你可能会因此失去收入。

一个好的混淆工具应该始终提供特定的功能,以尽可能减少性能损失,并允许你在代码的整个过程中微调转换。这也是免费的 JS 混淆器的另一个缺点,通常它们提供的微调保护的能力非常有限。

相比之下,使用 Jscrambler,你会发现有几个功能可以自动微调保护以最大化性能,例如性能分析 和应用程序分类。

了解这三个混淆指标对于确保你的代码真正受到保护,而不仅仅是看起来受到保护至关重要。

第三章:混淆与软件开发生命周期 (SDLC)

JavaScript 混淆不应导致流程开销或使你的 SDLC 过于复杂。为了确保这种情况不会发生,必须解决两个维度:兼容性和集成。

混淆的 JavaScript 代码的兼容性

在兼容性方面,首先要了解你的源代码是否与特定的混淆工具兼容。

一些 JS 混淆器与某些 ECMAScript 版本不兼容,可能需要你在保护之前将代码进行转换。更常见的是,它们可能与某些 JS 库和框架不兼容,需要进行大量更改才能启用代码保护。

另一个重要方面是混淆代码的兼容性。回到我们对 JavaScript 混淆的原始定义,它是 “用于转换(...)代码” 的工具。虽然你的混淆代码应始终像原始代码一样运行,但混淆可能会导致某些兼容性变化,特别是在特定浏览器版本上。

作为企业级产品,Jscrambler 确保与所有 ECMAScript 版本兼容,并提供诸如 “Browser Compatibility” 等功能,以提供对受保护代码兼容性的可视化和控制。因此,您始终可以确保受保护代码与目标浏览器版本兼容。此外,它确保与所有主要的 JS 库和框架兼容。

混淆、CI/CD 集成和工程师的幸福感

如果你想确保所有应用程序的部署都经过混淆处理,你可能会希望自动化此过程。在这里,尤其重要的是要考虑你使用的 JavaScript 框架以及你的构建过程是如何结构化的。

如前所述,一些混淆器与 JavaScript 框架的兼容性非常有限,特别是与 React Native 和 Ionic 的兼容性。因此,它们通常无法成功混淆代码。

在 Jscrambler 的情况下,混淆过程是在构建时完成的,并且与所有主要的 JavaScript 框架完全兼容。

Jscrambler 可以轻松集成到 React、Angular、Vue、Node.js、React Native、Ionic、NativeScript等框架的构建过程中。将 Jscrambler 集成到你的 CI/CD 管道中很简单,甚至还有针对特定构建过程的集成:你只需要调用 Jscrambler API 并获得应用程序的受保护版本。这个受保护的版本就是你应该部署的版本。

流畅的 CI/CD 集成肯定会让你的工程师脸上露出微笑,但还有另一个 “生活质量” 功能在混淆时特别相关。

在看到前面的混淆代码示例后,你可能会想 “我该如何调试这个受保护的代码?” 由于混淆的目的是使代码更难理解,当开发人员在生产环境中调试错误时,可能会让他们的生活变得非常困难。因此,源映射变得尤为重要。

尽管许多混淆工具不提供全面的源映射,Jscrambler 源映射 能够轻松将混淆代码映射回其原始源代码 —— 无论是通过 Web 应用程序还是通过 Jscrambler CLI。

支持与信任

与所有安全相关的事物一样,混淆是一个高风险过程

就像使用弱 JS 混淆器可能提供虚假的且危险的安全感一样,错误配置任何混淆工具也可能导致严重的问题,危及应用程序的整体安全性和可用性。

那么,如果你不是 JS 混淆专家,你该如何在配置中避免这些陷阱呢?

为了防止被糟糕的配置蒙蔽双眼,确保你使用的混淆工具提供全面的文档和优先支持非常重要。每个应用程序都不同,而混淆绝不是一刀切的解决方案。通过依靠一个专门的支持团队,你可以更轻松地微调混淆以匹配你的特定用例,并避免可能降低应用程序可用性的常见陷阱。

事实就是如此:安全就是信任。

就像你不会将源代码(特别是其中包含敏感信息的源代码)交给任何陌生人一样,你可能也不希望盲目地信任任何 JS 混淆器。

混淆的一个特殊之处在于很难验证最终结果。曾有一些案例,免费混淆器在混淆之前向源代码中添加了恶意软件 / 间谍软件。非常重要的是,在使用工具时要尽职调查,以避免任何不愉快的惊喜。

第四章:超越混淆,JavaScript 保护

通常,大多数关于 JavaScript 混淆的指南到此就结束了。但这个附加章节是必读的,因为它将解释为什么 JS 混淆通常不足以覆盖某些用例。

为什么 JS 混淆通常不足以覆盖某些用例?

虽然混淆应该提供一种很好的方式来防止逆向工程,并使任何人(包括攻击者)难以理解、攻击和潜在地窃取应用程序的逻辑,但更高级的威胁,如代码篡改、数据泄露、盗版和自动化滥用,则需要高级 JavaScript 保护

JavaScript 保护:环境检查 / 锁定

一种重要的 JavaScript 保护机制是所谓的环境检查或代码锁定。它们允许将 JavaScript 代码锁定在特定环境中运行。

这些环境通常包括操作系统、浏览器、域名、日期或某些类型的设备,如未被 root 或越狱的手机。

每种锁定都有助于实现不同的要求。例如,如果你的应用程序处理非常敏感的数据或执行关键任务,你可以防止它在被 root 或越狱的设备上运行,因为这些设备更容易受到攻击。如果你想执行许可协议,你可以向客户提供产品演示,并将该代码锁定在客户的域中,并在特定日期后自动过期。

通常,每当发生锁定违规时,应用程序将会崩溃。因此,这些锁定在防止盗版和许可违规方面尤其有用。但还有另一种几乎在每个用例中都非常有用的 JS 保护类型:运行时保护。

JavaScript 保护:运行时保护

仅仅通过混淆可能很难让攻击者打消念头。在某些类型的攻击中,如数据泄露和自动化滥用,成功攻击的潜在收益可能会使他们愿意花费大量精力进行逆向工程代码

逆向工程的最常见第一步是尝试通过调试代码并在运行时进行实验,以逐步理解部分代码的逻辑。

一种系统的方法可能最终会取得一些成果(这将极大地取决于用于混淆代码的工具和使用多态混淆的情况)。运行时保护 可以通过防止任何类型的调试或篡改受保护的代码,使这种逆向工程过程变得更加困难。

从技术角度来看,这是通过在源代码中散布完整性检查和防调试陷阱来实现的。

为了理解受保护代码的逻辑,攻击者通常会使用调试器并逐步检查代码。如果代码中已经加入了防调试陷阱,每当攻击者尝试使用调试器时,这些陷阱将被触发,故意破坏应用程序并使攻击者陷入无限调试器循环。

当攻击者无法通过调试动态检查代码时,他们的下一步是下载代码以进行静态分析并修改它。成功修改代码是任何人逆向工程或篡改应用程序的必要步骤。然而,如果源代码包含完整性检查,则一旦进行了任何更改(如仅更改单个字符),这些检查将被触发,从而破坏代码,防止攻击成功。

正如你所预料的那样,当这些锁定和检查与其他对策结合使用时,攻击者将会感到非常沮丧。

JavaScript 保护:对策

通常,每当发生代码锁定违规或触发防调试陷阱或完整性检查时,默认响应是破坏应用程序执行以控制可能的威胁。

然而,破坏应用程序只是几种对策中的一种示例。其他可能的对策包括:

  • 将攻击者重定向到另一个页面,使他们失去所有进度;

  • 删除 cookie,尤其是作为阻止抓取攻击的对策;

  • 向仪表板发送实时通知,并提供有关事件的详细信息;

  • 破坏攻击者的环境,方法是崩溃内存、销毁会话和对象;

  • 触发自定义回调函数,提供完全的灵活性和控制以应对威胁。

这种定制级别确实可以帮助微调整体代码保护,以匹配你的特定用例。

深入的安全性

虽然 JavaScript 混淆通常是寻求某种程度的源代码保护的人的入口,但最终混淆通常只是达到目的的一种手段。

在开发应用程序的威胁模型时,理解未受保护 JavaScript 代码带来的风险非常重要。回答这些安全问题始终需要采用深入安全性的方法,这意味着将源代码保护纳入强大的客户端安全策略中。

为了确保 JavaScript 源代码的最大程度的保护,最佳答案是依靠提供强大且具有弹性的混淆技术的可信供应商,并结合运行时保护和广泛的集成。

关于本文
译者:@飘飘
作者:@Jscrambler
原文:https://jscrambler.com/blog/javascript-obfuscation-the-definitive-guide

这期前端早读课
对你有帮助,帮” 
 “一下,
期待下一期,帮”
 在看” 一下 。