前置知识

原型链污染攻击也称JavaScript Prototype 污染攻击

Javascript

JavaScript(简称“JS”) 是一种具有函数优先的轻量级,解释型或即时编译型的编程语言。虽然它是作为开发Web页面的脚本语言而出名,但是它也被用到了很多非浏览器环境中,JavaScript 基于原型编程、多范式的动态脚本语言,并且支持面向对象、命令式、声明式、函数式编程范式

JavaScript 百度百科

NodeJS

Node.js发布于2009年5月,由Ryan Dahl开发,是一个基于Chrome V8引擎的JavaScript运行环境,使用了一个事件驱动、非阻塞式I/O模型, 让JavaScript 运行在服务端的开发平台,它让JavaScript成为与PHPPythonPerlRuby等服务端语言平起平坐的脚本语言。

NodeJS 百度百科

JavaScript数据类型

let和var关键字的区别

使用varlet关键字可以定义变量

let和var的区别如下:

  • var是全局作用域,let 只在当前代码块内有效
    当在代码块外访问let声明的变量时会报错
  • var有变量提升,let没有变量提升
    let必须先声明再使用,否则报Uncaught ReferenceError xxx is not definedvar可以在声明前访问,只是会报undefined

  • let变量不能重复声明,var变量可以重复声明

普通变量

1
2
3
4
var x=5;
var y=6;
var z=x+y;
var x,y,z=1;
1
let x=5;

数组变量

1
var a = new Array();
1
var a = [];

字典

1
var a = {};
1
var a = {"foo":"bar"};

JavaScript函数

在Javascript中,函数使用function关键字来进行声明

声明一个函数

下面是声明一个函数的示例

1
2
3
function myFuntion() {

}

声明带参数的函数

1
2
3
function myFuntion(a) {

}

声明带返回值的函数

1
2
3
function myFuntion(a) {
return a;
}

匿名函数

直接调用匿名函数

1
2
3
(function(a){
console.log(a);
})(123);

还可以把变量变成函数,调用fn()即调用了匿名函数的功能

1
2
3
var fn = function(){
return "将匿名函数赋值给变量";
}

闭包

假设在函数内部新建了一个变量,函数执行完毕之后,函数内部这个独立作用域或(封闭的盒子)就会删除,此时这个新建变量也会被删除。

如何令这个封闭的盒子是不会删除?可以使用“闭包”的方法(闭包涉及函数作用域、内存回收机制、作用域继承)

闭包后,内部函数可以访问外部函数作用域的变量,而外部的函数不能直接获取到内部函数的作用域变量


例如不使用额外的全局变量,实现一个计数器

因为add变量指定了函数自我调用的返回值(可以理解为计数器值保存在了add中), 每次调用值都加一而不是每次都是1

1
2
3
4
var add = (function () {
var counter = 0;
return function () {return counter += 1;}
})();

17001014295377

JavaScript类

在以前,如果要定义一个类,需要以定义“构造函数”的方式来定义,例如

1
2
3
4
5
function newClass() {
this.test = 1;
}

var newObj = new newClass();

如果想添加一些方法呢?可以在内部使用构造方法

1
2
3
4
5
6
7
8
9
function newClass() {
this.test = 123;
this.fn = function() {
return this.test;
}
}

var newObj = new newClass();
newObj.fn();

为了简化编写JavaScript代码,ECMAScript 6后增加了class语法

class 关键字

可以使用 class 关键字来创建一个类

形式如下(如果不定义构造方法,JavaScript 会自动添加一个空的构造方法)

1
2
3
class ClassName {
constructor() { ... }
}

例子

1
2
3
4
5
6
class myClass {
//newClass的构造方法如下
constructor(a) {
this.test = a;//含有一个test属性,值为构造时传入的参数
}
}

使用new创建对象

1
let testClass = new myClass("testtest");

测试

查看testClass对象的test属性的值,为testtest

1
console.log(testClass.test);

16720403649970

往对象添加属性

直接使用.属性名即可,例如向testClass添加aaa属性

1
testClass.aaa = 333;

1786060610611393

类的方法

形式如下

1
2
3
4
5
6
class ClassName {
constructor() { ... }
method_1() { ... }
method_2() { ... }
method_3() { ... }
}

简单使用NodeJS

搭建简单的Web服务器

安装express框架

1
npm install express --save-dev

17001018639845

源码

源码如下,可保存为ezWebServer.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/*
* 引入express框架,使用require函数传递形参 'express' 进行引入,
* 其实在 let 的后面的名称可以自己定义即可
*/
let express = require('express');

/*
* 使用引入进来的express框架的变量名express来构建一个web服务器实例,
* 名叫myWeb,也可自定义实例名称
*/
let myWeb = new express();

/*
* 往实例 myWeb 的调用函数use传入指定的网络路径和自己编写的响应中间件(其实就是一个函数),
* 这就是服务器的接口的编写方式
*/
myWeb.use("/",function(req,res){
res.send("Hello, NodeJS and express!");
res.end();
});

/*
* 调用listen函数,传递好服务器要用的端口号,一般尽量不是电脑操作系统保留范围的端口号即可,
* 8080就是这个服务器的端口号
*/
myWeb.listen(8080,function(){
//这里可以输入服务器启动成功后要执行的代码,如启动是否成功等终端输出提示,一般这个回调函数可有可无

});

运行

首先确保终端在ezWebServer.js所处目录下

1
node ezWebServer.js

17860606427929

此时访问127.0.0.1:8080即可看到

18430702514472

原型链污染

什么是原型

这里的原型指的是prototype

比如说上面前言部分讲的JavaScript类那里,

我们使用new新建了一个newClass对象给newObj变量

1
2
3
4
5
function newClass() {
this.test = 1;
}

var newObj = new newClass();

实际上这个newObj变量使用了原型(prototype)来实现对象的绑定【而不是绑定在“类”中,与JavaScript的特性有关,它的“类”与其它语言(例如JAVA、C++)类不同,它的“类”基于原型】

prototypenewClass类的一个属性,而所有用newClass类实例化的对象,都将拥有这个属性中的所有内容,包括变量和方法,如下

18770525536662

简单来说就是:

  • prototypenewClass类的一个属性
  • newClass类实例化的对象newObj不能访问prototype,但可以通过.__proto__来访问newClass类的prototype
  • newClass实例化的对象newObj.__proto__指向newClass类的prototype

下面这样表示可能比较直观

17251013107125124

关系如下

187705296489111

原型链污染原理

原理

现在已经知道实例化的对象的.__proto__指向类的prototype

那么修改了实例化的对象的.__proto__的内容, 类的prototype的内容是否也会发生改变?

答案是肯定的,这就是原型链污染的利用方法

一个简单利用的例子

比如说现在有一个类a

1
2
3
function a() {
this.test = 1;
}

然后实例化一个对象obj

1
var obj = new a();

此时查看obj的内容

1
obj

18750816408096

修改a类的原型(即Object,如本文什么是原型部分-关系如下所示),

添加一个属性test1,令其值为123

1
a.prototype.test1 = 123;

16240206214120

再次查看obj的内容,多了一个test1

1
obj

182311169186113

访问下obj.test1看看

16921005305214

再实例化一个a的对象

1
var obj1 = new a();

访问obj.test1,发现也是123

18430702628198

然后尝试通过obj1.__proto__属性来修改test1的值

1
obj1.__proto__.test1 = 124;

此时访问obj.test1,发现也被修改成了124

明明没有动objobj.test1却改变了,说明a类中的test1被修改了

1
obj.test1

1786060295130119

查看a类的属性,确实如此

16560315363464

通过obj1中.__proto__属性添加一个新属性,和上面修改a类的原型的过程也是一样的

下面演示添加新属性test2

1
obj1.__proto__.test2 = 111;

如下图操作所示

17891230367741

可以发现obj中也出现了新属性test2, 并且a类中也出现了新属性test2

18500827819769

进一步利用

上面的例子中,展示了如何通过对象往类中添加一个新属性并修改这个新属性

那如果想改变已有属性的值呢?


先实例化一个字典对象,叫obj,内有key名为testtest的value是123

1
var obj = {"test": 123};

然后通过obj的.__proto__属性为test重新赋值

1
obj.__proto__.test = 2;

再实例化一个空字典对象,叫ooo

1
var ooo = {};

查看ooo的test属性,发现居然是2

18430708287361

因为Object类的test属性已经被污染,而对象ooo和obj同属Object类

那再看看obj的test属性的值,为123

16560314398743

这是为啥?

这就涉及到查找顺序

查找顺序

描述

关于查找顺序,我觉得我无法写出比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引擎会进行如下操作:

  1. 在对象son中寻找last_name
  2. 如果找不到,则在son.__proto__中寻找last_name
  3. 如果仍然找不到,则继续在son.__proto__.__proto__中寻找last_name
  4. 依次寻找,直到找到null结束。比如,Object.prototype__proto__就是null

08c5d5d0-62da-40f9-9e2c-77831fa7488e.51324dd04eef

—-来自深入理解 JavaScript Prototype 污染攻击

更多描述

比如说此处的obj

利用.__proto__修改值后的test属性在当前对象的test属性下面(也就是在当前对象所绑定的prototype中),

所以优先读取当前对象下的test属性,即未被修改的值123

16720330214733

而ooo对象由于当前属性中没有test属性,只能从它绑定的prototype中找test对象(或下一级的prototype),

没找到返回undefined

18141222368283

参考链接

初探node.js相关之原型链污染

深入理解 JavaScript Prototype 污染攻击

【web安全】Nodejs原型链污染分析

最后

谢谢观看