Skip to content

Commit 44d7895

Browse files
fix(asm): fix flask block on path parameters (#5640)
Fix path parameters address handling in Flask framework. - move the call to the waf from the wsgi layer to the wrapper layer for views to handle the call just before the request is handled by the client code but just after the path parameters were computed - instead of blocking before the request, if a block is occuring, the call to the view is replaced by a function stored in the asm context - create a specific wrap_function for views to avoid polluting other functions with specific instrumentation of IAST and ASM ## Checklist - [x] Change(s) are motivated and described in the PR description. - [x] Testing strategy is described if automated tests are not included in the PR. - [x] Risk is outlined (performance impact, potential for breakage, maintainability, etc). - [x] Change is maintainable (easy to change, telemetry, documentation). - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/contributing.html#Release-Note-Guidelines) are followed. - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)). - [x] PR description includes explicit acknowledgement/acceptance of the performance implications of this PR as reported in the benchmarks PR comment. ## Reviewer Checklist - [ ] Title is accurate. - [ ] No unnecessary changes are introduced. - [ ] Description motivates each change. - [ ] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes unless absolutely necessary. - [ ] Testing strategy adequately addresses listed risk(s). - [ ] Change is maintainable (easy to change, telemetry, documentation). - [ ] Release note makes sense to a user of the library. - [ ] Reviewer has explicitly acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment.
1 parent c5f49c9 commit 44d7895

File tree

4 files changed

+55
-15
lines changed

4 files changed

+55
-15
lines changed

ddtrace/contrib/flask/patch.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
from .helpers import with_instance_pin
5050
from .wrappers import wrap_function
5151
from .wrappers import wrap_signal
52+
from .wrappers import wrap_view
5253

5354

5455
try:
@@ -244,9 +245,6 @@ def _request_span_modifier(self, span, environ, parsed_headers=None):
244245
request_body=req_body,
245246
peer_ip=request.remote_addr,
246247
)
247-
if config._appsec_enabled:
248-
log.debug("Flask WAF call for Suspicious Request Blocking on request")
249-
_asm_request_context.call_waf_callback()
250248

251249

252250
def patch():
@@ -524,7 +522,7 @@ def _wrap(rule, endpoint=None, view_func=None, **kwargs):
524522
if view_func:
525523
# TODO: `if hasattr(view_func, 'view_class')` then this was generated from a `flask.views.View`
526524
# should we do something special with these views? Change the name/resource? Add tags?
527-
view_func = wrap_function(instance, view_func, name=endpoint, resource=rule)
525+
view_func = wrap_view(instance, view_func, name=endpoint, resource=rule)
528526

529527
return wrapped(rule, endpoint=endpoint, view_func=view_func, **kwargs)
530528

ddtrace/contrib/flask/wrappers.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
from ddtrace import config
2+
import ddtrace.appsec._asm_request_context as _asmrc
3+
from ddtrace.appsec._constants import SPAN_DATA_NAMES
24
from ddtrace.appsec.iast._util import _is_iast_enabled
35
from ddtrace.internal.constants import COMPONENT
46
from ddtrace.vendor.wrapt import function_wrapper
57

68
from .. import trace_utils
9+
from ...internal.logger import get_logger
710
from ...internal.utils.importlib import func_name
811
from ...pin import Pin
912
from .helpers import get_current_app
1013

1114

12-
def wrap_function(instance, func, name=None, resource=None):
15+
log = get_logger(__name__)
16+
17+
18+
def wrap_view(instance, func, name=None, resource=None):
1319
"""
1420
Helper function to wrap common flask.app.Flask methods.
1521
@@ -26,6 +32,17 @@ def trace_func(wrapped, _instance, args, kwargs):
2632
with pin.tracer.trace(name, service=trace_utils.int_service(pin, config.flask), resource=resource) as span:
2733
span.set_tag_str(COMPONENT, config.flask.integration_name)
2834

35+
# if Appsec is enabled, we can try to block as we have the path parameters at that point
36+
if config._appsec_enabled and _asmrc.in_context():
37+
log.debug("Flask WAF call for Suspicious Request Blocking on request")
38+
if kwargs:
39+
_asmrc.set_waf_address(SPAN_DATA_NAMES.REQUEST_PATH_PARAMS, kwargs)
40+
_asmrc.call_waf_callback()
41+
if _asmrc.is_blocked():
42+
callback_block = _asmrc.get_value(_asmrc._CALLBACKS, "flask_block")
43+
if callback_block:
44+
return callback_block()
45+
2946
# If IAST is enabled, taint the Flask function kwargs (path parameters)
3047
if _is_iast_enabled() and kwargs:
3148
from ddtrace.appsec.iast._input_info import Input_info
@@ -39,6 +56,27 @@ def trace_func(wrapped, _instance, args, kwargs):
3956
return trace_func(func)
4057

4158

59+
def wrap_function(instance, func, name=None, resource=None):
60+
"""
61+
Helper function to wrap common flask.app.Flask methods.
62+
63+
This helper will first ensure that a Pin is available and enabled before tracing
64+
"""
65+
if not name:
66+
name = func_name(func)
67+
68+
@function_wrapper
69+
def trace_func(wrapped, _instance, args, kwargs):
70+
pin = Pin._find(wrapped, _instance, instance, get_current_app())
71+
if not pin or not pin.enabled():
72+
return wrapped(*args, **kwargs)
73+
with pin.tracer.trace(name, service=trace_utils.int_service(pin, config.flask), resource=resource) as span:
74+
span.set_tag_str(COMPONENT, config.flask.integration_name)
75+
return wrapped(*args, **kwargs)
76+
77+
return trace_func(func)
78+
79+
4280
def wrap_signal(app, signal, func):
4381
"""
4482
Helper used to wrap signal handlers

ddtrace/contrib/wsgi/wsgi.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -170,20 +170,18 @@ def __call__(self, environ, start_response):
170170
closing_iterator = [content]
171171
not_blocked = False
172172

173+
# [Suspicious Request Blocking on request]
174+
def blocked_view():
175+
ctype, content = self._make_block_content(environ, headers, req_span)
176+
return content, 403, [("content-type", ctype)]
177+
178+
_asm_request_context.set_value(_asm_request_context._CALLBACKS, "flask_block", blocked_view)
179+
173180
if not_blocked:
174181
req_span.set_tag_str(COMPONENT, self._config.integration_name)
175182
# set span.kind to the type of operation being performed
176183
req_span.set_tag_str(SPAN_KIND, SpanKind.SERVER)
177184
self._request_span_modifier(req_span, environ)
178-
if self.tracer._appsec_enabled:
179-
# [Suspicious Request Blocking on request]
180-
if _context.get_item("http.request.blocked", span=req_span):
181-
ctype, content = self._make_block_content(environ, headers, req_span)
182-
start_response("403 FORBIDDEN", [("content-type", ctype)])
183-
closing_iterator = [content]
184-
not_blocked = False
185-
186-
if not_blocked:
187185
try:
188186
app_span = self.tracer.trace(self._application_span_name)
189187

@@ -202,7 +200,7 @@ def __call__(self, environ, start_response):
202200
req_span.finish()
203201
raise
204202
if self.tracer._appsec_enabled and _context.get_item("http.request.blocked", span=req_span):
205-
# [Suspicious Request Blocking on response]
203+
# [Suspicious Request Blocking on request or response]
206204
_, content = self._make_block_content(environ, headers, req_span)
207205
closing_iterator = [content]
208206

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
fixes:
3+
- |
4+
ASM: This fix resolves an issue where path parameters for the Flask framework were handled at
5+
response time instead of at request time for suspicious request blocking.
6+
This close a known issue opened in 1.10.0.

0 commit comments

Comments
 (0)