复现原型链污染

目录

原型链污染是什么

例1

复现

例2

复现

原型链污染是什么
第一章中说到,foo.__proto__指向的是Foo类的prototype。那么,如果我们修改了foo.__proto__中的值,是不是就可以修改Foo类呢?

做个简单的实验:

// foo是一个简单的JavaScript对象

let foo = {bar: 1}

// foo.bar 此时为1

console.log(foo.bar)

// 修改foo的原型(即Object)

foo.__proto__.bar = 2

// 由于查找顺序的原因,foo.bar仍然是1

console.log(foo.bar)

// 此时再用Object创建一个空的zoo对象

let zoo = {}

// 查看zoo.bar

console.log(zoo.bar)

最后,虽然zoo是一个空对象{},但zoo.bar的结果居然是2:

 

原因也显而易见:因为前面我们修改了foo的原型foo.__proto__.bar = 2,而foo是一个Object类的实例,所以实际上是修改了Object这个类,给这个类增加了一个属性bar,值为2。

后来,我们又用Object类创建了一个zoo对象let zoo = {},zoo对象自然也有一个bar属性了。

那么,在一个应用中,如果攻击者控制并修改了一个对象的原型,那么将可以影响所有和这个对象来自同一个类、父祖类的对象。这种攻击方式就是原型链污染。

原型污染是一个安全漏洞,非常特定于 JavaScript。它源于 JavaScript 继承模型,称为基于原型的继承。与 C++ 或 Java 不同,在 JavaScript 中,您不需要定义类来创建对象。您只需要使用大括号符号并定义属性,例如:

  constobj={ prop1: 111, prop2: 222,}

该对象有两个属性:prop1和prop2。但这些并不是我们可以访问的唯一属性。例如调用obj.toString()将返回"[object Object]"。toString(连同其他一些默认成员)来自原型。JavaScript 中的每个对象都有一个原型(也可以是null)。如果我们不指定它,默认情况下对象的原型是Object.prototype.

在 DevTools 中,我们可以轻松检查以下属性的列表Object.prototype:


__proto__我们还可以通过检查其成员或调用来找出给定对象的原型是什么对象Object.getPrototypeOf: 

__proto__同样,我们可以使用or设置对象的原型Object.setPrototypeOf:

 

简而言之,当我们尝试访问对象的属性时,JS 引擎首先检查对象本身是否包含该属性。如果是,则将其退回。否则,JS 会检查原型是否具有该属性。如果没有,JS 会检查原型的原型……以此类推,直到原型为null. 它被称为原型链。

JS 遍历原型链的事实有一个重要的影响:如果我们能以某种方式污染 Object.prototype(即用新属性对其进行扩展),那么所有 JS 对象都会具有这些属性。

考虑以下示例:

const user = { userid: 123 };if (user.admin) {  console.log('You are an admin');}

乍一看,似乎不可能使 if 条件为真,因为userobject 没有名为 的属性admin。但是,如果我们污染了Object.prototype和定义名为 的属性admin,那么console.log将执行!

Object.prototype.admin = true;const user = { userid: 123 };if (user.admin) {  console.log('You are an admin'); // this will execute}

这证明原型污染可能会对应用程序的安全性产生巨大影响,因为我们可以定义会改变其逻辑的属性。但是,只有少数已知的滥用该漏洞的案例

在进入本文的重点之前,需要再讨论一个话题:原型污染是如何发生的?

此漏洞的入口点通常是合并操作(即将一个对象的所有属性复制到另一个对象)。例如:

const obj1 = { a: 1, b: 2 };

const obj2 = { c: 3, d: 4 };

merge(obj1, obj2)

// returns { a: 1, b: 2, c: 3, d: 4}

有时操作会递归地工作,例如:

const obj1 = { a: {  b: 1,  c: 2, }};

const obj2 = { a: {  d: 3 }};

recursiveMerge(obj1, obj2);

// returns { a: { b: 1, c: 2, d: 3 } }

递归合并的基本流程是:

遍历 obj2 的所有属性并检查它们是否存在于obj1.
如果存在属性,则对该属性执行合并操作。
如果属性不存在,则将其从 复制obj2到obj1。
在现实世界中,如果用户对要合并的对象有任何控制权,那么通常其中一个对象来自JSON.parse. AndJSON.parse有点特别,因为它被视为__proto__“普通”属性,即没有作为原型访问器的特殊含义。考虑以下示例:

在示例中,obj1使用 JS 的大括号符号obj2创建,而使用JSON.parse. 这两个对象都只定义了一个属性,称为__proto__. 但是,访问obj1.__proto__返回Object.prototype(__proto__返回原型的特殊属性也是如此),同时obj2.__proto__包含 JSON 中给出的值,即:123. 这证明了__proto__属性的处理方式与JSON.parse普通 JavaScript 不同。

所以现在想象一个recursiveMerge合并两个对象的函数:

obj1={}
obj2=JSON.parse('{"__proto__":{"x":1}}')
该功能或多或少类似于以下步骤:

遍历obj2. 唯一的属性是__proto__。
检查是否obj1.__proto__存在。确实如此。
遍历obj2.__proto__. 唯一的属性是x。
赋值:obj1.__proto__.x = obj2.__proto__.x。因为obj1.__proto__指向Object.prototype,则原型被污染。
在许多流行的 JS 库中都发现了这种类型的错误,包括lodash或jQuery。

例1
在www合适目录下创建js文件,然后再cmd运行node pp.js,如果没有相应模块则用npm install  moudle即可

const express = require('express')
 
var hbs = require('hbs');
 
var bodyParser = require('body-parser');
 
const md5 = require('md5');
 
var morganBody = require('morgan-body');
 
const app = express();
 
var user = []; //empty for now
 
var matrix = [];
 
for (var i = 0; i < 3; i++){
 
    matrix[i] = [null , null, null];
 
}
 
function draw(mat) {
 
    var count = 0;
 
    for (var i = 0; i < 3; i++){
 
        for (var j = 0; j < 3; j++){
 
            if (matrix[i][j] !== null){
 
                count += 1;
 
            }
 
        }
 
    }
 
    return count === 9;
 
}
 
app.use(express.static('public'));
 
app.use(bodyParser.json());
 
app.set('view engine', 'html');
 
morganBody(app);
 
app.engine('html', require('hbs').__express);
 
app.get('/', (req, res) => {
 
    for (var i = 0; i < 3; i++){
 
        matrix[i] = [null , null, null];
 
    }
 
    res.render('index');
 
})
 
 
 
app.get('/admin', (req, res) => {
 
    /*this is under development I guess ??*/
 
    console.log(user.admintoken);
 
    if(user.admintoken && req.query.querytoken && md5(user.admintoken) === req.query.querytoken){
 
        res.send('Hey admin your flag is <b>flag{prototype_pollution_is_very_dangerous}</b>');
 
    }
 
    else {
 
        res.status(403).send('Forbidden');
 
    }    
 
}
 
)
 
 
 
app.post('/api', (req, res) => {
 
    var client = req.body;
 
    var winner = null;
 
    if (client.row > 3 || client.col > 3){
 
        client.row %= 3;
 
        client.col %= 3;
 
    }
 
    matrix[client.row][client.col] = client.data;
 
    for(var i = 0; i < 3; i++){
 
        if (matrix[i][0] === matrix[i][1] && matrix[i][1] === matrix[i][2] ){
 
            if (matrix[i][0] === 'X') {
 
                winner = 1;
 
            }
 
            else if(matrix[i][0] === 'O') {
 
                winner = 2;
 
            }
 
        }
 
        if (matrix[0][i] === matrix[1][i] && matrix[1][i] === matrix[2][i]){
 
            if (matrix[0][i] === 'X') {
 
                winner = 1;
 
            }
 
            else if(matrix[0][i] === 'O') {
 
                winner = 2;
 
            }
 
        }
 
    }
 
    if (matrix[0][0] === matrix[1][1] && matrix[1][1] === matrix[2][2] && matrix[0][0] === 'X'){
 
        winner = 1;
 
    }
 
    if (matrix[0][0] === matrix[1][1] && matrix[1][1] === matrix[2][2] && matrix[0][0] === 'O'){
 
        winner = 2;
 
    }
 
    if (matrix[0][2] === matrix[1][1] && matrix[1][1] === matrix[2][0] && matrix[2][0] === 'X'){
 
        winner = 1;
 
    }
 
    if (matrix[0][2] === matrix[1][1] && matrix[1][1] === matrix[2][0] && matrix[2][0] === 'O'){
 
        winner = 2;
 
    }
 
    if (draw(matrix) && winner === null){
 
        res.send(JSON.stringify({winner: 0}))
 
    }
 
    else if (winner !== null) {
 
        res.send(JSON.stringify({winner: winner}))
 
    }
 
    else {
 
        res.send(JSON.stringify({winner: -1}))
 
    }
 
})
 
app.listen(3000, () => {
 
    console.log('app listening on port 3000!')
 
})

 复现

取flag的条件是 传入的querytoken要和user数组本身的admintoken的MD5值相等,且二者都要存在。由代码可知,全文没有对user.admintokn 进行赋值,所以理论上这个值时不存在的,但是下面有一句赋值语句: matrix[client.row][client.col] = client.data;

可以进行matrix原型污染,最后使uesr拿到admintoken值

例2

'use strict';
 
const express = require('express');
 
const bodyParser = require('body-parser')
 
const cookieParser = require('cookie-parser');
 
const path = require('path');
 
const isObject = obj => obj && obj.constructor && obj.constructor === Object;
 
function merge(a, b) {
 
    for (var attr in b) {
 
        if (isObject(a[attr]) && isObject(b[attr])) {
 
            merge(a[attr], b[attr]);
 
        } else {
 
            a[attr] = b[attr];
 
        }
 
    }
 
    return a
 
}
 
function clone(a) {
 
    return merge({}, a);
 
}
 
// Constants
 
const PORT = 8080;
 
const HOST = '0.0.0.0';
 
const admin = {};
 
// App
 
const app = express();
 
app.use(bodyParser.json())
 
app.use(cookieParser());
 
app.use('/', express.static(path.join(__dirname, 'views')));
 
app.post('/signup', (req, res) => {
 
    var body = JSON.parse(JSON.stringify(req.body));  {"__proto__": {"admin":1}}
 
    var copybody = clone(body)
 
    if (copybody.name) {
 
        res.cookie('name', copybody.name).json({
 
            "done": "cookie set"
 
        });
 
    } else {
 
        res.json({
 
            "error": "cookie not set"
 
        })
 
    }
 
});
 
app.get('/getFlag', (req, res) => {
 
    var аdmin = JSON.parse(JSON.stringify(req.cookies))
 
    if (admin.аdmin == 1) {
 
        res.send("hackim19{}");
 
    } else {
 
        res.send("You are not authorized");
 
    }
 
});
 
app.listen(PORT, HOST);
 
console.log(`Running on http://${HOST}:${PORT}`);
 

复现

从源代码入手:

  Merge()函数是以一种可能发生原型污染的方式编写的。这是问题分析的关键。

    易受攻击的函数是在通过clone(body)访问/signup时被调用的,因此我们可以在注册时发送JSON有效负载,这样就可以添加admin属性并立即调用/getFlag来获取Flag。

   如前所述,我们可以使用__proto__(points to constructor.prototype)来创建值为1的admin属性。

执行相同操作的最简单的payload:

{"__proto__": {"admin": 1}}
 

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/60598.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Go语言进阶

个人笔记&#xff0c;大量摘自Go语言高级编程、Go|Dave Cheney等 更新 go get -u all 在非go目录运行go install golang.org/x/tools/goplslatest更新go tools&#xff1a;在go目录运行go get -u golang.org/x/tools/...&#xff0c;会更新bin目录下的应用&#xff1b; 运行…

APP专项测试知识点

APP的专项测试 测试要点&#xff1a; 功能测试、兼容性测试、安装、卸载、升级测试、交叉事件测试、PUSH测试、性能测试-使用solopi监控-仅适用于安卓手机&#xff08;CPU、内存、流量测试、电量测试、流畅度测试、启动测试&#xff09;、用户体验测试、稳定性测试 &#xf…

【Linux 网络】NAT技术——缓解IPv4地址不足

NAT技术 NAT 技术背景NAT IP转换过程NAPTNAT 技术的缺陷 NAT&#xff08;Network Address Translation&#xff0c;网络地址转换&#xff09;技术&#xff0c;是解决IP地址不足的主要手段&#xff0c;并且能够有效地避免来自网络外部的攻击&#xff0c;隐藏并保护网络内部的计算…

设计模式——单例模式(懒汉和饿汉)

单例模式 一、概念 单例模式是一种对象创建型模式&#xff0c;使用单例模式&#xff0c;可以保证为一个类只生成唯一的实例对象。也就是说&#xff0c;在整个程序空间中&#xff0c;该类只存在一个实例对象。一个类只能有一个实例在生活中是很常见的&#xff0c;比如打印机程…

Python读取excel数据并创建文件目录树-全解析过程及逻辑

需求描述&#xff1a; 需要将以下excel内的结构解析&#xff0c;并创建对应的文件目录 实现思路&#xff1a; 实现思路是通过解析Excel文件中的目录结构&#xff0c;并根据目录结构创建对应的文件夹。 具体的实现步骤如下&#xff1a; 1. 加载指定的Excel文件&#xff0c…

无人驾驶实战-第十二课(强化学习自动驾驶系统)(完)

在七月算法上报了《无人驾驶实战》课程&#xff0c;老师讲的真好。好记性不如烂笔头&#xff0c;记录一下学习内容。 课程入口&#xff0c;感兴趣的也可以跟着学一下。 ————————————————————————————————————————— 强化学习&#xff…

Vite 创建 Vue项目之后,eslint 错误提示的处理

使用 npm create vuelatest创建 vue 项目&#xff08;TS&#xff09;之后&#xff0c;出现了一些 eslint 错误提示&#xff0c;显然&#xff0c;不是代码真实的错误&#xff0c;而是提示搞错了。 vuejs/create-vue: &#x1f6e0;️ The recommended way to start a Vite-pow…

Spring Boot集成EasyExcel实现excel导入导出操作

文章目录 Spring Boot集成EasyExcel实现excel导入导出操作0 简要说明简单使用读操作excel源文件实体类监听器业务代码 写操作*实体类*excel示例业务代码根据参数指定列导出指定哪几列导出复杂头导出 关于数值型&#xff0c;日期型&#xff0c;浮点型数据解决方案实体类接收字符…

【计算机视觉】关于图像处理的一些基本操作

目录 图像平滑滤波处理均值滤波计算过程python实现 高斯滤波计算过程python实现 中值滤波计算过程python实现 图像的边缘检测Robert算子计算过程python实现 图像处理腐蚀算子计算过程python实现 Hog&#xff08;梯度方向直方图&#xff09;特征计算流程&#xff1a;Hog的特征维…

ArcGIS、ENVI、InVEST、FRAGSTATS技术教程

专题一 空间数据获取与制图 1.1 软件安装与应用讲解 1.2 空间数据介绍 1.3海量空间数据下载 1.4 ArcGIS软件快速入门 1.5 Geodatabase地理数据库 专题二 ArcGIS专题地图制作 2.1专题地图制作规范 2.2 空间数据的准备与处理 2.3 空间数据可视化&#xff1a;地图符号与注…

计算机基础知识一

1、计算机系统组成 1.1 硬件 CPU&#xff1a;中央处理器、计算机核心部件、负责计算任务 内存&#xff1a;记忆功能、存储二进制数&#xff0c;内存是一个字节一个地址。 内存大小换算&#xff1a; 8 bits 1 Byte 1024 Bytes Bytes 1 KB &#xff0c; 1024 KB KB 1 …

【安装部署】Mysql下载及其安装的详细步骤

1.下载压缩包 官网地址&#xff1a;www.mysql.com 2.环境配置 1.先解压压缩包 2.配置环境变量 添加环境变量&#xff1a;我的电脑--->属性-->高级-->环境变量-->系统变量-->path 3.在mysql安装目录下新建my.ini文件并&#xff0c;编辑my.ini文件 编辑内容如…