复现原型链污染

news2024/12/26 23:25:40

目录

原型链污染是什么

例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:

image.png

原因也显而易见:因为前面我们修改了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,}

该对象有两个属性:prop1prop2。但这些并不是我们可以访问的唯一属性。例如调用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 } }

递归合并的基本流程是:

  1. 遍历 obj2 的所有属性并检查它们是否存在于obj1.
  2. 如果存在属性,则对该属性执行合并操作。
  3. 如果属性不存在,则将其从 复制obj2obj1

在现实世界中,如果用户对要合并的对象有任何控制权,那么通常其中一个对象来自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}}')

该功能或多或少类似于以下步骤:

  1. 遍历obj2. 唯一的属性是__proto__
  2. 检查是否obj1.__proto__存在。确实如此。
  3. 遍历obj2.__proto__. 唯一的属性是x
  4. 赋值:obj1.__proto__.x = obj2.__proto__.x。因为obj1.__proto__指向Object.prototype,则原型被污染。

在许多流行的 JS 库中都发现了这种类型的错误,包括lodashjQuery

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.coloradmin.cn/o/832891.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Linux】揭秘:提升dd命令效率的秘密武器!

红帽RHCE试听课程&#xff1a;如何快速实现对服务器密码爆破&#xff1f;https://mp.weixin.qq.com/s/JUpf8G86jvnNwvKLUfWcLQ 红帽RHCE试听课程&#xff1a;linux系统下&#xff0c;用这个命令可以提高60%的工作效率https://mp.weixin.qq.com/s/pZVjMI1PLJzrA8hoPzkgMA 大家好…

LNMP及论坛搭建(第一个访问,单节点)

LNMP&#xff1a;目前成熟的一个企业网站的应用模式之一&#xff0c;指的是一套协同工作的系统和相关软件 能够提供静态页面服务&#xff0c;也可以提供动态web服务&#xff0c;LNMP是缩写 L&#xff1a;指的是Linux操作系统。 N&#xff1a;指的是nginx&#xff0c;nginx提…

MS17-010永恒之蓝漏洞复现

一&#xff0c;认识永恒之蓝 1&#xff0c;简介 永恒之蓝&#xff0c;代号MS17-010。爆发于2017年&#xff0c;其通过控制用户主机&#xff0c;利用SMB协议的漏洞来获取系统的最高权限&#xff0c;进而可以窃取信息&#xff0c;偷窥隐私&#xff0c;甚至使系统瘫痪。曾爆发覆盖…

性能测试遇到问题怎么办?学会分析流程就不怕!

一、内存溢出 1、堆内存溢出 现象&#xff1a; &#xff08;1&#xff09;压测执行一段时间后&#xff0c;系统处理能力下降。这时用JConsole、JVisualVM等工具连上服务器查看GC情况&#xff0c;每次GC回收都不彻底并且可用堆内存越来越少。 &#xff08;2&#xff09;压测持续…

数据库数据恢复-Oracle数据库文件出现坏块的数据恢复案例

Oracle数据库故障&初检&分析&#xff1a; 打开Oracle数据库时报错&#xff0c;报错信息&#xff1a;“system01.dbf需要更多的恢复来保持一致性&#xff0c;数据库无法打开”。用户急需恢复zxfg用户下的数据。 出现上述报错的可能原因包括&#xff1a;控制文件损坏、数…

【零基础学Rust | 基础系列 | 数据结构】元组,数组,向量,字符串,结构体

文章标题 简介&#xff1a;一&#xff0c;元组&#xff1a;1&#xff0c;定义元组&#xff1a;2&#xff0c;访问元组元素&#xff1a;3&#xff0c;元组解构&#xff1a;4&#xff0c;元组在函数中的应用&#xff1a; 二&#xff0c;数组&#xff1a;1&#xff0c;数组的声明和…

核心交换机新增了一个网段,现在下面PC可以获取地址访问内网 ,访问外网说DNS有问题不通

环境: SANGFOR AF 8.0.75 SANGFOR AC 13.0.47 H3C S6520-26Q-SI 问题描述: 1.在核心交换机上新规划了一个网段192.168.200.0/24,现在下面PC可以正常获取IP地址和DNS,正常访问内网服务和其它地址段IP ,访问外网说DNS有问题不通打不开网页 2.DNS解析失败,ping dns服务…

C++初阶 - 7.STL简介

目录 1.什么是STL 2.STL的版本 3.STL的六大组件 4.STL的重要性 5.如何学习STL 6.STL的缺陷 1.什么是STL STL(standard template libiary-标准模板库)&#xff1a;是C标准库的重要组成部分&#xff0c;不仅是一个可复用的组件库&#xff0c;而且是一个包罗数据结构与算法…

Django实现音乐网站 ⑷

使用Python Django框架制作一个音乐网站&#xff0c;在系列文章3的基础上继续开发&#xff0c; 本篇主要是后台歌曲类型表、歌单表模块功能开发。 目录 表结构设计 歌曲类型表结构 歌单表结构 创建表模型 创建表 后台注册表模型 引入表模型 后台自定义 总结 表结构设计…

在线考试系统ssm学生线上答疑问答试卷管理java jsp源代码mysql

本项目为前几天收费帮学妹做的一个项目&#xff0c;Java EE JSP项目&#xff0c;在工作环境中基本使用不到&#xff0c;但是很多学校把这个当作编程入门的项目来做&#xff0c;故分享出本项目供初学者参考。 一、项目描述 在线考试系统ssm 系统有1权限&#xff1a;管理员 二…

Docker Compose构建lnmp

目录 Compose的优点 编排和部署 Compose原理 Compose应用案例 安装docker-ce 阿里云镜像加速器 安装docker-compose docker-compose用法 Yaml简介 验证LNMP环境 Compose的优点 先来了解一下我们平时是怎么样使用docker的&#xff1f;把它进行拆分一下&#xff1a; 1…

低代码已经发展到什么水平了?

在数字化转型的浪潮下&#xff0c;企业和组织迫切需要更快速、高效的应用开发方式来满足日益复杂的业务需求。而低代码开发作为一种创新的开发方式&#xff0c;正在引领着应用开发的新潮流。低代码开发允许开发者以可视化的方式快速构建应用&#xff0c;减少了繁琐的代码编写&a…

微服务——elasticsearch

初识ES——什么是elasticsearch elasticsearch的发展 初识ES——正向索引和倒排索引 初识ES——es与mysql的概念对比 类比到mysql中是表结构约束 概念对比 初始ES——安装es和kibana 1.部署单点es 1.1创建网络 要安装es容器和kibana容器并让他们之间相连&#xff0c;这里…

编辑接口和新增接口的分别调用

在后台管理系统中,有时候会碰到新增接口和编辑接口共用一个弹窗的时候. 一.场景 在点击新增或者编辑的时候都会使用这个窗口,新增直接调用接口进行增加即可&#xff0c;编辑则是打开这个窗口显示当前行的数据,然后调用编辑接口。 二.处理方法 在默认的情况下,这个窗口用来处理…

AOP的实战(统一功能处理模块)

一、用户登录权限效验 用户登录权限的发展从之前每个方法中自己验证用户登录权限&#xff0c;到现在统一的用户登录验证处理&#xff0c;它是一个逐渐完善和逐渐优化的过程。 1.1 最初用户登录验证 我们先来回顾一下最初用户登录验证的实现方法&#xff1a; RestController…

Android入门教程||Android 架构||Android 应用程序组件

Android 架构 Android 操作系统是一个软件组件的栈&#xff0c;在架构图中它大致可以分为五个部分和四个主要层。 Linux内核 在所有层的最底下是 Linux - 包括大约115个补丁的 Linux 3.6。它提供了基本的系统功能&#xff0c;比如进程管理&#xff0c;内存管理&#xff0c;设…

Redis的缓存穿透、缓存击穿和缓存雪崩

目录 一、解释说明 二、缓存穿透 1. 什么是缓存穿透&#xff1f; 2. 常见的两种解决方案 &#xff08;1&#xff09;缓存空对象 &#xff08;2&#xff09;布隆过滤 3. 编码解决商品查询的缓存穿透问题 三、缓存雪崩 1. 什么是缓存雪崩&#xff1f; 2. 缓存雪崩解决方…

蓝桥杯上岸每日N题 第七期(小猫爬山)!!!

蓝桥杯上岸每日N题 第七期(小猫爬山)&#xff01;&#xff01;&#xff01; 同步收录 &#x1f447; 蓝桥杯上岸必背&#xff01;&#xff01;&#xff01;(第四期DFS) 大家好 我是寸铁&#x1f4aa; 冲刺蓝桥杯省一模板大全来啦 &#x1f525; 蓝桥杯4月8号就要开始了 &a…

JS中的原型和原型链

原型 1.普通对象 每个对象都有一个__proto__属性,该属性指向对象的原型属性 const obj { name: 张三 }console.log(obj, obj.__proto__);console.log(Object, Object.prototype);我们可以得出&#xff1a;obj.proto Object.prototype console.log(obj.__proto__ Object.pr…

【黑马程序员前端】JavaScript入门到精通(2)--20230801

B站链接 【黑马程序员前端】JavaScript入门到精通(1)–20230801 【黑马程序员前端】JavaScript入门到精通(2)–20230801 2.web APIs资料(续前) web APIs第六天 01-正则表达式的使用 <!DOCTYPE html> <html lang"en"><head><meta charset&quo…