实现上传头像功能的时候遇到了 403,隐约感觉到可能是 csrf 的问题,但是嫌麻烦一直懒得翻源码里的校验逻辑,反而让自己怀疑是 mime type 有限制,走了弯路。
机缘巧合发现了 csrf 的校验位置,总算是通了
定位代码位置
在 CTFd/__init__.py
文件下,观察如下代码片段
init_request_processors(app)
init_template_filters(app)
init_template_globals(app)
# Importing here allows tests to use sensible names (e.g. api instead of api_bp)
from CTFd.admin import admin
from CTFd.api import api
from CTFd.auth import auth
from CTFd.challenges import challenges
from CTFd.errors import render_error
from CTFd.events import events
from CTFd.matches import matches
from CTFd.scoreboard import scoreboard
from CTFd.share import social
from CTFd.teams import teams
from CTFd.users import users
from CTFd.views import views
from CTFd.writeup import writeup
app.register_blueprint(views)
app.register_blueprint(teams)
app.register_blueprint(users)
app.register_blueprint(matches)
app.register_blueprint(challenges)
大概能猜出来 csrf 校验应该是在注册蓝图之前初始化的,也就是 init_request_processors
。进去后发现一堆 @app.before_request
的装饰器,感觉对了。往下翻,查询 403 找到
@app.before_request
def csrf():
try:
func = app.view_functions[request.endpoint]
except KeyError:
abort(404)
if hasattr(func, "_bypass_csrf"):
return
if request.headers.get("Authorization"):
return
if not session.get("nonce"):
session["nonce"] = generate_nonce()
if request.method not in ("GET", "HEAD", "OPTIONS", "TRACE"):
if request.content_type == "application/json":
if session["nonce"] != request.headers.get("CSRF-Token"):
abort(403)
if request.content_type != "application/json":
if session["nonce"] != request.form.get("nonce"):
abort(403)
bingo。原来只有 json 的 csrf token 是放在 header 里的,难怪 403 了。