Skip to content

Commit 93d0913

Browse files
tomaisthorpebitterpanda63
authored andcommitted
Add support for Django ASGI
1 parent c77efdf commit 93d0913

3 files changed

Lines changed: 99 additions & 5 deletions

File tree

aikido_zen/sources/django/__init__.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22

33
from ..functions.request_handler import request_handler
44
from .run_init_stage import run_init_stage
5-
from .pre_response_middleware import pre_response_middleware
5+
from .pre_response_middleware import (
6+
pre_response_middleware,
7+
pre_response_middleware_async,
8+
)
69
from ...helpers.get_argument import get_argument
7-
from ...sinks import on_import, patch_function, before, after
10+
from ...sinks import on_import, patch_function, before, after, before_async, after_async
811

912

1013
@before
@@ -25,13 +28,32 @@ def _get_response_after(func, instance, args, kwargs, return_value):
2528
request_handler(stage="post_response", status_code=return_value.status_code)
2629

2730

31+
@before_async
32+
async def _get_response_async_before(func, instance, args, kwargs):
33+
request = get_argument(args, kwargs, 0, "request")
34+
35+
run_init_stage(request)
36+
37+
if pre_response_middleware_async not in getattr(instance, "_view_middleware"):
38+
# pylint:disable=protected-access
39+
instance._view_middleware += [pre_response_middleware_async]
40+
41+
42+
@after_async
43+
async def _get_response_async_after(func, instance, args, kwargs, return_value):
44+
if hasattr(return_value, "status_code"):
45+
request_handler(stage="post_response", status_code=return_value.status_code)
46+
47+
2848
@on_import("django.core.handlers.base", "django")
2949
def patch(m):
3050
"""
31-
Patch for _get_response (Synchronous/WSGI)
51+
Patch for _get_response (WSGI) and _get_response_async (ASGI)
3252
- before: Parse body, create context & add middleware to run before a response
33-
- after: Check respone code to see if route should be analyzed
53+
- after: Check response code to see if route should be analyzed
3454
# https://github.com/django/django/blob/5865ff5adcf64da03d306dc32b36e87ae6927c85/django/core/handlers/base.py#L174
3555
"""
3656
patch_function(m, "BaseHandler._get_response", _get_response_before)
3757
patch_function(m, "BaseHandler._get_response", _get_response_after)
58+
patch_function(m, "BaseHandler._get_response_async", _get_response_async_before)
59+
patch_function(m, "BaseHandler._get_response_async", _get_response_async_after)

aikido_zen/sources/django/pre_response_middleware.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Exports pre_response_middleware function"""
1+
"""Exports pre_response_middleware and pre_response_middleware_async functions"""
22

33
from ..functions.request_handler import request_handler
44

@@ -12,3 +12,14 @@ def pre_response_middleware(request, *args, **kwargs):
1212

1313
return HttpResponse(response[0], status=response[1])
1414
return None
15+
16+
17+
async def pre_response_middleware_async(request, *args, **kwargs):
18+
"""Async variant of pre_response_middleware for Django ASGI"""
19+
response = request_handler(stage="pre_response")
20+
if response:
21+
# pylint:disable=import-outside-toplevel # We don't want to install this by default
22+
from django.http import HttpResponse
23+
24+
return HttpResponse(response[0], status=response[1])
25+
return None
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import pytest
2+
import requests
3+
import time
4+
from .server.check_events_from_mock import fetch_events_from_mock, validate_started_event, filter_on_event_type, \
5+
clear_events_from_mock
6+
7+
# e2e tests for django-asgi-uvicorn sample app
8+
post_url_fw = "http://localhost:8114/create"
9+
post_url_nofw = "http://localhost:8115/create"
10+
11+
12+
def test_firewall_started_okay():
13+
events = fetch_events_from_mock("http://localhost:5000")
14+
started_events = filter_on_event_type(events, "started")
15+
assert len(started_events) == 1
16+
validate_started_event(started_events[0], ["gunicorn", "django", "psycopg"])
17+
18+
19+
def test_safe_response_with_firewall():
20+
dog_name = "Bobby Tables"
21+
res = requests.post(post_url_fw, data={"dog_name": dog_name})
22+
assert res.status_code == 200
23+
24+
25+
def test_safe_response_without_firewall():
26+
dog_name = "Bobby Tables"
27+
res = requests.post(post_url_nofw, data={"dog_name": dog_name})
28+
assert res.status_code == 200
29+
30+
31+
def test_dangerous_response_with_firewall():
32+
clear_events_from_mock("http://localhost:5000")
33+
dog_name = "Dangerous bobby', TRUE); -- "
34+
res = requests.post(post_url_fw, data={"dog_name": dog_name})
35+
assert res.status_code == 500
36+
37+
time.sleep(5) # Wait for attack to be reported
38+
events = fetch_events_from_mock("http://localhost:5000")
39+
attacks = filter_on_event_type(events, "detected_attack")
40+
41+
assert len(attacks) == 1
42+
del attacks[0]["attack"]["stack"]
43+
assert attacks[0]["attack"] == {
44+
"blocked": True,
45+
"kind": "sql_injection",
46+
"metadata": {
47+
"dialect": "postgres",
48+
"sql": "INSERT INTO sample_app_dogs (dog_name, is_admin) VALUES ('Dangerous bobby', TRUE); -- ', FALSE)",
49+
},
50+
"operation": "psycopg.AsyncCursor.execute",
51+
"pathToPayload": ".dog_name.[0]",
52+
"payload": "\"Dangerous bobby', TRUE); -- \"",
53+
"source": "body",
54+
"user": None,
55+
}
56+
57+
58+
def test_dangerous_response_without_firewall():
59+
dog_name = "Dangerous bobby', TRUE); -- "
60+
res = requests.post(post_url_nofw, data={"dog_name": dog_name})
61+
assert res.status_code == 200

0 commit comments

Comments
 (0)