Skip to content

Commit 60b5599

Browse files
Merge pull request #101 from AikidoSec/AIK-3353
Send a cleaned up version of the stacktrace to the backend
2 parents 0cdc326 + bf16748 commit 60b5599

File tree

9 files changed

+95
-15
lines changed

9 files changed

+95
-15
lines changed

aikido_firewall/background_process/aikido_background_process.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ def send_to_reporter(self, event_scheduler):
8686
)
8787
logger.debug("Checking queue")
8888
while not self.queue.empty():
89-
attack = self.queue.get()
90-
logger.debug("Reporting attack : %s", attack)
91-
self.reporter.on_detected_attack(attack[0], attack[1])
89+
queue_attack_item = self.queue.get()
90+
self.reporter.on_detected_attack(
91+
attack=queue_attack_item[0],
92+
context=queue_attack_item[1],
93+
blocked=queue_attack_item[2],
94+
stack=queue_attack_item[3],
95+
)

aikido_firewall/background_process/commands/attack.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
def process_attack(bg_process, data, conn):
55
"""
66
Adds ATTACK data object to queue
7-
Expected data object : [injection_results, context, blocked_or_not]
7+
Expected data object : [injection_results, context, blocked_or_not, stacktrace]
88
"""
99
bg_process.queue.put(data)
1010
if bg_process.reporter.statistics:

aikido_firewall/background_process/reporter/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,9 @@ def start(self, event_scheduler):
5858
send_heartbeats_every_x_secs(self, self.heartbeat_secs, event_scheduler)
5959
start_polling_for_changes(self, event_scheduler)
6060

61-
def on_detected_attack(self, attack, context):
61+
def on_detected_attack(self, attack, context, blocked, stack):
6262
"""This will send something to the API when an attack is detected"""
63-
return on_detected_attack(self, attack, context)
63+
return on_detected_attack(self, attack, context, blocked, stack)
6464

6565
def on_start(self):
6666
"""This will send out an Event signalling the start to the server"""

aikido_firewall/background_process/reporter/on_detected_attack.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from aikido_firewall.helpers.get_ua_from_context import get_ua_from_context
88

99

10-
def on_detected_attack(reporter, attack, context):
10+
def on_detected_attack(reporter, attack, context, blocked, stack):
1111
"""
1212
This will send something to the API when an attack is detected
1313
"""
@@ -18,7 +18,8 @@ def on_detected_attack(reporter, attack, context):
1818
attack["user"] = None
1919
attack["payload"] = json.dumps(attack["payload"])[:4096]
2020
attack["metadata"] = limit_length_metadata(attack["metadata"], 4096)
21-
attack["blocked"] = reporter.block
21+
attack["blocked"] = blocked
22+
attack["stack"] = stack
2223

2324
payload = {
2425
"type": "detected_attack",

aikido_firewall/background_process/reporter/on_detected_attack_test.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class Context:
3131
def test_on_detected_attack_no_token(mock_context):
3232
reporter = MagicMock()
3333
reporter.token = None
34-
on_detected_attack(reporter, {}, mock_context)
34+
on_detected_attack(reporter, {}, mock_context, blocked=False, stack=None)
3535
reporter.api.report.assert_not_called()
3636

3737

@@ -42,7 +42,7 @@ def test_on_detected_attack_with_long_payload(mock_reporter, mock_context):
4242
"metadata": {"test": "1"},
4343
}
4444

45-
on_detected_attack(mock_reporter, attack, mock_context)
45+
on_detected_attack(mock_reporter, attack, mock_context, blocked=False, stack=None)
4646
assert len(attack["payload"]) == 4096 # Ensure payload is truncated
4747
mock_reporter.api.report.assert_called_once()
4848

@@ -54,7 +54,7 @@ def test_on_detected_attack_with_long_metadata(mock_reporter, mock_context):
5454
"metadata": {"test": long_metadata},
5555
}
5656

57-
on_detected_attack(mock_reporter, attack, mock_context)
57+
on_detected_attack(mock_reporter, attack, mock_context, blocked=False, stack=None)
5858

5959
assert (
6060
attack["metadata"]["test"] == long_metadata[:4096]
@@ -68,7 +68,7 @@ def test_on_detected_attack_success(mock_reporter, mock_context):
6868
"metadata": {},
6969
}
7070

71-
on_detected_attack(mock_reporter, attack, mock_context)
71+
on_detected_attack(mock_reporter, attack, mock_context, blocked=False, stack=None)
7272
assert mock_reporter.api.report.call_count == 1
7373

7474

@@ -81,6 +81,24 @@ def test_on_detected_attack_exception_handling(mock_reporter, mock_context, capl
8181
# Simulate an exception during the API call
8282
mock_reporter.api.report.side_effect = Exception("API error")
8383

84-
on_detected_attack(mock_reporter, attack, mock_context)
84+
on_detected_attack(mock_reporter, attack, mock_context, blocked=False, stack=None)
8585

8686
assert "Failed to report attack" in caplog.text
87+
88+
89+
def test_on_detected_attack_with_blocked_and_stack(mock_reporter, mock_context):
90+
attack = {
91+
"payload": {"key": "value"},
92+
"metadata": {},
93+
}
94+
blocked = True
95+
stack = "sample stack trace"
96+
97+
on_detected_attack(
98+
mock_reporter, attack, mock_context, blocked=blocked, stack=stack
99+
)
100+
101+
# Check that the attack dictionary has the blocked and stack fields set
102+
assert attack["blocked"] is True
103+
assert attack["stack"] == stack
104+
assert mock_reporter.api.report.call_count == 1
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
"""Exports function `get_clean_stacktrace`"""
2+
3+
import inspect
4+
import sys
5+
6+
7+
def get_clean_stacktrace():
8+
"""Returns a cleaned up stacktrace"""
9+
# Get the current stack
10+
stack = inspect.stack()
11+
12+
# List of built-in modules to filter out
13+
ignored_modules = sys.builtin_module_names
14+
15+
cleaned_stack = []
16+
17+
for frame_info in stack:
18+
name = frame_info.frame.f_globals.get("__name__", "")
19+
20+
if name not in ignored_modules and not name.startswith("aikido_firewall"):
21+
cleaned_stack.append(
22+
f"File: {frame_info.filename}, L{frame_info.lineno} {frame_info.function}(...)"
23+
)
24+
25+
cleaned_stack.reverse()
26+
return "• " + "\n\n• ".join(cleaned_stack)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import pytest
2+
import pytest
3+
from .get_clean_stacktrace import get_clean_stacktrace
4+
5+
6+
def test_get_clean_stacktrace_no_aikido():
7+
"""Test that the stack trace does not include aikido frames."""
8+
9+
def dummy_function():
10+
return get_clean_stacktrace()
11+
12+
result = dummy_function()
13+
14+
assert "/site-packages/aikido_firewall/" not in result
15+
16+
17+
def test_get_clean_stacktrace_with_aikido():
18+
"""Test that the stack trace includes non-aikido frames."""
19+
20+
def dummy_function():
21+
return get_clean_stacktrace()
22+
23+
result = dummy_function()

aikido_firewall/vulnerabilities/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
)
1515
from aikido_firewall.background_process import get_comms
1616
from aikido_firewall.helpers.logging import logger
17+
from aikido_firewall.helpers.get_clean_stacktrace import get_clean_stacktrace
1718
from aikido_firewall.helpers.blocking_enabled import is_blocking_enabled
1819
from .sql_injection.context_contains_sql_injection import context_contains_sql_injection
1920
from .nosql_injection.check_context import check_context_for_nosql_injection
@@ -87,6 +88,9 @@ def run_vulnerability_scan(kind, op, args):
8788
if injection_results:
8889
logger.debug("Injection results : %s", json.dumps(injection_results))
8990
blocked = is_blocking_enabled()
90-
comms.send_data_to_bg_process("ATTACK", (injection_results, context, blocked))
91+
stack = get_clean_stacktrace()
92+
comms.send_data_to_bg_process(
93+
"ATTACK", (injection_results, context, blocked, stack)
94+
)
9195
if blocked:
9296
raise error_type(*error_args)

aikido_firewall/vulnerabilities/ssrf/inspect_getaddrinfo_result.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from aikido_firewall.background_process import get_comms
1010
from aikido_firewall.errors import AikidoSSRF
1111
from aikido_firewall.helpers.blocking_enabled import is_blocking_enabled
12+
from aikido_firewall.helpers.get_clean_stacktrace import get_clean_stacktrace
1213
from .imds import is_trusted_hostname, is_imds_ip_address
1314
from .is_private_ip import is_private_ip
1415
from .find_hostname_in_context import find_hostname_in_context
@@ -59,7 +60,10 @@ def inspect_getaddrinfo_result(dns_results, hostname, port):
5960
logger.debug("Attack results : %s", attack)
6061

6162
logger.debug("Sending data to bg process :")
62-
get_comms().send_data_to_bg_process("ATTACK", (attack, context, should_block))
63+
stack = get_clean_stacktrace()
64+
get_comms().send_data_to_bg_process(
65+
"ATTACK", (attack, context, should_block, stack)
66+
)
6367

6468
if should_block:
6569
raise AikidoSSRF()

0 commit comments

Comments
 (0)