flask에서 CSRF token을 적용할 때에 거의 대부분 사용하는 것으로 보이는 flask-wft의 CSRFProtect를 분석해보았다.
ssti 등으로 flask 전역 변수를 변경할 수 있다면 CSRF를 우회할 수 있을 것으로 보인다.
ssti가 될 때 CSRF를 굳이 우회할 필요가 있을까..?
https://github.com/wtforms/flask-wtf/blob/main/src/flask_wtf/csrf.py
여기에서 CSRFProtect
클래스를 따라가면서 동작 원리를 살펴보자.
아래와 같이 기본 변수들을 설정한다. csrf_protect()
를 request가 도착할 때마다 수행하는데, WTF_CSRF_ENABLED
or WTF_CSRF_CHECK_DEFAULT
가 False면 return 한다(검사를 수행하지 않는다)
def init_app(self, app):
app.extensions["csrf"] = self
app.config.setdefault("WTF_CSRF_ENABLED", True)
app.config.setdefault("WTF_CSRF_CHECK_DEFAULT", True)
app.config["WTF_CSRF_METHODS"] = set(
app.config.get("WTF_CSRF_METHODS", ["POST", "PUT", "PATCH", "DELETE"])
)
app.config.setdefault("WTF_CSRF_FIELD_NAME", "csrf_token")
app.config.setdefault("WTF_CSRF_HEADERS", ["X-CSRFToken", "X-CSRF-Token"])
app.config.setdefault("WTF_CSRF_TIME_LIMIT", 3600)
app.config.setdefault("WTF_CSRF_SSL_STRICT", True)
app.jinja_env.globals["csrf_token"] = generate_csrf
app.context_processor(lambda: {"csrf_token": generate_csrf})
@app.before_request
def csrf_protect():
if not app.config["WTF_CSRF_ENABLED"]:
return
if not app.config["WTF_CSRF_CHECK_DEFAULT"]:
return
if request.method not in app.config["WTF_CSRF_METHODS"]:
return
if not request.endpoint:
return
if request.blueprint in self._exempt_blueprints:
return
view = app.view_functions.get(request.endpoint)
dest = f"{view.__module__}.{view.__name__}"
if dest in self._exempt_views:
return
self.protect()
이어서 protect()
를 보자.
method가 위에서 설정한 method(["POST", "PUT", "PATCH", "DELETE"]
)에 포함되지 않으면 검사 수행하지 않는다.
try 구문 아래에서 CSRF 검사를 수행하고, 아래에 추가적으로 referrer 검사와 SOP 검사도 수행한다.
def protect(self):
if request.method not in current_app.config["WTF_CSRF_METHODS"]:
return
try:
validate_csrf(self._get_csrf_token())
except ValidationError as e:
logger.info(e.args[0])
self._error_response(e.args[0])
if request.is_secure and current_app.config["WTF_CSRF_SSL_STRICT"]:
if not request.referrer:
self._error_response("The referrer header is missing.")
good_referrer = f"https://{request.host}/"
if not same_origin(request.referrer, good_referrer):
self._error_response("The referrer does not match the host.")
g.csrf_valid = True # mark this request as CSRF valid
결론적으로 WTF_CSRF_ENABLED
or WTF_CSRF_CHECK_DEFAULT
를 False
로 만들거나, WTF_CSRF_METHODS
를 []
로 만들면 모든 검사를 하지 않도록 변경할 수 있다.
근데 다시 여기서 드는 의문은 ssti가 될 때 CSRF를 굳이 우회할 필요가 있을까..?