最近看到一个关于Flask的CTF(RealWorld CTF 2018 web题bookhub)文章
其中的一个trick是装饰器的顺序问题,就想写篇博客回顾下装饰器~

首先强烈推荐很久之前看的一篇博文
(翻译)理解PYTHON中的装饰器
关于什么是装饰器看这篇文章就好了~
这里主要想写关于多个装饰器的执行流程

装饰顺序

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# import pdb;pdb.set_trace()

def functionOne(function_to_decorate):
print("functionOne初始化")

def wrapperOne():
pass
return wrapperOne

def functionTwo(function_to_decorate):
print("functionTwo初始化")

def wrapperTwo():
pass
return wrapperTwo

@functionOne
@functionTwo
def testFunction():
pass

# 输出结果
functionTwo初始化
functionOne初始化

从上面我们能得知:装饰顺序,就近装饰
然后我们利用下面的代码进行一步探究
如下我们得知:执行这段代码,相当于:
首先,将testFunction函数打包给wrapperTwo,由于没有调用,functionTwo整体返回了wrapperTwo,而没有执行
然后,functionOne将wrapperTwo作为参数,打包成wrapperOne

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# import pdb;pdb.set_trace()

def functionOne(function_to_decorate):
print("functionOne初始化")

def wrapperOne():
print("第一处"+function_to_decorate.__name__)
function_to_decorate()
return wrapperOne

def functionTwo(function_to_decorate):
print("functionTwo初始化")

def wrapperTwo():
print("第二处"+function_to_decorate.__name__)
function_to_decorate()
return wrapperTwo

@functionOne
@functionTwo
def testFunction():
print('index')

testFunction()

#输出结果
functionTwo初始化
functionOne初始化
第一处wrapperTwo
第二处testFunction
index

执行顺序

从上面的第二段代码我们已经能看出部分执行顺序了
就是它会优先执行我们打包好的wrapperOne,因为从起始的testFunction,wrapperTwo都已经打包在wrapperOne
可以说成执行顺序,就远执行
我们继续执行下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# import pdb;pdb.set_trace()

def functionOne(function_to_decorate):
print("functionOne初始化")

def wrapperOne():
print("第一处"+function_to_decorate.__name__)
function_to_decorate()
print("wrapperOne")
return wrapperOne

def functionTwo(function_to_decorate):
print("functionTwo初始化")

def wrapperTwo():
print("第二处"+function_to_decorate.__name__)
function_to_decorate()
print("wrapperTwo")
return wrapperTwo

@functionOne
@functionTwo
def testFunction():
print('index')

testFunction()

# 输出结果
functionTwo初始化
functionOne初始化
第一处wrapperTwo
第二处testFunction
index
wrapperTwo
wrapperOne

这个执行顺序可能也困扰了很多人,现在我们从输出结果看
对照代码,就很容易清楚了,执行到wrapperOne中的function_to_decorate时
其实相当于跳转到了函数wrapperTwo,然后执行wrapperTwo

Flask @login_require

从上面的几个例子我们应该大概了解了,多个装饰器进行装饰以及执行的顺序
我们来看这道CTF题目,我们首先需要知道的是Flask中路由就是一个装饰

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
from flask import Flask

app = Flask(__name__)
app.debug = True

# import pdb;pdb.set_trace()

# 为了更好的控制输出,自定义了loginRequire装饰器
def loginRequire(function_to_decorate):
print("loginRequire初始化")

def wrapperTwo():
print("loginRequire装饰成功")
print(function_to_decorate.__name__)
return function_to_decorate()
return wrapperTwo

@loginRequire
@app.route('/up')
def up():
return "装饰路由放在上面!"

@app.route('/down')
@loginRequire
def down():
return "装饰路由放在下面!"

if __name__ == '__main__':
app.run()

# 分别访问两个url输出结果
loginRequire初始化
loginRequire初始化
* Debugger is active!
* Debugger PIN: 244-957-971
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [24/Aug/2018 19:01:30] "GET /up HTTP/1.1" 200 -
loginRequire装饰成功
down
127.0.0.1 - - [24/Aug/2018 19:01:35] "GET /down HTTP/1.1" 200 -

从输出结果我们能清楚的看到up的装饰,并没有执行装饰器
如果按照我们上面的分析,无论在上面还是下面都会执行的啊??只是顺序不同罢了~
我们利用pdb来一步步调试查看哪里的问题,部分log如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
> c:\users\bayi\desktop\test\256.py(17)<module>()
-> @loginRequire
(Pdb) s
> c:\users\bayi\desktop\test\256.py(18)<module>()
-> @app.route('/up')
(Pdb) s
> c:\users\bayi\.virtualenvs\test-gq7eoxbq\lib\site-packages\flask\app.py(1252)route()-><function Fla...at 0x0376F978>
-> return decorator
(Pdb) s
--Call--
> c:\users\bayi\.virtualenvs\test-gq7eoxbq\lib\site-packages\flask\app.py(1248)decorator()
-> def decorator(f):
(Pdb) f
<function up at 0x0376F9C0>
(Pdb) s
> c:\users\bayi\.virtualenvs\test-gq7eoxbq\lib\site-packages\flask\app.py(1249)decorator()
-> endpoint = options.pop('endpoint', None)
(Pdb) s
> c:\users\bayi\.virtualenvs\test-gq7eoxbq\lib\site-packages\flask\app.py(1250)decorator()
-> self.add_url_rule(rule, endpoint, f, **options)
(Pdb) f
<function up at 0x0376F9C0>
#===================================================================================#
上方up 下方down
#===================================================================================#
> c:\users\bayi\desktop\test\256.py(22)<module>()
-> @app.route('/down')
(Pdb) s
> c:\users\bayi\desktop\test\256.py(23)<module>()
-> @loginRequire
(Pdb) s
--Call--
> c:\users\bayi\desktop\test\256.py(6)loginRequire()
-> def loginRequire(function_to_decorate):
(Pdb) s
> c:\users\bayi\desktop\test\256.py(7)loginRequire()
-> print("loginRequire初始化")
(Pdb) s
loginRequire初始化
> c:\users\bayi\desktop\test\256.py(9)loginRequire()
-> def wrapperTwo():
(Pdb) s
> c:\users\bayi\desktop\test\256.py(13)loginRequire()
-> return wrapperTwo
(Pdb) s
--Return--
> c:\users\bayi\desktop\test\256.py(13)loginRequire()-><function log...at 0x0071C468>
-> return wrapperTwo
(Pdb) s
--Call--
> c:\users\bayi\.virtualenvs\test-gq7eoxbq\lib\site-packages\flask\app.py(1248)decorator()
-> def decorator(f):
(Pdb) f
<function loginRequire.<locals>.wrapperTwo at 0x0071C468>

从上面的执行流程,打印出不断出现的f,我们能看出,两个顺序的f值不同
在up中,f=up()
在down中,f=wrapperTwo()
这点符合预期,装饰位置不同,然而在执行Flask源码 add_url_rule时
如上面log所示,直接添加了f的值

1
2
3
4
5
6
7
> c:\users\bayi\.virtualenvs\test-gq7eoxbq\lib\site-packages\flask\app.py(1249)decorator()
-> endpoint = options.pop('endpoint', None)
(Pdb) s
> c:\users\bayi\.virtualenvs\test-gq7eoxbq\lib\site-packages\flask\app.py(1250)decorator()
-> self.add_url_rule(rule, endpoint, f, **options)
(Pdb) f
<function up at 0x0376F9C0>

也就是添加路由的时候会选择丢失外层的路由,只装饰route下方的函数
在add_url_rule中,有这段注释:

1
2
3
4
5
6
7
8
9
10
11
Basically this example::

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

Is equivalent to the following::

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

博客地址