From 021c4f63d74a68d06d9ed566a0c19c266bbc8e2b Mon Sep 17 00:00:00 2001 From: Wout Feys Date: Tue, 23 Jul 2024 10:35:05 +0200 Subject: [PATCH 01/10] Import firewall inside main function --- sample-apps/django-mysql/manage.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sample-apps/django-mysql/manage.py b/sample-apps/django-mysql/manage.py index f66fda4b7..37c761ef2 100755 --- a/sample-apps/django-mysql/manage.py +++ b/sample-apps/django-mysql/manage.py @@ -1,14 +1,13 @@ #!/usr/bin/env python """Django's command-line utility for administrative tasks.""" -import aikido_firewall # Aikido module -aikido_firewall.protect("django") - import os import sys def main(): """Run administrative tasks.""" + import aikido_firewall # Aikido module + aikido_firewall.protect("django") os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'sample-django-mysql-app.settings') try: from django.core.management import execute_from_command_line From d367cd65e73c48eb6f4b69835c3a239439ef35df Mon Sep 17 00:00:00 2001 From: Wout Feys Date: Tue, 23 Jul 2024 11:06:03 +0200 Subject: [PATCH 02/10] Create new mysqlclient file for sink --- aikido_firewall/sinks/mysqlclient.py | 43 ++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 aikido_firewall/sinks/mysqlclient.py diff --git a/aikido_firewall/sinks/mysqlclient.py b/aikido_firewall/sinks/mysqlclient.py new file mode 100644 index 000000000..b153340a0 --- /dev/null +++ b/aikido_firewall/sinks/mysqlclient.py @@ -0,0 +1,43 @@ +""" +Sink module for `mysqlclient` +""" + +import copy +import json +from importlib.metadata import version +import importhook +from aikido_firewall.context import get_current_context +from aikido_firewall.vulnerabilities.sql_injection.check_context_for_sql_injection import ( + check_context_for_sql_injection, +) +from aikido_firewall.vulnerabilities.sql_injection.dialects import MySQL +from aikido_firewall.helpers.logging import logger + + +@importhook.on_import("MySQLdb.connections") +def on_mysqlclient_import(mysql): + """ + Hook 'n wrap on `MySQLdb.connections` + Our goal is to wrap the query() function of the Connection class : + https://github.com/PyMySQL/mysqlclient/blob/9fd238b9e3105dcbed2b009a916828a38d1f0904/src/MySQLdb/connections.py#L257 + Returns : Modified MySQLdb.connections object + """ + modified_mysql = importhook.copy_module(mysql) + + prev_query_function = copy.deepcopy(mysql.Connection.query) + + def aikido_new_query(_self, sql, unbuffered=False): + logger.debug("Wrapper - `mysqlclient` version : %s", version("mysqlclient")) + + context = get_current_context() + result = check_context_for_sql_injection(sql, "Test_op", context, MySQL()) + + logger.info("sql_injection results : %s", json.dumps(result)) + if result: + raise Exception("SQL Injection [aikido_firewall]") + return prev_query_function(_self, sql, unbuffered=False) + + # pylint: disable=no-member + setattr(mysql.Connection, "query", aikido_new_query) + logger.debug("Wrapped `mysqlclient` module") + return modified_mysql From cf0a54a9675108f955945c88e66921d5c594006a Mon Sep 17 00:00:00 2001 From: Wout Feys Date: Tue, 23 Jul 2024 11:12:08 +0200 Subject: [PATCH 03/10] Ad mysqlclient sink import in main file --- aikido_firewall/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/aikido_firewall/__init__.py b/aikido_firewall/__init__.py index aa4910279..0ad26fbf7 100644 --- a/aikido_firewall/__init__.py +++ b/aikido_firewall/__init__.py @@ -25,6 +25,7 @@ def protect(module="any"): # Import sinks import aikido_firewall.sinks.pymysql + import aikido_firewall.sinks.mysqlclient logger.info("Aikido python firewall started") start_agent() From 95094ebde3bea3dcacfe4cba363b1972c6623115 Mon Sep 17 00:00:00 2001 From: Wout Feys Date: Tue, 23 Jul 2024 12:42:29 +0200 Subject: [PATCH 04/10] rm unbuffered parameter, not used in mysqlclient --- aikido_firewall/sinks/mysqlclient.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aikido_firewall/sinks/mysqlclient.py b/aikido_firewall/sinks/mysqlclient.py index b153340a0..15eb23378 100644 --- a/aikido_firewall/sinks/mysqlclient.py +++ b/aikido_firewall/sinks/mysqlclient.py @@ -26,7 +26,7 @@ def on_mysqlclient_import(mysql): prev_query_function = copy.deepcopy(mysql.Connection.query) - def aikido_new_query(_self, sql, unbuffered=False): + def aikido_new_query(_self, sql): logger.debug("Wrapper - `mysqlclient` version : %s", version("mysqlclient")) context = get_current_context() @@ -35,7 +35,7 @@ def aikido_new_query(_self, sql, unbuffered=False): logger.info("sql_injection results : %s", json.dumps(result)) if result: raise Exception("SQL Injection [aikido_firewall]") - return prev_query_function(_self, sql, unbuffered=False) + return prev_query_function(_self, sql) # pylint: disable=no-member setattr(mysql.Connection, "query", aikido_new_query) From 3c8cdf1250acd451189d68553d918404409170df Mon Sep 17 00:00:00 2001 From: Wout Feys Date: Tue, 23 Jul 2024 13:05:10 +0200 Subject: [PATCH 05/10] The sql statement needs to be decoded --- aikido_firewall/sinks/mysqlclient.py | 1 + 1 file changed, 1 insertion(+) diff --git a/aikido_firewall/sinks/mysqlclient.py b/aikido_firewall/sinks/mysqlclient.py index 15eb23378..a79bea0cc 100644 --- a/aikido_firewall/sinks/mysqlclient.py +++ b/aikido_firewall/sinks/mysqlclient.py @@ -31,6 +31,7 @@ def aikido_new_query(_self, sql): context = get_current_context() result = check_context_for_sql_injection(sql, "Test_op", context, MySQL()) + result = check_context_for_sql_injection(sql.decode("utf-8"), "Test_op", context, MySQL()) logger.info("sql_injection results : %s", json.dumps(result)) if result: From 3cd63e698e7ef889a32f5bb1421732073ca1f009 Mon Sep 17 00:00:00 2001 From: Wout Feys Date: Tue, 23 Jul 2024 13:08:34 +0200 Subject: [PATCH 06/10] Linting --- aikido_firewall/sinks/mysqlclient.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/aikido_firewall/sinks/mysqlclient.py b/aikido_firewall/sinks/mysqlclient.py index a79bea0cc..c0069eb58 100644 --- a/aikido_firewall/sinks/mysqlclient.py +++ b/aikido_firewall/sinks/mysqlclient.py @@ -31,7 +31,9 @@ def aikido_new_query(_self, sql): context = get_current_context() result = check_context_for_sql_injection(sql, "Test_op", context, MySQL()) - result = check_context_for_sql_injection(sql.decode("utf-8"), "Test_op", context, MySQL()) + result = check_context_for_sql_injection( + sql.decode("utf-8"), "Test_op", context, MySQL() + ) logger.info("sql_injection results : %s", json.dumps(result)) if result: From f4b184255ac50255b31488e6d87eebc7a1292e00 Mon Sep 17 00:00:00 2001 From: Wout Feys Date: Thu, 25 Jul 2024 14:39:47 +0200 Subject: [PATCH 07/10] Move aikido_firewall import to the top --- sample-apps/django-mysql/manage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sample-apps/django-mysql/manage.py b/sample-apps/django-mysql/manage.py index 37c761ef2..15437c74d 100755 --- a/sample-apps/django-mysql/manage.py +++ b/sample-apps/django-mysql/manage.py @@ -2,11 +2,11 @@ """Django's command-line utility for administrative tasks.""" import os import sys +import aikido_firewall # Aikido module def main(): """Run administrative tasks.""" - import aikido_firewall # Aikido module aikido_firewall.protect("django") os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'sample-django-mysql-app.settings') try: From 96bb8c22f106205b94df16b30e2576df9fd794d2 Mon Sep 17 00:00:00 2001 From: Wout Feys Date: Thu, 25 Jul 2024 14:42:24 +0200 Subject: [PATCH 08/10] Make info log debug log --- aikido_firewall/sinks/mysqlclient.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aikido_firewall/sinks/mysqlclient.py b/aikido_firewall/sinks/mysqlclient.py index c0069eb58..4b4d0fe50 100644 --- a/aikido_firewall/sinks/mysqlclient.py +++ b/aikido_firewall/sinks/mysqlclient.py @@ -35,7 +35,7 @@ def aikido_new_query(_self, sql): sql.decode("utf-8"), "Test_op", context, MySQL() ) - logger.info("sql_injection results : %s", json.dumps(result)) + logger.debug("sql_injection results : %s", json.dumps(result)) if result: raise Exception("SQL Injection [aikido_firewall]") return prev_query_function(_self, sql) From 1790cc89da43146529cc8c8ecfb53be5453ae06c Mon Sep 17 00:00:00 2001 From: Wout Feys Date: Tue, 30 Jul 2024 09:48:26 +0200 Subject: [PATCH 09/10] Remove wrong check_context_for_sql_injection --- aikido_firewall/sinks/mysqlclient.py | 1 - 1 file changed, 1 deletion(-) diff --git a/aikido_firewall/sinks/mysqlclient.py b/aikido_firewall/sinks/mysqlclient.py index 4b4d0fe50..285f20c82 100644 --- a/aikido_firewall/sinks/mysqlclient.py +++ b/aikido_firewall/sinks/mysqlclient.py @@ -30,7 +30,6 @@ def aikido_new_query(_self, sql): logger.debug("Wrapper - `mysqlclient` version : %s", version("mysqlclient")) context = get_current_context() - result = check_context_for_sql_injection(sql, "Test_op", context, MySQL()) result = check_context_for_sql_injection( sql.decode("utf-8"), "Test_op", context, MySQL() ) From 02e2a382701e6a94037c9923cc7f2a813467f7f4 Mon Sep 17 00:00:00 2001 From: Wout Feys Date: Tue, 30 Jul 2024 10:08:29 +0200 Subject: [PATCH 10/10] Remove test op and set to right operation --- aikido_firewall/sinks/mysqlclient.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aikido_firewall/sinks/mysqlclient.py b/aikido_firewall/sinks/mysqlclient.py index 285f20c82..801df40fe 100644 --- a/aikido_firewall/sinks/mysqlclient.py +++ b/aikido_firewall/sinks/mysqlclient.py @@ -31,7 +31,7 @@ def aikido_new_query(_self, sql): context = get_current_context() result = check_context_for_sql_injection( - sql.decode("utf-8"), "Test_op", context, MySQL() + sql.decode("utf-8"), "MySQLdb.connections.query", context, MySQL() ) logger.debug("sql_injection results : %s", json.dumps(result))