本文内容:

  • 原型链污染的概念以及原理
  • 如何利用原型链污染
  • 例子分析

概念

本文可能会边学边写,思维可能会有些跳跃,但我尽力写明白

JavaScript当中,如果函数用来创建新的对象,则这个函数称为对象的构造函数

比如:

这里其实是构造了一个名叫a的类,他有个属性test,值为1

在JavaScript中,每个函数都有一个prototype属性,这个属性指向函数的原型对象。

所有引用类型(函数,数组,对象)都拥有__proto__属性(隐式原型

所有类拥有prototype属性(显式原型)

原型链是javascript的实现的形式,递归继承原型对象的原型,原型链的顶端是Object的原型。

在JavaScript中,声明一个函数A的同时,浏览器在内存中创建一个对象B,然后A函数默认有一个属性prototype指向了这个对象B,这个B就是函数A的原型对象,简称为函数的原型。这个对象B默认会有个属性constructor指向了这个函数A。

image-20200318235545687

其中类生成的对象的__proto__和类本身的prototype是等价的

image-20200318235901138

所以说,这里你如果修改了对象的__proto属性,同时会印象到这个原型类的属性,这里举个例子

image-20200319001132706

这里只是修改了test这个对象的属性,但由于修改的是对象的原型,那么实际上整条原型链被污染了,它实际上是修改了Object这个类的属性值,从而导致新的继承自Object类的新对象的值也被污染修改了

这里总结一下:

  • 每一个构造函数都有一个原型对象
  • 对象的__proto属性指向原型对象prototype
  • 在调用一个对象的属性的时候,如果在这个对象中没有这个属性,会向__proto寻找,如果没有的话,继续往__proto__.__proto__中寻找,一直找到null为止

image-20200319011223891

如何利用原型链污染呢?

在能控制数组的键值时,可以将其修改为__proto__从而实现原型链污染

  • 对象merge
  • 对象clone(其实内核就是将待操作的对象merge到一个空对象中)

举例

http://prompt.ml/13

function escape(input) {
// extend method from Underscore library
// _.extend(destination, *sources)
function extend(obj) {
var source, prop;
for (var i = 1, length = arguments.length; i < length; i++) {
source = arguments[i];
for (prop in source) {
obj[prop] = source[prop];
}
}
return obj;
}
// a simple picture plugin
try {
// pass in something like {"source":"http://sandbox.prompt.ml/PROMPT.JPG"}
var data = JSON.parse(input);
var config = extend({
// default image source
source: 'http://placehold.it/350x150'
}, JSON.parse(input));
// forbit invalid image source
if (/[^\w:\/.]/.test(config.source)) {
delete config.source;
}
// purify the source by stripping off "
var source = config.source.replace(/"/g, '');
// insert the content using mustache-ish template
return '<img src="{{source}}">'.replace('{{source}}', source);
} catch (e) {
return 'Invalid image data.';
}
}

这里对config.source进行了delete处理,而且我们要输出的src也是config.source,那能不能通过原型链污染来把他的值给覆盖成#" onerror=prompt(1) />呢?

image-20200321222255192

这里要满足删除条件是满足这个正则

if (/[^\w:\/.]/.test(config.source)) {
delete config.source;
}

source传过去一个#即可

这里直接传的话,会发现src无法闭合

image-20200325151907487

这里找了一下,网上建议是用正则的匹配规则

image-20200325151950002

image-20200325152120263

所以这里通过$`来实现把前面<img src="重复输出来闭合src

这时候实际上src="<img src="

image-20200325152450879

Payload: {"source":"#","__proto__": {"source":"$`onerror=prompt(1) />"}}