Nodejs原型链污染攻击基础知识
前置知识
原型链污染攻击也称JavaScript Prototype 污染攻击
Javascript
JavaScript(简称“JS”) 是一种具有函数优先的轻量级,解释型或即时编译型的编程语言。虽然它是作为开发Web页面的脚本语言而出名,但是它也被用到了很多非浏览器环境中,JavaScript 基于原型编程、多范式的动态脚本语言,并且支持面向对象、命令式、声明式、函数式编程范式
NodeJS
Node.js发布于2009年5月,由Ryan Dahl开发,是一个基于Chrome V8引擎的JavaScript运行环境,使用了一个事件驱动、非阻塞式I/O模型, 让JavaScript 运行在服务端的开发平台,它让JavaScript成为与PHP、Python、Perl、Ruby等服务端语言平起平坐的脚本语言。
JavaScript数据类型
let和var关键字的区别
使用var
或let
关键字可以定义变量
let和var的区别如下:
var
是全局作用域,let
只在当前代码块内有效
当在代码块外访问let声明的变量时会报错
var
有变量提升,let没有变量提升let
必须先声明再使用,否则报Uncaught ReferenceError xxx is not defined
;var
可以在声明前访问,只是会报undefined
let
变量不能重复声明,var
变量可以重复声明
普通变量
1 | var x=5; |
1 | let x=5; |
数组变量
1 | var a = new Array(); |
1 | var a = []; |
字典
1 | var a = {}; |
1 | var a = {"foo":"bar"}; |
JavaScript函数
在Javascript中,函数使用function
关键字来进行声明
声明一个函数
下面是声明一个函数的示例
1 | function myFuntion() { |
声明带参数的函数
1 | function myFuntion(a) { |
声明带返回值的函数
1 | function myFuntion(a) { |
匿名函数
直接调用匿名函数
1 | (function(a){ |
还可以把变量变成函数,调用fn()
即调用了匿名函数的功能
1 | var fn = function(){ |
闭包
假设在函数内部新建了一个变量,函数执行完毕之后,函数内部这个独立作用域或(封闭的盒子)就会删除,此时这个新建变量也会被删除。
如何令这个封闭的盒子是不会删除?可以使用“闭包”的方法(闭包涉及函数作用域、内存回收机制、作用域继承)
闭包后,内部函数可以访问外部函数作用域的变量,而外部的函数不能直接获取到内部函数的作用域变量
例如不使用额外的全局变量,实现一个计数器
因为add变量指定了函数自我调用的返回值(可以理解为计数器值保存在了add中), 每次调用值都加一而不是每次都是1
1 | var add = (function () { |
JavaScript类
在以前,如果要定义一个类,需要以定义“构造函数”的方式来定义,例如
1 | function newClass() { |
如果想添加一些方法呢?可以在内部使用构造方法
1 | function newClass() { |
为了简化编写JavaScript代码,ECMAScript 6后增加了class
语法
class 关键字
可以使用 class
关键字来创建一个类
形式如下(如果不定义构造方法,JavaScript 会自动添加一个空的构造方法)
1 | class ClassName { |
例子
1 | class myClass { |
使用new创建对象
1 | let testClass = new myClass("testtest"); |
测试
查看testClass对象的test
属性的值,为testtest
1 | console.log(testClass.test); |
往对象添加属性
直接使用.属性名
即可,例如向testClass添加aaa属性
1 | testClass.aaa = 333; |
类的方法
形式如下
1 | class ClassName { |
简单使用NodeJS
搭建简单的Web服务器
安装express框架
1 | npm install express --save-dev |
源码
源码如下,可保存为ezWebServer.js
1 | /* |
运行
首先确保终端在ezWebServer.js所处目录下
1 | node ezWebServer.js |
此时访问127.0.0.1:8080即可看到
原型链污染
什么是原型
这里的原型指的是prototype
比如说上面前言部分讲的JavaScript类那里,
我们使用new新建了一个newClass
对象给newObj
变量
1 | function newClass() { |
实际上这个newObj
变量使用了原型(prototype)来实现对象的绑定【而不是绑定在“类”中,与JavaScript的特性有关,它的“类”与其它语言(例如JAVA、C++)类不同,它的“类”基于原型】
prototype
是newClass
类的一个属性,而所有用newClass
类实例化的对象,都将拥有这个属性中的所有内容,包括变量和方法,如下
简单来说就是:
prototype
是newClass
类的一个属性newClass
类实例化的对象newObj
不能访问prototype,但可以通过.__proto__
来访问newClass
类的prototype
newClass
实例化的对象newObj
的.__proto__
指向newClass
类的prototype
下面这样表示可能比较直观
关系如下
原型链污染原理
原理
现在已经知道实例化的对象的.__proto__
指向类的prototype
,
那么修改了实例化的对象的.__proto__
的内容, 类的prototype
的内容是否也会发生改变?
答案是肯定的,这就是原型链污染的利用方法
一个简单利用的例子
比如说现在有一个类a
1 | function a() { |
然后实例化一个对象obj
1 | var obj = new a(); |
此时查看obj的内容
1 | obj |
修改a类的原型(即Object,如本文什么是原型部分-关系如下
所示),
添加一个属性test1,令其值为123
1 | a.prototype.test1 = 123; |
再次查看obj的内容,多了一个test1
1 | obj |
访问下obj.test1
看看
再实例化一个a的对象
1 | var obj1 = new a(); |
访问obj.test1
,发现也是123
然后尝试通过obj1的.__proto__
属性来修改test1的值
1 | obj1.__proto__.test1 = 124; |
此时访问obj.test1
,发现也被修改成了124
明明没有动obj,obj.test1
却改变了,说明a类中的test1被修改了
1 | obj.test1 |
查看a类的属性,确实如此
通过obj1中.__proto__
属性添加一个新属性,和上面修改a类的原型的过程也是一样的
下面演示添加新属性test2
1 | obj1.__proto__.test2 = 111; |
如下图操作所示
可以发现obj中也出现了新属性test2, 并且a类中也出现了新属性test2
进一步利用
上面的例子中,展示了如何通过对象往类中添加一个新属性并修改这个新属性
那如果想改变已有属性的值呢?
先实例化一个字典对象,叫obj,内有key名为test
,test
的value是123
1 | var obj = {"test": 123}; |
然后通过obj的.__proto__
属性为test重新赋值
1 | obj.__proto__.test = 2; |
再实例化一个空字典对象,叫ooo
1 | var ooo = {}; |
查看ooo的test属性,发现居然是2
因为Object类的test属性已经被污染,而对象ooo和obj同属Object类
那再看看obj的test属性的值,为123
这是为啥?
这就涉及到查找顺序了
查找顺序
描述
关于查找顺序,我觉得我无法写出比P神更好的解释,所以这里直接引用P神的解释
所有类对象在实例化的时候将会拥有
prototype
中的属性和方法,这个特性被用来实现JavaScript中的继承机制。比如:
1
2
3
4
5
6
7
8
9
10
11
12
13 function Father() {
this.first_name = 'Donald'
this.last_name = 'Trump'
}
function Son() {
this.first_name = 'Melania'
}
Son.prototype = new Father()
let son = new Son()
console.log(`Name: ${son.first_name} ${son.last_name}`)Son类继承了Father类的
last_name
属性,最后输出的是Name: Melania Trump
。总结一下,对于对象son,在调用
son.last_name
的时候,实际上JavaScript引擎会进行如下操作:
- 在对象son中寻找last_name
- 如果找不到,则在
son.__proto__
中寻找last_name- 如果仍然找不到,则继续在
son.__proto__.__proto__
中寻找last_name- 依次寻找,直到找到
null
结束。比如,Object.prototype
的__proto__
就是null
更多描述
比如说此处的obj
利用.__proto__
修改值后的test属性在当前对象的test属性下面(也就是在当前对象所绑定的prototype中),
所以优先读取当前对象下的test属性,即未被修改的值123
而ooo对象由于当前属性中没有test属性,只能从它绑定的prototype中找test对象(或下一级的prototype),
没找到返回undefined
参考链接
深入理解 JavaScript Prototype 污染攻击
最后
谢谢观看