python Flask 装饰器顺序问题解决

yipeiwu_com7年前Python基础

上周 RealWorld CTF 2018 web 题 bookhub 有个未授权访问的漏洞,比较有意思,赛后看了一下公开的 WriteUp,大家也都没写清楚,所以就有了这篇博文。

前言

这个题是用 flask 框架写的,在 www/bookhub/views/user.py 中, refresh_session 方法存在未授权访问漏洞,代码是这样写的:

@login_required
@user_blueprint.route('/admin/system/refresh_session/', methods=['POST'])
def refresh_session():
 pass # 这里省略内容

注意看 @login_required 这个装饰器写在了 route 装饰器上面了,导致了 login_required 未调用。那么,为什么会这样子呢?

官方文档

Flask 官方文档中关于Login Required Decorator说明 这一节里面有一行说明:

To use the decorator, apply it as innermost decorator to a view function. When applying further decorators, always remember that the route() decorator is the outermost.

大概意思就是,必须保证 route 装饰器在最顶层

那么为什么要这样提示呢?

Python 装饰器顺序说明

本节内容可直接参考: Python 装饰器执行顺序迷思

总结一下就是,装饰的顺序按靠近函数顺序执行,从内到外装饰,调用时由外而内,执行顺序和装饰顺序相反。

回过头来看 Flask

Flask 框架中, route 装饰器是这么写的:

def route(self, rule, **options):
 """Like :meth:`Flask.route` but for a blueprint. The endpoint for the
 :func:`url_for` function is prefixed with the name of the blueprint.
 """
 def decorator(f):
  endpoint = options.pop("endpoint", f.__name__)
  self.add_url_rule(rule, endpoint, f, **options)
  return f
 return decorator

route 调用了 add_url_rule , 对传入的 f 添加一条 URL 规则。

所以,按照 python 装饰器顺序:

  1. 如果 @app.route 在内层,那么就会把最原始的 view 函数传给 add_url_rule , Flask 框架就会添加一条 URL 规则,指向最原始的 view 函数。
  2. 如果 @app.route 在外层,那么就会把已经被 login_required 装饰过的 view 函数传给 add_url_rule , Flask 框架就会添加一条 URL 规则,指向已经装饰过的 view 函数。

下面是两个例子,来说明:

正确写法

@user_blueprint.route('/admin/refresh_session/', methods=['POST'])
@login_required
def refresh_session():
 pass

这段代码相当于:

# 这里没有装饰器
def refresh_session():
 pass

login_wrapped = login_required(refresh_session) # login 装饰器
both_wrapped = app.route('/admin/refresh_session/')(login_wrapped) # route 装饰器

/admin/refresh_session/ 这条路由指向的实际是 login_wrapped ,这样就会经过 login 检查

错误写法

@login_required
@user_blueprint.route('/admin/refresh_session/', methods=['POST'])
def refresh_session():
 pass

这段代码相当于:

# 这里没有装饰器
def refresh_session():
 pass

route_wrapped = app.route('/admin/refresh_session/')(refresh_session) # route 装饰器
login_wrapped = login_required(route_wrapped)  # login 装饰器

/admin/refresh_session/ 这条路由指向的实际是 refresh_session , 而 login_wrapped 并没有与路由挂勾,所以不会被调用

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持【听图阁-专注于Python设计】。

相关文章

浅析PEP570新语法: 只接受位置参数

最近 PEP 570被接受了,其实要加的这个Positional-Only Parameters原来在内置的C函数上有很多都用到了: In : __builtin__.eval Ou...

python性能测量工具cProfile使用解析

背景: Python是一种解释性的语言,执行速度相比C、C++等语言十分缓慢;因此我们需要在其它地方上下功夫来提高代码的执行速度。 首先需要对代码进行分析,这个时候则需要用一些工具。...

Python写的一个简单监控系统

Python写的一个简单监控系统

市面上有很多开源的监控系统:Cacti、nagios、zabbix。感觉都不符合我的需求,为什么不自己做一个呢 用Python两个小时徒手撸了一个简易的监控系统,给大家分享一下,希望能对...

python批量从es取数据的方法(文档数超过10000)

如下所示: """ 提取文档数超过10000的数据 按照某个字段的值具有唯一性进行升序, 按照@timestamp进行降序, 第一次查询,先将10000条数据取出, 取出最后一个时间...

python3 写一个WAV音频文件播放器的代码

环境:ubuntu 16.04 python3.5 pycharm 包 : wave pyaudio sys 上代码:AudioPlayer.py # coding:utf-8 #...