From f2083aeb251d9ecef2ee7927ce20344521ec4c77 Mon Sep 17 00:00:00 2001
From: nadiraikido <166383531+nadiraikido@users.noreply.github.com>
Date: Mon, 7 Apr 2025 19:53:00 +0200
Subject: [PATCH 01/18] Prioritize JSON parsing for body
---
aikido_zen/sources/django/run_init_stage.py | 13 ++-
.../sources/django/run_init_stage_test.py | 30 +++++
end2end/django_mysql_test.py | 110 +++++++++++++-----
3 files changed, 115 insertions(+), 38 deletions(-)
diff --git a/aikido_zen/sources/django/run_init_stage.py b/aikido_zen/sources/django/run_init_stage.py
index 4b865c1ae..15493a905 100644
--- a/aikido_zen/sources/django/run_init_stage.py
+++ b/aikido_zen/sources/django/run_init_stage.py
@@ -10,6 +10,13 @@ def run_init_stage(request):
"""Parse request and body, run "init" stage with request_handler"""
body = None
try:
+ # Check for JSON
+ if body is None and request.content_type == "application/json":
+ try:
+ body = json.loads(request.body)
+ except Exception:
+ pass
+
# try-catch loading of form parameters, this is to fix issue with DATA_UPLOAD_MAX_NUMBER_FIELDS :
try:
body = request.POST.dict()
@@ -18,12 +25,6 @@ def run_init_stage(request):
except Exception:
pass
- # Check for JSON or XML :
- if body is None and request.content_type == "application/json":
- try:
- body = json.loads(request.body)
- except Exception:
- pass
if body is None or len(body) == 0:
# E.g. XML Data
body = request.body
diff --git a/aikido_zen/sources/django/run_init_stage_test.py b/aikido_zen/sources/django/run_init_stage_test.py
index 7afbf5275..497bdd0da 100644
--- a/aikido_zen/sources/django/run_init_stage_test.py
+++ b/aikido_zen/sources/django/run_init_stage_test.py
@@ -120,6 +120,36 @@ def test_run_init_stage_with_empty_body_string(mock_request):
assert context.body is None
+def test_run_init_stage_with_json_wrong_content_type(mock_request):
+ """Test run_init_stage with an XML request."""
+ mock_request.content_type = "application/x-www-form-urlencoded"
+ mock_request.body = '{"key": "value"}' # Example XML body
+ run_init_stage(mock_request)
+ # Assertions
+ context: Context = get_current_context()
+ assert context.body == {"key": "value"}
+
+
+def test_run_init_stage_with_xml_wrong_content_type(mock_request):
+ """Test run_init_stage with an XML request."""
+ mock_request.content_type = "application/json"
+ mock_request.body = "value" # Example XML body
+ run_init_stage(mock_request)
+ # Assertions
+ context: Context = get_current_context()
+ assert context.body == "value"
+
+
+def test_run_init_stage_with_xml_wrong_content_type_form_urlencoded(mock_request):
+ """Test run_init_stage with an XML request."""
+ mock_request.content_type = "application/x-www-form-urlencoded"
+ mock_request.body = "value=2" # Example XML body
+ run_init_stage(mock_request)
+ # Assertions
+ context: Context = get_current_context()
+ assert context.body == "value=2"
+
+
def test_run_init_stage_with_xml(mock_request):
"""Test run_init_stage with an XML request."""
mock_request.content_type = "application/xml"
diff --git a/end2end/django_mysql_test.py b/end2end/django_mysql_test.py
index 7e499be4d..6f5db62c5 100644
--- a/end2end/django_mysql_test.py
+++ b/end2end/django_mysql_test.py
@@ -1,89 +1,135 @@
import time
import pytest
import requests
-from .server.check_events_from_mock import fetch_events_from_mock, validate_started_event, filter_on_event_type, validate_heartbeat
+from .server.check_events_from_mock import (
+ fetch_events_from_mock,
+ validate_started_event,
+ filter_on_event_type,
+ validate_heartbeat,
+)
# e2e tests for django_mysql sample app
base_url_fw = "http://localhost:8080/app"
base_url_nofw = "http://localhost:8081/app"
+
def test_firewall_started_okay():
events = fetch_events_from_mock("http://localhost:5000")
started_events = filter_on_event_type(events, "started")
assert len(started_events) == 1
validate_started_event(started_events[0], ["django", "mysqlclient"])
+
def test_safe_response_with_firewall():
dog_name = "Bobby Tables"
- res = requests.post(base_url_fw + "/create", data={'dog_name': dog_name})
+ res = requests.post(base_url_fw + "/create", data={"dog_name": dog_name})
assert res.status_code == 200
+
def test_safe_response_without_firewall():
dog_name = "Bobby Tables"
- res = requests.post(base_url_nofw + "/create", data={'dog_name': dog_name})
+ res = requests.post(base_url_nofw + "/create", data={"dog_name": dog_name})
assert res.status_code == 200
def test_dangerous_response_with_firewall():
dog_name = 'Dangerous bobby", 1); -- '
- res = requests.post(base_url_fw + "/create", data={'dog_name': dog_name})
+ res = requests.post(base_url_fw + "/create", data={"dog_name": dog_name})
+ assert res.status_code == 500
+ time.sleep(5) # Wait for attack to be reported
+ events = fetch_events_from_mock("http://localhost:5000")
+ attacks = filter_on_event_type(events, "detected_attack")
+
+ assert len(attacks) == 1
+ del attacks[0]["attack"]["stack"]
+ assert attacks[0]["attack"] == {
+ "blocked": True,
+ "kind": "sql_injection",
+ "metadata": {
+ "sql": 'INSERT INTO sample_app_dogs (dog_name, dog_boss) VALUES ("Dangerous bobby", 1); -- ", "N/A")'
+ },
+ "operation": "MySQLdb.Cursor.execute",
+ "pathToPayload": ".dog_name",
+ "payload": '"Dangerous bobby\\", 1); -- "',
+ "source": "body",
+ "user": None,
+ }
+
+
+def test_dangerous_response_with_form_header_but_json_body():
+ import json
+
+ headers = {"Content-Type": "application/x-www-form-urlencoded"}
+
+ json_body = json.dumps({"dog_name": 'Dangerous bobby", 1); -- '})
+
+ res = requests.post(base_url_fw + "/create", headers=headers, data=json_body)
assert res.status_code == 500
- time.sleep(5) # Wait for attack to be reported
+ time.sleep(5)
+
events = fetch_events_from_mock("http://localhost:5000")
attacks = filter_on_event_type(events, "detected_attack")
-
+
assert len(attacks) == 1
del attacks[0]["attack"]["stack"]
assert attacks[0]["attack"] == {
"blocked": True,
"kind": "sql_injection",
- 'metadata': {'sql': 'INSERT INTO sample_app_dogs (dog_name, dog_boss) VALUES ("Dangerous bobby", 1); -- ", "N/A")'},
- 'operation': 'MySQLdb.Cursor.execute',
- 'pathToPayload': '.dog_name',
- 'payload': '"Dangerous bobby\\", 1); -- "',
- 'source': "body",
- 'user': None
+ "metadata": {
+ "sql": 'INSERT INTO sample_app_dogs (dog_name, dog_boss) VALUES ("Dangerous bobby", 1); -- ", "N/A")'
+ },
+ "operation": "MySQLdb.Cursor.execute",
+ "pathToPayload": ".dog_name",
+ "payload": '"Dangerous bobby\\", 1); -- "',
+ "source": "body",
+ "user": None,
}
+
def test_dangerous_response_with_firewall_shell():
dog_name = 'Dangerous bobby", 1); -- '
res = requests.get(base_url_fw + "/shell/ls -la")
assert res.status_code == 500
- time.sleep(5) # Wait for attack to be reported
+ time.sleep(5) # Wait for attack to be reported
events = fetch_events_from_mock("http://localhost:5000")
attacks = filter_on_event_type(events, "detected_attack")
-
+
assert len(attacks) == 2
- del attacks[0] # Previous attack
+ del attacks[0] # Previous attack
del attacks[0]["attack"]["stack"]
assert attacks[0]["attack"] == {
"blocked": True,
"kind": "shell_injection",
- 'metadata': {'command': 'ls -la'},
- 'operation': 'subprocess.Popen',
- 'pathToPayload': '.[0]',
- 'payload': '"ls -la"',
- 'source': "route_params",
- 'user': None
+ "metadata": {"command": "ls -la"},
+ "operation": "subprocess.Popen",
+ "pathToPayload": ".[0]",
+ "payload": '"ls -la"',
+ "source": "route_params",
+ "user": None,
}
+
def test_dangerous_response_without_firewall():
dog_name = 'Dangerous bobby", 1); -- '
- res = requests.post(base_url_nofw + "/create", data={'dog_name': dog_name})
+ res = requests.post(base_url_nofw + "/create", data={"dog_name": dog_name})
assert res.status_code == 200
+
def test_initial_heartbeat():
- time.sleep(55) # Sleep 5 + 55 seconds for heartbeat
+ time.sleep(55) # Sleep 5 + 55 seconds for heartbeat
events = fetch_events_from_mock("http://localhost:5000")
heartbeat_events = filter_on_event_type(events, "heartbeat")
assert len(heartbeat_events) == 1
- validate_heartbeat(heartbeat_events[0],
- [{
- "apispec": {},
- "hits": 1,
- "hits_delta_since_sync": 1,
- "method": "POST",
- "path": "/app/create"
- }],
- {"aborted":0,"attacksDetected":{"blocked":2,"total":2},"total":0}
+ validate_heartbeat(
+ heartbeat_events[0],
+ [
+ {
+ "apispec": {},
+ "hits": 1,
+ "hits_delta_since_sync": 1,
+ "method": "POST",
+ "path": "/app/create",
+ }
+ ],
+ {"aborted": 0, "attacksDetected": {"blocked": 2, "total": 2}, "total": 0},
)
From a2fd5f956670383cb99cf95604f731695c7d3b4e Mon Sep 17 00:00:00 2001
From: nadiraikido <166383531+nadiraikido@users.noreply.github.com>
Date: Tue, 8 Apr 2025 13:35:33 +0200
Subject: [PATCH 02/18] Revert linting, add JSON endpoint in Django sample app
---
end2end/django_mysql_test.py | 85 ++++++++------------
sample-apps/django-mysql/sample_app/urls.py | 1 +
sample-apps/django-mysql/sample_app/views.py | 15 +++-
3 files changed, 48 insertions(+), 53 deletions(-)
diff --git a/end2end/django_mysql_test.py b/end2end/django_mysql_test.py
index 6f5db62c5..42c1b7e78 100644
--- a/end2end/django_mysql_test.py
+++ b/end2end/django_mysql_test.py
@@ -1,66 +1,53 @@
import time
import pytest
import requests
-from .server.check_events_from_mock import (
- fetch_events_from_mock,
- validate_started_event,
- filter_on_event_type,
- validate_heartbeat,
-)
+import json
+from .server.check_events_from_mock import fetch_events_from_mock, validate_started_event, filter_on_event_type, validate_heartbeat
# e2e tests for django_mysql sample app
base_url_fw = "http://localhost:8080/app"
base_url_nofw = "http://localhost:8081/app"
-
def test_firewall_started_okay():
events = fetch_events_from_mock("http://localhost:5000")
started_events = filter_on_event_type(events, "started")
assert len(started_events) == 1
validate_started_event(started_events[0], ["django", "mysqlclient"])
-
def test_safe_response_with_firewall():
dog_name = "Bobby Tables"
- res = requests.post(base_url_fw + "/create", data={"dog_name": dog_name})
+ res = requests.post(base_url_fw + "/create", data={'dog_name': dog_name})
assert res.status_code == 200
-
def test_safe_response_without_firewall():
dog_name = "Bobby Tables"
- res = requests.post(base_url_nofw + "/create", data={"dog_name": dog_name})
+ res = requests.post(base_url_nofw + "/create", data={'dog_name': dog_name})
assert res.status_code == 200
def test_dangerous_response_with_firewall():
dog_name = 'Dangerous bobby", 1); -- '
- res = requests.post(base_url_fw + "/create", data={"dog_name": dog_name})
+ res = requests.post(base_url_fw + "/create", data={'dog_name': dog_name})
assert res.status_code == 500
- time.sleep(5) # Wait for attack to be reported
+ time.sleep(5) # Wait for attack to be reported
events = fetch_events_from_mock("http://localhost:5000")
attacks = filter_on_event_type(events, "detected_attack")
-
+
assert len(attacks) == 1
del attacks[0]["attack"]["stack"]
assert attacks[0]["attack"] == {
"blocked": True,
"kind": "sql_injection",
- "metadata": {
- "sql": 'INSERT INTO sample_app_dogs (dog_name, dog_boss) VALUES ("Dangerous bobby", 1); -- ", "N/A")'
- },
- "operation": "MySQLdb.Cursor.execute",
- "pathToPayload": ".dog_name",
- "payload": '"Dangerous bobby\\", 1); -- "',
- "source": "body",
- "user": None,
+ 'metadata': {'sql': 'INSERT INTO sample_app_dogs (dog_name, dog_boss) VALUES ("Dangerous bobby", 1); -- ", "N/A")'},
+ 'operation': 'MySQLdb.Cursor.execute',
+ 'pathToPayload': '.dog_name',
+ 'payload': '"Dangerous bobby\\", 1); -- "',
+ 'source': "body",
+ 'user': None
}
-
def test_dangerous_response_with_form_header_but_json_body():
- import json
-
headers = {"Content-Type": "application/x-www-form-urlencoded"}
-
json_body = json.dumps({"dog_name": 'Dangerous bobby", 1); -- '})
res = requests.post(base_url_fw + "/create", headers=headers, data=json_body)
@@ -85,51 +72,45 @@ def test_dangerous_response_with_form_header_but_json_body():
"user": None,
}
-
def test_dangerous_response_with_firewall_shell():
dog_name = 'Dangerous bobby", 1); -- '
res = requests.get(base_url_fw + "/shell/ls -la")
assert res.status_code == 500
- time.sleep(5) # Wait for attack to be reported
+ time.sleep(5) # Wait for attack to be reported
events = fetch_events_from_mock("http://localhost:5000")
attacks = filter_on_event_type(events, "detected_attack")
-
+
assert len(attacks) == 2
- del attacks[0] # Previous attack
+ del attacks[0] # Previous attack
del attacks[0]["attack"]["stack"]
assert attacks[0]["attack"] == {
"blocked": True,
"kind": "shell_injection",
- "metadata": {"command": "ls -la"},
- "operation": "subprocess.Popen",
- "pathToPayload": ".[0]",
- "payload": '"ls -la"',
- "source": "route_params",
- "user": None,
+ 'metadata': {'command': 'ls -la'},
+ 'operation': 'subprocess.Popen',
+ 'pathToPayload': '.[0]',
+ 'payload': '"ls -la"',
+ 'source': "route_params",
+ 'user': None
}
-
def test_dangerous_response_without_firewall():
dog_name = 'Dangerous bobby", 1); -- '
- res = requests.post(base_url_nofw + "/create", data={"dog_name": dog_name})
+ res = requests.post(base_url_nofw + "/create", data={'dog_name': dog_name})
assert res.status_code == 200
-
def test_initial_heartbeat():
- time.sleep(55) # Sleep 5 + 55 seconds for heartbeat
+ time.sleep(55) # Sleep 5 + 55 seconds for heartbeat
events = fetch_events_from_mock("http://localhost:5000")
heartbeat_events = filter_on_event_type(events, "heartbeat")
assert len(heartbeat_events) == 1
- validate_heartbeat(
- heartbeat_events[0],
- [
- {
- "apispec": {},
- "hits": 1,
- "hits_delta_since_sync": 1,
- "method": "POST",
- "path": "/app/create",
- }
- ],
- {"aborted": 0, "attacksDetected": {"blocked": 2, "total": 2}, "total": 0},
+ validate_heartbeat(heartbeat_events[0],
+ [{
+ "apispec": {},
+ "hits": 1,
+ "hits_delta_since_sync": 1,
+ "method": "POST",
+ "path": "/app/create"
+ }],
+ {"aborted":0,"attacksDetected":{"blocked":2,"total":2},"total":0}
)
diff --git a/sample-apps/django-mysql/sample_app/urls.py b/sample-apps/django-mysql/sample_app/urls.py
index 296442ee1..c7be2b8e8 100644
--- a/sample-apps/django-mysql/sample_app/urls.py
+++ b/sample-apps/django-mysql/sample_app/urls.py
@@ -5,6 +5,7 @@
urlpatterns = [
path("", views.index, name="index"),
path("dogpage/", views.dog_page, name="dog_page"),
+ path("json/create", views.json_create_dog, name="json_create"),
path("shell/", views.shell_url, name="shell"),
path("create", views.create_dogpage, name="create")
]
diff --git a/sample-apps/django-mysql/sample_app/views.py b/sample-apps/django-mysql/sample_app/views.py
index 37e45702d..1eb7721ea 100644
--- a/sample-apps/django-mysql/sample_app/views.py
+++ b/sample-apps/django-mysql/sample_app/views.py
@@ -1,11 +1,12 @@
from django.shortcuts import render, get_object_or_404
-from django.http import HttpResponse
+from django.http import HttpResponse, JsonResponse
from django.template import loader
from .models import Dogs
from django.db import connection
from django.views.decorators.csrf import csrf_exempt
# Create your views here.
import subprocess
+import json
def index(request):
dogs = Dogs.objects.all()
@@ -37,3 +38,15 @@ def create_dogpage(request):
print("QUERY : ", query)
cursor.execute(query)
return HttpResponse("Dog page created")
+
+@csrf_exempt
+def json_create_dog(request):
+ if request.method == 'POST':
+ body = request.body.decode('utf-8')
+ body_json = json.loads(body)
+ dog_name = body_json.get('dog_name')
+
+ with connection.cursor() as cursor:
+ query = 'INSERT INTO sample_app_dogs (dog_name, dog_boss) VALUES ("%s", "N/A")' % dog_name
+ cursor.execute(query)
+ return JsonResponse({"status": "Dog page created"})
\ No newline at end of file
From baeeb8e5371b2692e4be8b4aad4750fa7a79ebd4 Mon Sep 17 00:00:00 2001
From: nadiraikido <166383531+nadiraikido@users.noreply.github.com>
Date: Tue, 8 Apr 2025 15:37:00 +0200
Subject: [PATCH 03/18] Always prioritize parsing JSON
---
aikido_zen/sources/django/run_init_stage.py | 21 ++++++++++-----------
1 file changed, 10 insertions(+), 11 deletions(-)
diff --git a/aikido_zen/sources/django/run_init_stage.py b/aikido_zen/sources/django/run_init_stage.py
index 15493a905..887286af0 100644
--- a/aikido_zen/sources/django/run_init_stage.py
+++ b/aikido_zen/sources/django/run_init_stage.py
@@ -10,20 +10,19 @@ def run_init_stage(request):
"""Parse request and body, run "init" stage with request_handler"""
body = None
try:
- # Check for JSON
- if body is None and request.content_type == "application/json":
- try:
- body = json.loads(request.body)
- except Exception:
- pass
-
- # try-catch loading of form parameters, this is to fix issue with DATA_UPLOAD_MAX_NUMBER_FIELDS :
try:
- body = request.POST.dict()
- if len(body) == 0:
- body = None # Reset
+ body = json.loads(request.body)
except Exception:
pass
+
+ if body is None or len(body) == 0:
+ # try-catch loading of form parameters, this is to fix issue with DATA_UPLOAD_MAX_NUMBER_FIELDS :
+ try:
+ body = request.POST.dict()
+ if len(body) == 0:
+ body = None # Reset
+ except Exception:
+ pass
if body is None or len(body) == 0:
# E.g. XML Data
From df376a35b8373e6c3842840cc38f023ddc6d6dfb Mon Sep 17 00:00:00 2001
From: nadiraikido <166383531+nadiraikido@users.noreply.github.com>
Date: Tue, 8 Apr 2025 15:41:49 +0200
Subject: [PATCH 04/18] Linting
---
aikido_zen/sources/django/run_init_stage.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/aikido_zen/sources/django/run_init_stage.py b/aikido_zen/sources/django/run_init_stage.py
index 887286af0..6b8625b16 100644
--- a/aikido_zen/sources/django/run_init_stage.py
+++ b/aikido_zen/sources/django/run_init_stage.py
@@ -14,10 +14,10 @@ def run_init_stage(request):
body = json.loads(request.body)
except Exception:
pass
-
+
if body is None or len(body) == 0:
# try-catch loading of form parameters, this is to fix issue with DATA_UPLOAD_MAX_NUMBER_FIELDS :
- try:
+ try:
body = request.POST.dict()
if len(body) == 0:
body = None # Reset
From 10f9c2460c46159ae0a3c81ac6c6107bd2122f10 Mon Sep 17 00:00:00 2001
From: kapyteinaikido <166383531+kapyteinaikido@users.noreply.github.com>
Date: Tue, 8 Apr 2025 15:43:26 +0200
Subject: [PATCH 05/18] Update django_mysql_test.py
---
end2end/django_mysql_test.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/end2end/django_mysql_test.py b/end2end/django_mysql_test.py
index 42c1b7e78..a1f2146bc 100644
--- a/end2end/django_mysql_test.py
+++ b/end2end/django_mysql_test.py
@@ -50,7 +50,7 @@ def test_dangerous_response_with_form_header_but_json_body():
headers = {"Content-Type": "application/x-www-form-urlencoded"}
json_body = json.dumps({"dog_name": 'Dangerous bobby", 1); -- '})
- res = requests.post(base_url_fw + "/create", headers=headers, data=json_body)
+ res = requests.post(base_url_fw + "/json/create", headers=headers, data=json_body)
assert res.status_code == 500
time.sleep(5)
From 137100918a82fb3b80af547c78155d3e58ee303a Mon Sep 17 00:00:00 2001
From: nadiraikido <166383531+nadiraikido@users.noreply.github.com>
Date: Tue, 8 Apr 2025 17:25:52 +0200
Subject: [PATCH 06/18] Improve incorrect test
---
.../sources/django/run_init_stage_test.py | 17 ++++++++++++++---
1 file changed, 14 insertions(+), 3 deletions(-)
diff --git a/aikido_zen/sources/django/run_init_stage_test.py b/aikido_zen/sources/django/run_init_stage_test.py
index 497bdd0da..60796336b 100644
--- a/aikido_zen/sources/django/run_init_stage_test.py
+++ b/aikido_zen/sources/django/run_init_stage_test.py
@@ -40,6 +40,18 @@ def mock_request():
return request
+@pytest.fixture
+def mock_request_form_body():
+ """Fixture to create a mock request object."""
+ request = MagicMock()
+ request.POST.dict.return_value = {"a": [1, 2], "b": [2, 3]}
+ request.content_type = "application/x-www-form-urlencoded"
+ request.body = "a[0]=1&a[1]=2&b[0]=2&b[1]=3" # Example JSON body
+ request.META = wsgi_request
+ request.scope = None
+ return request
+
+
@pytest.fixture(autouse=True)
def run_around_tests():
yield
@@ -57,10 +69,9 @@ def test_run_init_stage_with_json(mock_request):
assert {"key": "value"} == context.body
-def test_run_init_stage_with_dict(mock_request):
+def test_run_init_stage_with_dict(mock_request_form_body):
"""Test run_init_stage with a JSON request."""
- mock_request.POST.dict.return_value = {"a": [1, 2], "b": [2, 3]}
- run_init_stage(mock_request)
+ run_init_stage(mock_request_form_body)
# Assertions
context: Context = get_current_context()
From 2fcca894dba5db3fd99972a000af2d5ee901bad3 Mon Sep 17 00:00:00 2001
From: nadiraikido <166383531+nadiraikido@users.noreply.github.com>
Date: Tue, 8 Apr 2025 17:34:23 +0200
Subject: [PATCH 07/18] Fix assertions
---
end2end/django_mysql_test.py | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/end2end/django_mysql_test.py b/end2end/django_mysql_test.py
index a1f2146bc..a25f094ff 100644
--- a/end2end/django_mysql_test.py
+++ b/end2end/django_mysql_test.py
@@ -57,7 +57,8 @@ def test_dangerous_response_with_form_header_but_json_body():
events = fetch_events_from_mock("http://localhost:5000")
attacks = filter_on_event_type(events, "detected_attack")
- assert len(attacks) == 1
+ assert len(attacks) == 2
+ del attacks[0] # Previous attack
del attacks[0]["attack"]["stack"]
assert attacks[0]["attack"] == {
"blocked": True,
@@ -80,7 +81,7 @@ def test_dangerous_response_with_firewall_shell():
events = fetch_events_from_mock("http://localhost:5000")
attacks = filter_on_event_type(events, "detected_attack")
- assert len(attacks) == 2
+ assert len(attacks) == 3
del attacks[0] # Previous attack
del attacks[0]["attack"]["stack"]
assert attacks[0]["attack"] == {
From 20d6c5bf91938f3597ee3903a298fc1c5f495918 Mon Sep 17 00:00:00 2001
From: nadiraikido <166383531+nadiraikido@users.noreply.github.com>
Date: Tue, 8 Apr 2025 17:44:55 +0200
Subject: [PATCH 08/18] Delete attacks after they happened
---
end2end/django_mysql_test.py | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/end2end/django_mysql_test.py b/end2end/django_mysql_test.py
index a25f094ff..5309795cf 100644
--- a/end2end/django_mysql_test.py
+++ b/end2end/django_mysql_test.py
@@ -45,6 +45,7 @@ def test_dangerous_response_with_firewall():
'source': "body",
'user': None
}
+ del attacks[0]
def test_dangerous_response_with_form_header_but_json_body():
headers = {"Content-Type": "application/x-www-form-urlencoded"}
@@ -57,8 +58,7 @@ def test_dangerous_response_with_form_header_but_json_body():
events = fetch_events_from_mock("http://localhost:5000")
attacks = filter_on_event_type(events, "detected_attack")
- assert len(attacks) == 2
- del attacks[0] # Previous attack
+ assert len(attacks) == 1
del attacks[0]["attack"]["stack"]
assert attacks[0]["attack"] == {
"blocked": True,
@@ -72,6 +72,7 @@ def test_dangerous_response_with_form_header_but_json_body():
"source": "body",
"user": None,
}
+ del attacks[0]
def test_dangerous_response_with_firewall_shell():
dog_name = 'Dangerous bobby", 1); -- '
@@ -81,8 +82,7 @@ def test_dangerous_response_with_firewall_shell():
events = fetch_events_from_mock("http://localhost:5000")
attacks = filter_on_event_type(events, "detected_attack")
- assert len(attacks) == 3
- del attacks[0] # Previous attack
+ assert len(attacks) == 1
del attacks[0]["attack"]["stack"]
assert attacks[0]["attack"] == {
"blocked": True,
@@ -94,6 +94,7 @@ def test_dangerous_response_with_firewall_shell():
'source': "route_params",
'user': None
}
+ del attacks[0]
def test_dangerous_response_without_firewall():
dog_name = 'Dangerous bobby", 1); -- '
From 3331bc880a16bdc7aed44bb11dbf09de37bff11f Mon Sep 17 00:00:00 2001
From: nadiraikido <166383531+nadiraikido@users.noreply.github.com>
Date: Tue, 8 Apr 2025 17:54:36 +0200
Subject: [PATCH 09/18] Revert as attacks list is not persisted
---
end2end/django_mysql_test.py | 14 ++++++--------
1 file changed, 6 insertions(+), 8 deletions(-)
diff --git a/end2end/django_mysql_test.py b/end2end/django_mysql_test.py
index 5309795cf..9437b837f 100644
--- a/end2end/django_mysql_test.py
+++ b/end2end/django_mysql_test.py
@@ -58,9 +58,9 @@ def test_dangerous_response_with_form_header_but_json_body():
events = fetch_events_from_mock("http://localhost:5000")
attacks = filter_on_event_type(events, "detected_attack")
- assert len(attacks) == 1
- del attacks[0]["attack"]["stack"]
- assert attacks[0]["attack"] == {
+ assert len(attacks) == 2
+ del attacks[1]["attack"]["stack"]
+ assert attacks[1]["attack"] == {
"blocked": True,
"kind": "sql_injection",
"metadata": {
@@ -72,7 +72,6 @@ def test_dangerous_response_with_form_header_but_json_body():
"source": "body",
"user": None,
}
- del attacks[0]
def test_dangerous_response_with_firewall_shell():
dog_name = 'Dangerous bobby", 1); -- '
@@ -82,9 +81,9 @@ def test_dangerous_response_with_firewall_shell():
events = fetch_events_from_mock("http://localhost:5000")
attacks = filter_on_event_type(events, "detected_attack")
- assert len(attacks) == 1
- del attacks[0]["attack"]["stack"]
- assert attacks[0]["attack"] == {
+ assert len(attacks) == 3
+ del attacks[2]["attack"]["stack"]
+ assert attacks[2]["attack"] == {
"blocked": True,
"kind": "shell_injection",
'metadata': {'command': 'ls -la'},
@@ -94,7 +93,6 @@ def test_dangerous_response_with_firewall_shell():
'source': "route_params",
'user': None
}
- del attacks[0]
def test_dangerous_response_without_firewall():
dog_name = 'Dangerous bobby", 1); -- '
From d9fa71105c4672f96238fd018e834c8743277c02 Mon Sep 17 00:00:00 2001
From: nadiraikido <166383531+nadiraikido@users.noreply.github.com>
Date: Tue, 8 Apr 2025 19:04:13 +0200
Subject: [PATCH 10/18] Increase number of attacks
---
end2end/django_mysql_test.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/end2end/django_mysql_test.py b/end2end/django_mysql_test.py
index 9437b837f..bd987b385 100644
--- a/end2end/django_mysql_test.py
+++ b/end2end/django_mysql_test.py
@@ -112,5 +112,5 @@ def test_initial_heartbeat():
"method": "POST",
"path": "/app/create"
}],
- {"aborted":0,"attacksDetected":{"blocked":2,"total":2},"total":0}
+ {"aborted":0,"attacksDetected":{"blocked":3,"total":3},"total":0}
)
From 5a97992442e7e79a73a5782427b7f18207ffdaa4 Mon Sep 17 00:00:00 2001
From: nadiraikido <166383531+nadiraikido@users.noreply.github.com>
Date: Wed, 9 Apr 2025 17:42:52 +0200
Subject: [PATCH 11/18] Apply the same prioritization for Quart
---
aikido_zen/sources/quart.py | 16 +++++++++-----
end2end/quart_postgres_uvicorn_test.py | 26 +++++++++++++++++++++++
sample-apps/quart-postgres-uvicorn/app.py | 18 ++++++++++++++++
3 files changed, 55 insertions(+), 5 deletions(-)
diff --git a/aikido_zen/sources/quart.py b/aikido_zen/sources/quart.py
index a2057362f..cf0636963 100644
--- a/aikido_zen/sources/quart.py
+++ b/aikido_zen/sources/quart.py
@@ -36,14 +36,20 @@ async def handle_request_wrapper(former_handle_request, quart_app, req):
try:
context = get_current_context()
if context:
+ body = None
+ try:
+ body = await req.get_json(force=True)
+ except Exception:
+ pass
+
form = await req.form
- if req.is_json:
- context.set_body(await req.get_json())
- elif form:
- context.set_body(form)
+ if form and body is None:
+ body = form
else:
data = await req.data
- context.set_body(data.decode("utf-8"))
+ body = data.decode("utf-8")
+
+ context.set_body(body)
context.cookies = req.cookies.to_dict()
context.set_as_current_context()
except Exception as e:
diff --git a/end2end/quart_postgres_uvicorn_test.py b/end2end/quart_postgres_uvicorn_test.py
index ebea4a9ab..563c95e4e 100644
--- a/end2end/quart_postgres_uvicorn_test.py
+++ b/end2end/quart_postgres_uvicorn_test.py
@@ -1,5 +1,6 @@
import time
import pytest
+import json
import requests
from .server.check_events_from_mock import fetch_events_from_mock, validate_started_event, filter_on_event_type
@@ -45,6 +46,31 @@ def test_dangerous_response_with_firewall():
assert attacks[0]["attack"]["user"]["id"] == "user123"
assert attacks[0]["attack"]["user"]["name"] == "John Doe"
+def test_dangerous_response_with_form_header_but_json_body():
+ headers = {"Content-Type": "application/x-www-form-urlencoded"}
+ json_body = json.dumps({"dog_name": 'Dangerous bobby", 1); -- '})
+
+ res = requests.post(post_url_fw + "/json/create", headers=headers, data=json_body)
+ assert res.status_code == 500
+ time.sleep(5)
+
+ events = fetch_events_from_mock("http://localhost:5000")
+ attacks = filter_on_event_type(events, "detected_attack")
+
+ assert len(attacks) == 2
+ del attacks[1]["attack"]["stack"]
+ assert attacks[1]["attack"] == {
+ "blocked": True,
+ "kind": "sql_injection",
+ "metadata": {
+ "sql": 'INSERT INTO sample_app_dogs (dog_name, dog_boss) VALUES ("Dangerous bobby", 1); -- ", "N/A")'
+ },
+ "operation": "asyncpg.connection.Connection.execute",
+ "pathToPayload": ".dog_name",
+ "payload": '"Dangerous bobby\\", 1); -- "',
+ "source": "body",
+ "user": None,
+ }
def test_dangerous_response_without_firewall():
dog_name = "Dangerous Bobby', TRUE); -- "
diff --git a/sample-apps/quart-postgres-uvicorn/app.py b/sample-apps/quart-postgres-uvicorn/app.py
index 65b59725b..41e1c39f6 100644
--- a/sample-apps/quart-postgres-uvicorn/app.py
+++ b/sample-apps/quart-postgres-uvicorn/app.py
@@ -64,6 +64,24 @@ async def create_dog():
return jsonify({"message": f'Dog {dog_name} created successfully'}), 201
+@app.route("/json/create", methods=['POST'])
+async def create_dog_json():
+ data = await request.get_json(force=True)
+ dog_name = data.get('dog_name')
+
+ if not dog_name:
+ return jsonify({"error": "dog_name is required"}), 400
+
+ conn = await get_db_connection()
+ try:
+ await conn.execute(
+ f"INSERT INTO dogs (dog_name, isAdmin) VALUES ('%s', FALSE)" % dog_name
+ )
+ finally:
+ await conn.close()
+
+ return jsonify({"message": f'Dog {dog_name} created successfully'}), 201
+
@app.route("/create_many", methods=['POST'])
async def create_dog_many():
data = await request.form
From 3e29cfaaff271dda7ec4416a268a84265bf2aa1b Mon Sep 17 00:00:00 2001
From: nadiraikido <166383531+nadiraikido@users.noreply.github.com>
Date: Wed, 9 Apr 2025 17:44:51 +0200
Subject: [PATCH 12/18] Remove unnecessary del
---
end2end/django_mysql_test.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/end2end/django_mysql_test.py b/end2end/django_mysql_test.py
index bd987b385..efe90be0b 100644
--- a/end2end/django_mysql_test.py
+++ b/end2end/django_mysql_test.py
@@ -45,7 +45,6 @@ def test_dangerous_response_with_firewall():
'source': "body",
'user': None
}
- del attacks[0]
def test_dangerous_response_with_form_header_but_json_body():
headers = {"Content-Type": "application/x-www-form-urlencoded"}
From 97b1827d9e6de93b4a8e6dede6669df374709619 Mon Sep 17 00:00:00 2001
From: nadiraikido <166383531+nadiraikido@users.noreply.github.com>
Date: Wed, 9 Apr 2025 17:48:50 +0200
Subject: [PATCH 13/18] Update route
---
end2end/quart_postgres_uvicorn_test.py | 2 +-
sample-apps/quart-postgres-uvicorn/app.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/end2end/quart_postgres_uvicorn_test.py b/end2end/quart_postgres_uvicorn_test.py
index 563c95e4e..39480069a 100644
--- a/end2end/quart_postgres_uvicorn_test.py
+++ b/end2end/quart_postgres_uvicorn_test.py
@@ -50,7 +50,7 @@ def test_dangerous_response_with_form_header_but_json_body():
headers = {"Content-Type": "application/x-www-form-urlencoded"}
json_body = json.dumps({"dog_name": 'Dangerous bobby", 1); -- '})
- res = requests.post(post_url_fw + "/json/create", headers=headers, data=json_body)
+ res = requests.post(post_url_fw + "/json", headers=headers, data=json_body)
assert res.status_code == 500
time.sleep(5)
diff --git a/sample-apps/quart-postgres-uvicorn/app.py b/sample-apps/quart-postgres-uvicorn/app.py
index 41e1c39f6..e02d48b1e 100644
--- a/sample-apps/quart-postgres-uvicorn/app.py
+++ b/sample-apps/quart-postgres-uvicorn/app.py
@@ -64,7 +64,7 @@ async def create_dog():
return jsonify({"message": f'Dog {dog_name} created successfully'}), 201
-@app.route("/json/create", methods=['POST'])
+@app.route("/create/json", methods=['POST'])
async def create_dog_json():
data = await request.get_json(force=True)
dog_name = data.get('dog_name')
From 97549739a1809e30904d18a5a511868eb04b8b97 Mon Sep 17 00:00:00 2001
From: kapyteinaikido <166383531+kapyteinaikido@users.noreply.github.com>
Date: Fri, 11 Apr 2025 18:52:47 +0200
Subject: [PATCH 14/18] Change payload
---
end2end/quart_postgres_uvicorn_test.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/end2end/quart_postgres_uvicorn_test.py b/end2end/quart_postgres_uvicorn_test.py
index 39480069a..d8a73eae0 100644
--- a/end2end/quart_postgres_uvicorn_test.py
+++ b/end2end/quart_postgres_uvicorn_test.py
@@ -48,7 +48,7 @@ def test_dangerous_response_with_firewall():
def test_dangerous_response_with_form_header_but_json_body():
headers = {"Content-Type": "application/x-www-form-urlencoded"}
- json_body = json.dumps({"dog_name": 'Dangerous bobby", 1); -- '})
+ json_body = json.dumps({"dog_name": "Dangerous bobby', 1); -- "})
res = requests.post(post_url_fw + "/json", headers=headers, data=json_body)
assert res.status_code == 500
From a533ca106b0693acb0da1bb459568aa67d380872 Mon Sep 17 00:00:00 2001
From: kapyteinaikido <166383531+kapyteinaikido@users.noreply.github.com>
Date: Fri, 11 Apr 2025 18:53:38 +0200
Subject: [PATCH 15/18] Update quart_postgres_uvicorn_test.py
---
end2end/quart_postgres_uvicorn_test.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/end2end/quart_postgres_uvicorn_test.py b/end2end/quart_postgres_uvicorn_test.py
index d8a73eae0..930248fdb 100644
--- a/end2end/quart_postgres_uvicorn_test.py
+++ b/end2end/quart_postgres_uvicorn_test.py
@@ -67,7 +67,7 @@ def test_dangerous_response_with_form_header_but_json_body():
},
"operation": "asyncpg.connection.Connection.execute",
"pathToPayload": ".dog_name",
- "payload": '"Dangerous bobby\\", 1); -- "',
+ "payload": '"Dangerous bobby\\', 1); -- "',
"source": "body",
"user": None,
}
From 74d3b04459a2fb71bc11b2ae4a2b8525f11192cd Mon Sep 17 00:00:00 2001
From: kapyteinaikido <166383531+kapyteinaikido@users.noreply.github.com>
Date: Fri, 11 Apr 2025 18:57:50 +0200
Subject: [PATCH 16/18] Correct payload
---
end2end/quart_postgres_uvicorn_test.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/end2end/quart_postgres_uvicorn_test.py b/end2end/quart_postgres_uvicorn_test.py
index 930248fdb..ea7d6ccec 100644
--- a/end2end/quart_postgres_uvicorn_test.py
+++ b/end2end/quart_postgres_uvicorn_test.py
@@ -67,7 +67,7 @@ def test_dangerous_response_with_form_header_but_json_body():
},
"operation": "asyncpg.connection.Connection.execute",
"pathToPayload": ".dog_name",
- "payload": '"Dangerous bobby\\', 1); -- "',
+ "payload": '"Dangerous bobby\', 1); -- "',
"source": "body",
"user": None,
}
From 9804b9702da05b5f068f54485e69200f1dcfed1a Mon Sep 17 00:00:00 2001
From: kapyteinaikido <166383531+kapyteinaikido@users.noreply.github.com>
Date: Mon, 14 Apr 2025 16:30:51 +0200
Subject: [PATCH 17/18] Update assertion
---
end2end/quart_postgres_uvicorn_test.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/end2end/quart_postgres_uvicorn_test.py b/end2end/quart_postgres_uvicorn_test.py
index ea7d6ccec..9636f22d9 100644
--- a/end2end/quart_postgres_uvicorn_test.py
+++ b/end2end/quart_postgres_uvicorn_test.py
@@ -63,7 +63,7 @@ def test_dangerous_response_with_form_header_but_json_body():
"blocked": True,
"kind": "sql_injection",
"metadata": {
- "sql": 'INSERT INTO sample_app_dogs (dog_name, dog_boss) VALUES ("Dangerous bobby", 1); -- ", "N/A")'
+ "sql": "INSERT INTO dogs (dog_name, isAdmin) VALUES ('Dangerous bobby', 1); -- ', FALSE)"
},
"operation": "asyncpg.connection.Connection.execute",
"pathToPayload": ".dog_name",
From 537fbaf9f4ef66fe180d8f0c5e8e93dcae6baaae Mon Sep 17 00:00:00 2001
From: kapyteinaikido <166383531+kapyteinaikido@users.noreply.github.com>
Date: Mon, 14 Apr 2025 17:01:44 +0200
Subject: [PATCH 18/18] Change assertion again
---
end2end/quart_postgres_uvicorn_test.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/end2end/quart_postgres_uvicorn_test.py b/end2end/quart_postgres_uvicorn_test.py
index 9636f22d9..75d13ea17 100644
--- a/end2end/quart_postgres_uvicorn_test.py
+++ b/end2end/quart_postgres_uvicorn_test.py
@@ -69,7 +69,7 @@ def test_dangerous_response_with_form_header_but_json_body():
"pathToPayload": ".dog_name",
"payload": '"Dangerous bobby\', 1); -- "',
"source": "body",
- "user": None,
+ "user": {'id': 'user123', 'lastIpAddress': '127.0.0.1', 'name': 'John Doe'},
}
def test_dangerous_response_without_firewall():