专栏首页字节脉搏实验室JavaScript原型、原型链及原型链污染

JavaScript原型、原型链及原型链污染

文章源自【字节脉搏社区】-字节脉搏实验室

作者-purplet

0x00 前言

因为在CTF中时常也会考察原型链污染的问题,以前也一直让我捉襟见肘,一直没有系统的学习了解过JS原型的这些相关概念,因此写下本文,通过不断总结大佬的文章,写出自己对于此部分内容的理解。同时建议学习本文前要有对面向对象部分知识的一定理解(无论哪种语言)。

JavaScript没有”子类”和”父类”的概念,也没有”类”(class)和”实例(instance)的区分,全靠一种很奇特的”原型链”(prototype chain)模式,来实现继承。 在javascript中一切皆对象,因为所有的变量,函数,数组,对象 都始于object的原型即object.prototype。

0x01 JavaScript原型

一、对象和函数

在学习原型和原型链之前,首先一定要搞清楚对象和函数到底有什么区别和联系:

“对象是由函数创建的,而函数又是一种对象。”这样一句话要深刻记忆。

我们都知道JavaScript可以在浏览器中使用“F12”打开的控制台中输入JavaScript代码进行执行,但也要知道其实在浏览器中已经内置了几个全局函数可供随时调用,如:Number()、String()、Boolean()、Object()等 。

在JavaScript中声明一种数据类型的变量时其实有以下两种方式,而第一种可以更直观的体现对象和函数之间的关系,但第二种在各种语言中都较为常用。

但第二种虽然我们是用赋值形式创建的,但在JavaScript的内部,则仍然是通过调用函数来创建对象的。也就是说他们是一样的。

而对于“函数又是一种对象”这句话,也可以使用 instanceof 关键字来验证:

instanceof 的作用是判断一个对象是不是一个函数的实例。 比如 obj instanceof fn 实际上是判断fn的prototype是不是在obj的原型链上。 比如: obj.__proto__ === fn.prototype obj. __proto__.__proto__=== fn.prototype obj. __proto__ … __proto__ === fn.prototype 以上只要一个成立即可。

以上这个内容如果现在看不懂,不要着急后面会解释什么是原型、原型链和__proto__属性。

二、原型(共有属性):__proto__

首先定义个对象:var str = new String(‘hello’); 输出看看该对象中包含哪些属性:

再创建一个num对象。

可以看到两个不同的对象,但都存在__proto__属性。再看以下例子:

肯定会疑惑valueOf和toString方法是哪里来的呢,其实这两个方法也都是在__proto__属性中带来的,打开__proto__的指向箭头就可以看到

总结:不只是str和num对象,每个对象中都有__proto__属性,JavaScript将这些对象(如:Number(函数也是对象))中的共有属性,拿了出来,全都集中到一个新的对象(num)中。而新对象中,就保存着一个__proto__,指向这个原对象。

而__proto__所指向的这个原对象,也叫做原型对象。后文会继续解释。

而既然存在共有属性,那也一定存在独有属性。string对象有string对象的属性是其他对象没有的;number对象有number对象的属性是其他对象没的;boolean对象有boolean对象的属性是其他对象没有的;以此类推。 那这个“独有对象”又是保存在哪儿的呢?我们来看看什么是prototype。

三、函数的原型(prototype)

上面说到,__proto__是每个对象都有的属性,那么要区别记住的是prototype是函数才有的属性。

再继续了解prototype属性前再补充学习几个知识点:

1-什么是构造函数

Person就是一个构造函数,我们使用 new 创建了一个实例对象 person

2-constructor属性

接着按照刚刚的例子查看Person和person的结构输出。

可以看到person的构造函数Person存在的原型包含一个constructor属性。接下来记住一句话:“每个原型(prototype)都有一个 constructor 属性指向关联的构造函数,实例原型指向构造函数 ”。再看person的结果中__proto__属性所指的constructor属性也是与之关联的构造函数,而对于该例中,它的构造函数就是function Person()函数,因此整个结构图如下图所示。

所以以下代码是成立的

0x02 JavaScript原型链

其实当认真理解完上面的内容,原型链的概念就基本清楚了,以下总结出几点:

1-从上面的代码中可以看到,创建person对象虽然使用的是由构造函数Person创建,但是对象创建出来之后,这个person对象其实已经与Person构造函数没有任何关系了,person对象的__proto__属性指向的是Person构造函数的原型对象(Person.prototype)。 2-如果使用new Person()创建多个对象person1、person2、person3,则多个对象都会同时指向Person构造函数的原型对象。 3-我们可以手动给这个原型对象添加属性和方法,那么person1、person2、person3这些对象就会共享这些在构造函数的原型对象中添加的属性和方法。 4-如果我们访问person中的一个属性name,如果在person对象中找到,则直接返回。如果person对象中没有找到,则直接去person对象的__proto__属性指向的原型对象中查找,如果查找到则返回。(如果原型中也没有找到,则继续向上找原型的原型—原型链),直到最高级Object的__proto__为Null为止。 5-如果通过person对象添加了一个属性name,则通过person访问name时,就相当于屏蔽了原型中的属性name,输出的是person对象中的name值 6-通过person对象只能读取构造函数的原型中的属性name值,而不能修改原型中的属性name值。person.name = “purplet”; 并不是修改了原型中的值,而是在person对象中给添加了一个属性name。

下面可以把原型、原型链的关系当作一个公式一般去记忆:

由于__proto__是任何对象都有的属性,而JavaScript里万物皆对象,所以会形成一条__proto__连起来的链条,但递归访问__proto__必须最终到头,其终点是Null 当JavaScript引擎查找对象的属性时,先查找对象本身是否存在该属性,如果不存在,会在原型链上查找,但不会查找自身的prototype,如图所示。

0x03 JavaScript原型链污染

在看懂原型链的那几点内容后,其实就应该可以理解什么是原型链污染了,就是修改其构造函数的原型中的属性值,使其他通过该构造函数实例出的对象也具有该属性值。

可以看到我们修改成功了,新生成的 foo2 对象也具有hacker 属性,如果给foo1再往上加一个__proto__就可以修改(添加)Object的属性了。

那么在哪些情况下原型链会存在污染?

这里我引用郁离歌师傅的博客内容了。

我们思考一下,哪些情况下我们可以设置__proto__的值呢?其实找找能够控制数组(对象)的“键名”的操作即可:

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

以对象merge为例,我们想象一个简单的merge函数:

在合并的过程中,存在赋值的操作target[key] = source[key],那么,这个key如果是__proto__,是不是就可以原型链污染呢?

我们用如下代码实验一下:

结果是,合并虽然成功了,但原型链没有被污染:

这是因为,我们用JavaScript创建o2的过程(let o2 = {a: 1, "__proto__": {b: 2}})中,__proto__已经代表o2的原型了,此时遍历o2的所有键名,你拿到的是[a, b]__proto__并不是一个key,自然也不会修改Object的原型。

那么,如何让__proto__被认为是一个键名呢?

我们将代码改成如下:

可见,新建的o3对象,也存在b属性,说明Object已经被污染:

这是因为,JSON解析的情况下,__proto__会被认为是一个真正的“键名”,而不代表“原型”,所以在遍历o2的时候会存在这个键。

merge操作是最常见可能控制键名的操作,也最能被原型链攻击,很多常见的库都存在这个问题。

0x04 文章参考

https://www.zhihu.com/tardis/sogou/art/44035916

https://www.jianshu.com/p/be7c95714586

https://www.leavesongs.com/PENETRATION/javascript-prototype-pollution-attack.html#0x04

本文分享自微信公众号 - 字节脉搏实验室(zijiemaiboshiyanshi),作者:purplet

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-07-24

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 实战渗透,轻松拿shell五步走

    ?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=system&v...

    字节脉搏实验室
  • 内网渗透常用端口转发与代理工具枚举

    lcx.exe是一个端口转发工具,有Windows版和Linux版两个版本,Windows版是lcx.exe,Linux版为portmap,

    字节脉搏实验室
  • Nc内网端口转发

    Netcat工具可在github下载,瑞士军刀,没啥好说的老牌工具了(杀软杀的厉害)

    字节脉搏实验室
  • 稳扎稳打JS——“继承”

    JS使用原型链实现“继承” JS是“基于对象”的语言,因此没有继承。但可以使用JS的特性实现“继承”——原型链。 JS使用_proto_属性构造原型链,如 ...

    大闲人柴毛毛
  • JavaScript——对象的原型

    如机制和原理(对象基于原型)里所记述的那样,JavaScript是一个基于原型的面向对象的语言。本文着重于对原型的实现机制进行剖析和说明。

    Html5知典
  • ACL 2016:基于深度学习的 NLP 看点

    【新智元导读】计算语言领域顶级会议ACL2016今天公布了10篇优秀论文(outstanding paper)名单。本文介绍 ACL 2016 概况,同时收录清...

    新智元
  • 公共选择学派 | 理性选民的神话(5)

    本次介绍的是卡普兰教授所著《理性选民的神话》一书中的第五章主要思想。第五章标题为:理性的胡闹。

    Defu Li
  • Java自学之道环境变量设置补充

        java自学之道在第0章向大家介绍了如何配置环境变量,这里的环境变量配置只讲了path的配置,而没有讲classpath的配置,因为考虑到刚入门同学配...

    闵开慧
  • 动图+源码+总结:演示 JDK8 中的数据结构(珍藏版)

    最近在整理数据结构方面的知识, 系统化看了下Java中常用数据结构, 突发奇想用动画来绘制数据流转过程

    搜云库技术团队
  • 几张动态图捋清Java常用数据结构及其设计原理

    最近在整理数据结构方面的知识, 系统化看了下Java中常用数据结构, 突发奇想用动画来绘制数据流转过程.

    java进阶架构师

扫码关注云+社区

领取腾讯云代金券