adout JS
# JS 混淆与反混淆
JavaScript
混淆 (Obfuscation)
是指通过一系列技术手段,使 JS 代码变得难以理解和分析,增加代码的复杂性和混淆度,阻碍逆向工程和代码盗用。实际上就是一种保护 JS 代码的手段。
JS 最早被设计出来就是为了在客户端运行,直接以源码的形式传递给客户端,如果不做处理则完全公开透明,任何人都可以读、分析、复制、盗用,甚至篡改源码与数据,这是网站开发者不愿意看到的。
JS 混淆也是 CTF 比赛中常见的 Web 题型
# 常见混淆手段
# 代码压缩
就是将代码中的空格和换行符全部删除让代码变成一坨甚至一行,这种方式甚至会出现在一些代码审计的题中阻碍代码阅读
# 代码混淆
# 变量名 / 函数名替换
将有意义的变量名和函数名替换为随机生成的名称
1 | /* |
# 字符串混淆
将代码中的字符串替换为十六或八进制、编码或者加密形式,防止代码被轻易读取,或者通过拼接使之不可以通过搜索查找原本字符串
1 | // let str = 'eval' |
# 访问成员变量的方法
JavaScript 中可以通过 window.eval()
访问 windows 对象的 eval 方法,也可以用 window['eval']
来访问
# 利用数组拆分
1 | // console.log(new window.Date().getTime()) |
# 常量改算术表达式
1 | // var num = 1234 |
还可以进一步改成函数调用表达式
1 | // var num = 602216 ^ 603322 |
像这种方式在 js 中还有很多,(甚至可以套娃?滑稽.jpg
# 反调试
# 禁止 debugger
# 定时器死循环
1 | function debug() { |
1 | var c = new RegExp("1"); |
# 内存耗尽
更隐蔽的反调试手段,代码运行造成的内存占用会越来越大,很快会使浏览器崩溃。
1 | var startTime = new Date(); |
# 清空控制台
1 | function clear() { |
# 检测函数、对象属性修改
攻击者在调试的时,经常会把防护的函数删除,或者把检测数据对象进行篡改。可以检测函数内容,在原型上设置禁止修改。
1 | // eval函数 |
# 一些混淆与反混淆工具
混淆工具:
- YUI Compressor
- Google Closure Compiler
- UglifyJS
- JScrambler // 付费,其余均为免费
反混淆工具:
# JS 原型链污染
一个超好的原理解析文章 JavaScript 原型链污染 (yuque.com)
# JS 创建对象
1 | //普通方式 |
# JS 继承机制
JS 没有 java 中的 class,它通过 prototype
实现继承
1 | function Dog(name) { |
# 万物皆对象 & proto
在 js 中所有的东西都可看为对象。
而在 js 中每一个对象都会有一个 __proto__
的属性。
__proto__
对象可以通过__proto__
来找到其自己的父类。
而对于构造函数也有一个 prototype 与之相对应。
prototype
构造函数prototype
也是一个对象,为构造函数的原型对象
# constructor 构造函数
在 JS 中,每个函数对象还有一个特殊的属性叫做 constructor
。这个属性指向创建该对象的构造函数。当我们创建一个函数时,JS 会自动为该函数创建一个 prototype
对象,并且这个 prototype
对象包含一个指向该函数本身的 constructor
属性。
当我们使用构造函数创建实例对象时,这些实例对象会继承构造函数的 prototype
对象,从而形成原型链。因此,通过 constructor
属性,实例对象就可以访问到创建它们的构造函数。
直接把 constructor
当作反向 prototype
理解即可。以刚才的代码举例:
1 | console.log(Dog.prototype.constructor === Dog); // true |
# 原型链
通俗来讲就是 js 中类之间因继承机制而产生的线性关系。
# 污染
原理很简单,就是 JS 基于原型链实现的继承机制。如果我们能控制某个对象的原型,那我们就可以控制所有基于该原型创建的对象。
1 | // foo是一个简单的JavaScript对象 |
最后,虽然 zoo 是一个空对象 {}
,但 zoo.bar
的结果居然是 2:
原因也显而易见:因为前面我们修改了 foo 的原型 foo.__proto__.bar = 2
,而 foo 是一个 Object 类的实例,所以实际上是修改了 Object 这个类,给这个类增加了一个属性 bar,值为 2。
后来,我们又用 Object 类创建了一个 zoo 对象 let zoo = {}
,zoo 对象自然也有一个 bar 属性了。
那么,在一个应用中,如果攻击者控制并修改了一个对象的原型,那么将可以影响所有和这个对象来自同一个类、父祖类的对象。这种攻击方式就是原型链污染。
以下是一个简单的示范案例:
1 | // 创建一个空对象 userA |
在 CTF 中,往往都是去找一些能够控制对象键名的操作,比如 merge
、 clone
等,这其中 merge
又是最常见的可操纵键名操作。最普通的 merge
函数如下:
1 | function merge(target, source) { |
此时,我们运行以下代码,以 JSON 格式创建 o2
,在与 o1
合并的过程中,经过赋值操作 target[key] = source[key]
,实现了一个基本的原型链污染,被污染的对象是 Object.prototype
:
1 | let o1 = {}; |
- Title: adout JS
- Author: Fc04dB
- Created at : 2024-05-23 10:54:43
- Updated at : 2024-10-22 21:48:16
- Link: https://redefine.ohevan.com/2024/05/23/adout-JS/
- License: This work is licensed under CC BY-NC-SA 4.0.