Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use zen_internals sql tokenizer #213

Merged
merged 42 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
a4bbddc
Update __init__.py and remove old sql injection algo
Sep 24, 2024
800a0ad
It's only up until 1 char user input
Sep 24, 2024
14d82b4
Better integer test using regex in sql_injection algo
Sep 24, 2024
97002ff
Bugfix for should_return_early sql inj : .contains => in
Sep 25, 2024
5b14d89
Add unit tests for should_return_early in sql_injection algo
Sep 25, 2024
3144aca
Update good and bad sql commands to remove mails, invalid sql and "are"
Sep 25, 2024
6d7de6b
Move union i snot union test to IS_NOT_INJECTION
Sep 25, 2024
76320ea
Remove parameterization of "dangerous chars"
Sep 25, 2024
4dd66f7
foobar) foobar ) and eurofoobar are all examples of sql injections
Sep 25, 2024
5297560
'123' as userinput should not be marked as an injection
Sep 25, 2024
8ade460
Bugfix : Lower query and user input on python side before sending to …
Sep 25, 2024
40f966a
Remove dialects and interchange them with an integer map
Sep 25, 2024
88d7696
Fix test_allow_escape_sequences by setting to not sql injection
Sep 25, 2024
b2c650e
Change what is "invalid query" and what is not
Sep 25, 2024
aa0183e
Fix safely escaped test with backtick
Sep 25, 2024
28bbcd5
Allow for alphanumerical input with dashes
Sep 26, 2024
14821e0
Used wrong naming, dashes when in fact referring to underscores
Sep 26, 2024
2df772c
Move keyword-only queries to "bad queries"
Sep 26, 2024
61a0383
Only lowercase query and userinput once
Sep 27, 2024
ba58135
Update makefile to auto download zen library with install and build
Sep 27, 2024
bfdb293
Use the new folder/naming of the lib.so file in sql injection
Sep 27, 2024
4c71136
Update all github workflows to use make commands for install
Sep 27, 2024
0335ade
Remove dialects from sink tests
Sep 27, 2024
6830d47
Allow aikido_zen/lib/*.so to be included in project, use for download
Sep 27, 2024
3085616
Add comments explaining the reasoning behind removing/altering tests
Sep 27, 2024
a600ee0
Add comments explaining why we removed the e-mail tests
Sep 27, 2024
07d28c6
Add comm'ents test back in as "Mysql" specific
Sep 30, 2024
3b1909f
Allow dialects in is_sql_injection and is_not_sql_injection
Sep 30, 2024
87dde9a
Add mysql specific tests and tests back in for backticks
Sep 30, 2024
dedc404
Also test escaped strings from dangerous strs as not injection
Sep 30, 2024
042a71e
Add comments for given query and add positive counter-examples
Oct 1, 2024
97433b6
Return false if return integer is 2
hansott Oct 23, 2024
00fbb56
Use `logger.debug`
hansott Oct 23, 2024
cbf04e3
Merge branch 'main' into update-sql-injection-algo-to-tokenizer
Nov 26, 2024
c12a0b2
Linting
Nov 26, 2024
bac09da
Download all rust binaries
Nov 26, 2024
6329071
Try-except and use get_lib_path
Nov 26, 2024
f197586
Fix makefile
Nov 26, 2024
af057c8
Fix a broken unit test that has a dialect import
Nov 26, 2024
b4b911d
Bump zen_internals v0.1.26 --> v0.1.31
Nov 26, 2024
b00e0fe
Get the library after early return in sql_injeciton algorithm
Nov 26, 2024
941b377
Update Makefile
bitterpanda63 Nov 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions aikido_zen/sinks/asyncpg.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import copy
import aikido_zen.importhook as importhook
from aikido_zen.vulnerabilities.sql_injection.dialects import Postgres
from aikido_zen.background_process.packages import pkg_compat_check
import aikido_zen.vulnerabilities as vulns
from aikido_zen.helpers.logging import logger
Expand Down Expand Up @@ -36,7 +35,7 @@ def aikido_new__execute(_self, query, *args, **kwargs):
vulns.run_vulnerability_scan(
kind="sql_injection",
op="asyncpg.connection.Connection._execute",
args=(query, Postgres()),
args=(query, "postgres"),
)

return former__execute(_self, query, *args, **kwargs)
Expand All @@ -46,15 +45,15 @@ def aikido_new_executemany(_self, query, *args, **kwargs):
vulns.run_vulnerability_scan(
kind="sql_injection",
op="asyncpg.connection.Connection.executemany",
args=(query, Postgres()),
args=(query, "postgres"),
)
return former_executemany(_self, query, *args, **kwargs)

def aikido_new_execute(_self, query, *args, **kwargs):
vulns.run_vulnerability_scan(
kind="sql_injection",
op="asyncpg.connection.Connection.execute",
args=(query, Postgres()),
args=(query, "postgres"),
)
return former_execute(_self, query, *args, **kwargs)

Expand Down
5 changes: 2 additions & 3 deletions aikido_zen/sinks/mysqlclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import copy
import aikido_zen.importhook as importhook
from aikido_zen.vulnerabilities.sql_injection.dialects import MySQL
from aikido_zen.background_process.packages import pkg_compat_check
from aikido_zen.helpers.logging import logger
import aikido_zen.vulnerabilities as vulns
Expand All @@ -31,13 +30,13 @@ def aikido_new_execute(self, query, args=None):
logger.debug("Query is bytearray, normally comes from executemany.")
return prev_execute_func(self, query, args)
vulns.run_vulnerability_scan(
kind="sql_injection", op="MySQLdb.Cursor.execute", args=(query, MySQL())
kind="sql_injection", op="MySQLdb.Cursor.execute", args=(query, "mysql")
)
return prev_execute_func(self, query, args)

def aikido_new_executemany(self, query, args):
op = "MySQLdb.Cursor.executemany"
vulns.run_vulnerability_scan(kind="sql_injection", op=op, args=(query, MySQL()))
vulns.run_vulnerability_scan(kind="sql_injection", op=op, args=(query, "mysql"))
return prev_executemany_func(self, query, args)

setattr(mysql.Cursor, "execute", aikido_new_execute)
Expand Down
7 changes: 3 additions & 4 deletions aikido_zen/sinks/psycopg.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import copy
import aikido_zen.importhook as importhook
from aikido_zen.vulnerabilities.sql_injection.dialects import Postgres
from aikido_zen.background_process.packages import pkg_compat_check
import aikido_zen.vulnerabilities as vulns

Expand All @@ -27,19 +26,19 @@ def on_psycopg_import(psycopg):
def aikido_copy(self, statement, params=None, *args, **kwargs):
sql = statement
vulns.run_vulnerability_scan(
kind="sql_injection", op="psycopg.Cursor.copy", args=(sql, Postgres())
kind="sql_injection", op="psycopg.Cursor.copy", args=(sql, "postgres")
)
return former_copy_funtcion(self, statement, params, *args, **kwargs)

def aikido_execute(self, query, params=None, *args, **kwargs):
sql = query
vulns.run_vulnerability_scan(
kind="sql_injection", op="psycopg.Cursor.execute", args=(sql, Postgres())
kind="sql_injection", op="psycopg.Cursor.execute", args=(sql, "postgres")
)
return former_execute_function(self, query, params, *args, **kwargs)

def aikido_executemany(self, query, params_seq):
args = (query, Postgres())
args = (query, "postgres")
op = "psycopg.Cursor.executemany"
vulns.run_vulnerability_scan(kind="sql_injection", op=op, args=args)
return former_executemany_function(self, query, params_seq)
Expand Down
5 changes: 2 additions & 3 deletions aikido_zen/sinks/psycopg2.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import copy
import aikido_zen.importhook as importhook
from aikido_zen.vulnerabilities.sql_injection.dialects import Postgres
from aikido_zen.background_process.packages import pkg_compat_check
import aikido_zen.vulnerabilities as vulns

Expand All @@ -21,7 +20,7 @@ def execute(self, *args, **kwargs):
vulns.run_vulnerability_scan(
kind="sql_injection",
op="psycopg2.Connection.Cursor.execute",
args=(args[0], Postgres()), # args[0] : sql
args=(args[0], "postgres"), # args[0] : sql
)
if former_cursor_factory and hasattr(former_cursor_factory, "execute"):
return former_cursor_factory.execute(self, *args, **kwargs)
Expand All @@ -33,7 +32,7 @@ def executemany(self, *args, **kwargs):
vulns.run_vulnerability_scan(
kind="sql_injection",
op="psycopg2.Connection.Cursor.executemany",
args=(sql, Postgres()),
args=(sql, "postgres"),
)
if former_cursor_factory and hasattr(former_cursor_factory, "executemany"):
return former_cursor_factory.executemany(self, *args, **kwargs)
Expand Down
5 changes: 2 additions & 3 deletions aikido_zen/sinks/pymysql.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import copy
import logging
import aikido_zen.importhook as importhook
from aikido_zen.vulnerabilities.sql_injection.dialects import MySQL
from aikido_zen.background_process.packages import pkg_compat_check
import aikido_zen.vulnerabilities as vulns

Expand Down Expand Up @@ -34,13 +33,13 @@ def aikido_new_execute(self, query, args=None):
logger.debug("Query is bytearray, normally comes from executemany.")
return prev_execute_func(self, query, args)
vulns.run_vulnerability_scan(
kind="sql_injection", op="pymysql.Cursor.execute", args=(query, MySQL())
kind="sql_injection", op="pymysql.Cursor.execute", args=(query, "mysql")
)
return prev_execute_func(self, query, args)

def aikido_new_executemany(self, query, args):
op = "pymysql.Cursor.executemany"
vulns.run_vulnerability_scan(kind="sql_injection", op=op, args=(query, MySQL()))
vulns.run_vulnerability_scan(kind="sql_injection", op=op, args=(query, "mysql"))
return prev_executemany_func(self, query, args)

setattr(mysql.Cursor, "execute", aikido_new_execute)
Expand Down
1 change: 0 additions & 1 deletion aikido_zen/sinks/tests/asyncpg_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import aikido_zen.sinks.asyncpg
import aikido_zen.sinks.os
from aikido_zen.background_process.comms import reset_comms
from aikido_zen.vulnerabilities.sql_injection.dialects import Postgres

kind = "sql_injection"
op = "MySQLdb.connections.query"
Expand Down
1 change: 0 additions & 1 deletion aikido_zen/sinks/tests/mysqlclient_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from unittest.mock import patch
import aikido_zen.sinks.mysqlclient
from aikido_zen.background_process.comms import reset_comms
from aikido_zen.vulnerabilities.sql_injection.dialects import MySQL

kind = "sql_injection"
op = "MySQLdb.connections.query"
Expand Down
1 change: 0 additions & 1 deletion aikido_zen/sinks/tests/psycopg2_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from unittest.mock import patch
import aikido_zen.sinks.psycopg2
from aikido_zen.background_process.comms import reset_comms
from aikido_zen.vulnerabilities.sql_injection.dialects import Postgres

kind = "sql_injection"
op = "pymysql.connections.query"
Expand Down
1 change: 0 additions & 1 deletion aikido_zen/sinks/tests/psycopg_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from unittest.mock import patch
import aikido_zen.sinks.psycopg
from aikido_zen.background_process.comms import reset_comms
from aikido_zen.vulnerabilities.sql_injection.dialects import Postgres


@pytest.fixture
Expand Down
1 change: 0 additions & 1 deletion aikido_zen/sinks/tests/pymysql_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from unittest.mock import patch
import aikido_zen.sinks.pymysql
from aikido_zen.background_process.comms import reset_comms
from aikido_zen.vulnerabilities.sql_injection.dialects import MySQL

kind = "sql_injection"
op = "pymysql.connections.query"
Expand Down
2 changes: 1 addition & 1 deletion aikido_zen/vulnerabilities/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def run_vulnerability_scan(kind, op, args):
sql=args[0], dialect=args[1], operation=op, context=context
)
error_type = AikidoSQLInjection
error_args = (type(args[1]).__name__,) # Pass along the dialect
error_args = (args[1],) # Pass along the dialect
elif kind == "nosql_injection":
injection_results = check_context_for_nosql_injection(
context=context, op=op, _filter=args[0]
Expand Down
7 changes: 2 additions & 5 deletions aikido_zen/vulnerabilities/init_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@ def test_lifecycle_cache_bypassed_ip(caplog, get_context):


def test_sql_injection(caplog, get_context, monkeypatch):
from aikido_zen.vulnerabilities.sql_injection.dialects import MySQL

get_context.set_as_current_context()
cache = IPCLifecycleCache(get_context)
Expand All @@ -104,12 +103,11 @@ def test_sql_injection(caplog, get_context, monkeypatch):
run_vulnerability_scan(
kind="sql_injection",
op="test_op",
args=("INSERT * INTO VALUES ('doggoss2', TRUE);", MySQL()),
args=("INSERT * INTO VALUES ('doggoss2', TRUE);", "mysql"),
)


def test_sql_injection_with_comms(caplog, get_context, monkeypatch):
from aikido_zen.vulnerabilities.sql_injection.dialects import MySQL

get_context.set_as_current_context()
cache = IPCLifecycleCache(get_context)
Expand All @@ -123,7 +121,7 @@ def test_sql_injection_with_comms(caplog, get_context, monkeypatch):
run_vulnerability_scan(
kind="sql_injection",
op="test_op",
args=("INSERT * INTO VALUES ('doggoss2', TRUE);", MySQL()),
args=("INSERT * INTO VALUES ('doggoss2', TRUE);", "mysql"),
)
mock_comms.send_data_to_bg_process.assert_called_once()
call_args = mock_comms.send_data_to_bg_process.call_args[0]
Expand All @@ -136,7 +134,6 @@ def test_sql_injection_with_comms(caplog, get_context, monkeypatch):


def test_ssrf_with_comms_hostnames_add(caplog, get_context, monkeypatch):
from aikido_zen.vulnerabilities.sql_injection.dialects import MySQL

get_context.set_as_current_context()
cache = IPCLifecycleCache(get_context)
Expand Down
70 changes: 43 additions & 27 deletions aikido_zen/vulnerabilities/sql_injection/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,57 @@
SQL Injection algorithm
"""

from aikido_zen.vulnerabilities.sql_injection.dialects import MySQL
from aikido_zen.vulnerabilities.sql_injection.query_contains_user_input import (
query_contains_user_input,
)
from aikido_zen.vulnerabilities.sql_injection.userinput_contains_sql_syntax import (
userinput_contains_sqlsyntax,
)
from aikido_zen.vulnerabilities.sql_injection.uinput_occ_safely_encapsulated import (
uinput_occ_safely_encapsulated,
)
import re
import ctypes
from aikido_zen.helpers.logging import logger
from .map_dialect_to_rust_int import map_dialect_to_rust_int

zen_vulns_lib = ctypes.CDLL("zen_vulns_lib/libzen_rustlib.so")


def detect_sql_injection(query, user_input, dialect):
"""
Execute this to check if the query is actually a SQL injection
"""
if len(user_input) <= 1:
# We ignore single characters since they are only able to crash the SQL Server,
# And don't pose a big threat.
if should_return_early(query, user_input):
return False

if len(user_input) > len(query):
# We ignore cases where the user input is longer than the query.
# Because the user input can't be part of the query.
return False
query_bytes = query.lower().encode("utf-8")
bitterpanda63 marked this conversation as resolved.
Show resolved Hide resolved
userinput_bytes = user_input.lower().encode("utf-8")
dialect_int = map_dialect_to_rust_int(dialect)
c_int_res = zen_vulns_lib.detect_sql_injection(
query_bytes, userinput_bytes, dialect_int
)
return bool(c_int_res)

if not query_contains_user_input(query, user_input):
# If the user input is not part of the query, return false (No need to check)
return False

if uinput_occ_safely_encapsulated(query, user_input):
return False
# Executing our final check with the massive RegEx
logger.debug(
"detect_sql_injection : No reason to return false with : %s", user_input
)
return userinput_contains_sqlsyntax(user_input, dialect)
def should_return_early(query, user_input):
"""
Returns true if the detect_sql_injection algo should return early :
- user_input is <= 1 char or user input larger than query
- user_input not in query
- user_input is alphanumerical
- user_input is an array of integers
"""
if len(user_input) <= 1 or len(query) < len(user_input):
# User input too small or larger than query, returning
return True

# Lowercase :
query_l = query.lower()
user_input_l = user_input.lower()
if user_input_l not in query_l:
# User input not in query, returning
return True

if user_input_l.isalnum():
bitterpanda63 marked this conversation as resolved.
Show resolved Hide resolved
# User input is alphanumerical, returning
return True

cleaned_input_for_list = user_input_l.replace(" ", "").replace(",", "")
is_valid_comma_seperated_number_list = re.match(r"^\d+$", cleaned_input_for_list)
if is_valid_comma_seperated_number_list:
# E.g. 1, 24028, 828, 4, 1
return True

return False
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import pytest
from aikido_zen.context import Context
from .context_contains_sql_injection import context_contains_sql_injection
from .dialects import MySQL


class Context2(Context):
Expand Down Expand Up @@ -44,6 +43,6 @@ def test_doesnt_crash_with_invalid_sql(invalid_input):
sql=invalid_input,
operation="mysqlclient.query",
context=context,
dialect=MySQL(),
dialect="mysql",
)
assert result == {}
13 changes: 0 additions & 13 deletions aikido_zen/vulnerabilities/sql_injection/dialects/__init__.py

This file was deleted.

28 changes: 0 additions & 28 deletions aikido_zen/vulnerabilities/sql_injection/dialects/abstract.py

This file was deleted.

Loading
Loading