Skip to content

Add sink statistics #395

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

Merged
merged 24 commits into from
Jun 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
50b4219
create helper function to register a call
bitterpanda63 Jun 5, 2025
a83481a
Pass along operation to statistics
bitterpanda63 Jun 5, 2025
b87af05
Create new operations dict-like object
bitterpanda63 Jun 5, 2025
8162818
integrate operations into statistics object
bitterpanda63 Jun 5, 2025
b80fb22
Add register_call to pymysql
bitterpanda63 Jun 5, 2025
37cf792
Add register_call to pymongo
bitterpanda63 Jun 5, 2025
21455a8
add register_call to mysqlclient
bitterpanda63 Jun 5, 2025
99968fb
asyncpg add register_call
bitterpanda63 Jun 5, 2025
2c91970
builtins add register_call
bitterpanda63 Jun 5, 2025
4f08157
add sink stats to clickhouse_driver
bitterpanda63 Jun 5, 2025
9b0b0d7
add sink stats to http client
bitterpanda63 Jun 5, 2025
30836f7
Add sink stats to io
bitterpanda63 Jun 5, 2025
93d97b7
Add sink stats to os_system
bitterpanda63 Jun 5, 2025
71cdf75
Add sink stats to os
bitterpanda63 Jun 5, 2025
9846ada
Add sink stats to psycopg
bitterpanda63 Jun 5, 2025
09a20c9
Add sink stats to psycopg2
bitterpanda63 Jun 5, 2025
8c799a8
Add sink stats to shutil
bitterpanda63 Jun 5, 2025
040e9cb
Add sink stats to socket
bitterpanda63 Jun 5, 2025
63915c6
Add sink stats to subprocess
bitterpanda63 Jun 5, 2025
d53c18d
Add test cases for new Operations calss
bitterpanda63 Jun 5, 2025
ea22080
Update statistics/__init__.py test cases for operations
bitterpanda63 Jun 5, 2025
388afc8
Add modified update function to operations.py, in order to merge diff…
bitterpanda63 Jun 5, 2025
e237bc7
Fix bug with thread_cache sync and update thread_cache test cases
bitterpanda63 Jun 5, 2025
e96e4e8
Add deserialize_op (xml/lxml)
bitterpanda63 Jun 5, 2025
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: 7 additions & 0 deletions aikido_zen/helpers/register_call.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from aikido_zen.thread.thread_cache import get_cache


def register_call(operation, kind):
cache = get_cache()
if cache:
cache.stats.operations.register_call(operation, kind)
3 changes: 3 additions & 0 deletions aikido_zen/sinks/asyncpg.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import aikido_zen.vulnerabilities as vulns
from aikido_zen.helpers.get_argument import get_argument
from aikido_zen.helpers.register_call import register_call
from aikido_zen.sinks import patch_function, before, on_import


Expand All @@ -12,6 +13,8 @@ def _execute(func, instance, args, kwargs):
query = get_argument(args, kwargs, 0, "query")

op = f"asyncpg.connection.Connection.{func.__name__}"
register_call(op, "sql_op")

vulns.run_vulnerability_scan(kind="sql_injection", op=op, args=(query, "postgres"))


Expand Down
8 changes: 5 additions & 3 deletions aikido_zen/sinks/builtins.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from pathlib import PurePath
import aikido_zen.vulnerabilities as vulns
from aikido_zen.helpers.get_argument import get_argument
from aikido_zen.helpers.register_call import register_call
from aikido_zen.sinks import patch_function, on_import, before


Expand All @@ -14,9 +15,10 @@ def _open(func, instance, args, kwargs):
if not isinstance(filename, (str, bytes, PurePath)):
return

vulns.run_vulnerability_scan(
kind="path_traversal", op="builtins.open", args=(filename,)
)
op = "builtins.open"
register_call(op, "fs_op")

vulns.run_vulnerability_scan(kind="path_traversal", op=op, args=(filename,))


@on_import("builtins")
Expand Down
9 changes: 6 additions & 3 deletions aikido_zen/sinks/clickhouse_driver.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
from aikido_zen.helpers.get_argument import get_argument
from aikido_zen.helpers.register_call import register_call
from aikido_zen.sinks import before, on_import, patch_function
from aikido_zen.vulnerabilities import run_vulnerability_scan


@before
def _execute(func, instance, args, kwargs):
kind = "sql_injection"
op = "clickhouse_driver.Client.execute"
query = get_argument(args, kwargs, 0, "query")
run_vulnerability_scan(kind, op, args=(query, "clickhouse"))

op = "clickhouse_driver.Client.execute"
register_call(op, "sql_op")

run_vulnerability_scan("sql_injection", op, args=(query, "clickhouse"))


@on_import("clickhouse_driver", package="clickhouse_driver")
Expand Down
4 changes: 4 additions & 0 deletions aikido_zen/sinks/http_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""

from aikido_zen.helpers.get_argument import get_argument
from aikido_zen.helpers.register_call import register_call
from aikido_zen.sinks import before, after, patch_function, on_import
from aikido_zen.vulnerabilities.ssrf.handle_http_response import (
handle_http_response,
Expand All @@ -22,6 +23,9 @@ def _putrequest(func, instance, args, kwargs):
def _getresponse(func, instance, args, kwargs, return_value):
path = getattr(instance, "_aikido_var_path")
source_url = try_parse_url(f"http://{instance.host}:{instance.port}{path}")

register_call("HTTPConnection.getresponse", "outgoing_http_op")

handle_http_response(http_response=return_value, source=source_url)


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

import aikido_zen.vulnerabilities as vulns
from aikido_zen.helpers.get_argument import get_argument
from aikido_zen.helpers.register_call import register_call
from aikido_zen.sinks import patch_function, before, on_import


Expand All @@ -13,7 +14,10 @@ def _open(func, instance, args, kwargs):
if not file:
return

vulns.run_vulnerability_scan(kind="path_traversal", op="io.open", args=(file,))
op = "io.open"
register_call(op, "fs_op")

vulns.run_vulnerability_scan(kind="path_traversal", op=op, args=(file,))


@before
Expand All @@ -22,7 +26,10 @@ def _open_code(func, instance, args, kwargs):
if not path:
return

vulns.run_vulnerability_scan(kind="path_traversal", op="io.open_code", args=(path,))
op = "io.open_code"
register_call(op, "fs_op")

vulns.run_vulnerability_scan(kind="path_traversal", op=op, args=(path,))


@on_import("io")
Expand Down
3 changes: 3 additions & 0 deletions aikido_zen/sinks/mysqlclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from aikido_zen.helpers.get_argument import get_argument
import aikido_zen.vulnerabilities as vulns
from aikido_zen.helpers.register_call import register_call
from aikido_zen.sinks import patch_function, on_import, before


Expand All @@ -14,6 +15,7 @@ def _execute(func, instance, args, kwargs):
# If query is type bytearray, it will be picked up by our wrapping of executemany
return

register_call("MySQLdb.Cursor.execute", "sql_op")
vulns.run_vulnerability_scan(
kind="sql_injection", op="MySQLdb.Cursor.execute", args=(query, "mysql")
)
Expand All @@ -23,6 +25,7 @@ def _execute(func, instance, args, kwargs):
def _executemany(func, instance, args, kwargs):
query = get_argument(args, kwargs, 0, "query")

register_call("MySQLdb.Cursor.executemany", "sql_op")
vulns.run_vulnerability_scan(
kind="sql_injection", op="MySQLdb.Cursor.executemany", args=(query, "mysql")
)
Expand Down
2 changes: 2 additions & 0 deletions aikido_zen/sinks/os.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from pathlib import PurePath
import aikido_zen.vulnerabilities as vulns
from aikido_zen.helpers.register_call import register_call
from aikido_zen.sinks import before, patch_function, on_import


Expand All @@ -17,6 +18,7 @@ def _os_patch(func, instance, args, kwargs):
op = f"os.{func.__name__}"
if func.__name__ in ("getsize", "join", "expanduser", "expandvars", "realpath"):
op = f"os.path.{func.__name__}"
register_call(op, "fs_op")

vulns.run_vulnerability_scan(kind="path_traversal", op=op, args=(path,))

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

import aikido_zen.vulnerabilities as vulns
from aikido_zen.helpers.get_argument import get_argument
from aikido_zen.helpers.register_call import register_call
from aikido_zen.sinks import patch_function, before, on_import


Expand All @@ -13,9 +14,10 @@ def _system(func, instance, args, kwargs):
if not isinstance(command, str):
return

vulns.run_vulnerability_scan(
kind="shell_injection", op="os.system", args=(command,)
)
op = "os.system"
register_call(op, "exec_op")

vulns.run_vulnerability_scan(kind="shell_injection", op=op, args=(command,))


@on_import("os")
Expand Down
7 changes: 6 additions & 1 deletion aikido_zen/sinks/psycopg.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,19 @@

import aikido_zen.vulnerabilities as vulns
from aikido_zen.helpers.get_argument import get_argument
from aikido_zen.helpers.register_call import register_call
from aikido_zen.sinks import patch_function, on_import, before


@before
def _copy(func, instance, args, kwargs):
statement = get_argument(args, kwargs, 0, "statement")

op = "psycopg.Cursor.copy"
register_call(op, "sql_op")

vulns.run_vulnerability_scan(
kind="sql_injection", op="psycopg.Cursor.copy", args=(statement, "postgres")
kind="sql_injection", op=op, args=(statement, "postgres")
)


Expand Down
4 changes: 4 additions & 0 deletions aikido_zen/sinks/psycopg2.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from aikido_zen.background_process.packages import is_package_compatible
import aikido_zen.vulnerabilities as vulns
from aikido_zen.helpers.get_argument import get_argument
from aikido_zen.helpers.register_call import register_call
from aikido_zen.sinks import on_import, before, patch_function, after


Expand Down Expand Up @@ -35,7 +36,10 @@ def _connect(func, instance, _args, _kwargs, rv):
@before
def psycopg2_patch(func, instance, args, kwargs):
query = get_argument(args, kwargs, 0, "query")

op = f"psycopg2.Connection.Cursor.{func.__name__}"
register_call(op, "sql_op")

vulns.run_vulnerability_scan(kind="sql_injection", op=op, args=(query, "postgres"))


Expand Down
21 changes: 17 additions & 4 deletions aikido_zen/sinks/pymongo.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from aikido_zen.helpers.get_argument import get_argument
import aikido_zen.vulnerabilities as vulns
from . import patch_function, on_import, before
from ..helpers.register_call import register_call


@before
Expand All @@ -14,9 +15,12 @@ def _func_filter_first(func, instance, args, kwargs):
if not nosql_filter:
return

operation = f"pymongo.collection.Collection.{func.__name__}"
register_call(operation, "nosql_op")

vulns.run_vulnerability_scan(
kind="nosql_injection",
op=f"pymongo.collection.Collection.{func.__name__}",
op=operation,
args=(nosql_filter,),
)

Expand All @@ -28,9 +32,12 @@ def _func_filter_second(func, instance, args, kwargs):
if not nosql_filter:
return

operation = f"pymongo.collection.Collection.{func.__name__}"
register_call(operation, "nosql_op")

vulns.run_vulnerability_scan(
kind="nosql_injection",
op=f"pymongo.collection.Collection.{func.__name__}",
op=operation,
args=(nosql_filter,),
)

Expand All @@ -42,9 +49,12 @@ def _func_pipeline(func, instance, args, kwargs):
if not nosql_pipeline:
return

operation = f"pymongo.collection.Collection.{func.__name__}"
register_call(operation, "nosql_op")

vulns.run_vulnerability_scan(
kind="nosql_injection",
op=f"pymongo.collection.Collection.{func.__name__}",
op=operation,
args=(nosql_pipeline,),
)

Expand All @@ -53,12 +63,15 @@ def _func_pipeline(func, instance, args, kwargs):
def _bulk_write(func, instance, args, kwargs):
requests = get_argument(args, kwargs, 0, "requests")

operation = "pymongo.collection.Collection.bulk_write"
register_call(operation, "nosql_op")

# Filter requests that contain "_filter"
requests_with_filter = [req for req in requests if hasattr(req, "_filter")]
for request in requests_with_filter:
vulns.run_vulnerability_scan(
kind="nosql_injection",
op="pymongo.collection.Collection.bulk_write",
op=operation,
args=(request._filter,),
)

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

import aikido_zen.vulnerabilities as vulns
from aikido_zen.helpers.get_argument import get_argument
from aikido_zen.helpers.register_call import register_call
from aikido_zen.sinks import patch_function, on_import, before


Expand All @@ -14,6 +15,7 @@ def _execute(func, instance, args, kwargs):
# If query is type bytearray, it will be picked up by our wrapping of executemany
return

register_call("pymysql.Cursor.execute", "sql_op")
vulns.run_vulnerability_scan(
kind="sql_injection", op="pymysql.Cursor.execute", args=(query, "mysql")
)
Expand All @@ -23,6 +25,7 @@ def _execute(func, instance, args, kwargs):
def _executemany(func, instance, args, kwargs):
query = get_argument(args, kwargs, 0, "query")

register_call("pymysql.Cursor.executemany", "sql_op")
vulns.run_vulnerability_scan(
kind="sql_injection", op="pymysql.Cursor.executemany", args=(query, "mysql")
)
Expand Down
5 changes: 4 additions & 1 deletion aikido_zen/sinks/shutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import aikido_zen.vulnerabilities as vulns
from aikido_zen.helpers.get_argument import get_argument
from aikido_zen.helpers.register_call import register_call
from aikido_zen.sinks import on_import, patch_function, before


Expand All @@ -12,8 +13,10 @@ def _shutil_func(func, instance, args, kwargs):
source = get_argument(args, kwargs, 0, "src")
destination = get_argument(args, kwargs, 1, "dst")

kind = "path_traversal"
op = f"shutil.{func.__name__}"
register_call(op, "fs_op")

kind = "path_traversal"
if isinstance(source, str):
vulns.run_vulnerability_scan(kind, op, args=(source,))
if isinstance(destination, str):
Expand Down
7 changes: 6 additions & 1 deletion aikido_zen/sinks/socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""

from aikido_zen.helpers.get_argument import get_argument
from aikido_zen.helpers.register_call import register_call
from aikido_zen.sinks import on_import, patch_function, after
from aikido_zen.vulnerabilities import run_vulnerability_scan

Expand All @@ -11,8 +12,12 @@
def _getaddrinfo(func, instance, args, kwargs, return_value):
host = get_argument(args, kwargs, 0, "host")
port = get_argument(args, kwargs, 1, "port")

op = "socket.getaddrinfo"
register_call(op, "outgoing_http_op")

arguments = (return_value, host, port) # return_value = dns response
run_vulnerability_scan(kind="ssrf", op="socket.getaddrinfo", args=arguments)
run_vulnerability_scan(kind="ssrf", op=op, args=arguments)


@on_import("socket")
Expand Down
7 changes: 6 additions & 1 deletion aikido_zen/sinks/subprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import aikido_zen.vulnerabilities as vulns
from aikido_zen.helpers.get_argument import get_argument
from aikido_zen.helpers.register_call import register_call
from aikido_zen.sinks import on_import, patch_function, before


Expand All @@ -26,9 +27,13 @@ def _subprocess_init(func, instance, args, kwargs):
command = shell_arguments
if not command:
return

op = "subprocess.Popen"
register_call(op, "exec_op")

vulns.run_vulnerability_scan(
kind="shell_injection",
op=f"subprocess.Popen",
op=op,
args=(command,),
)

Expand Down
Loading
Loading