diff --git a/lib/cuckoo/core/data/guests.py b/lib/cuckoo/core/data/guests.py index a7227d01967..55a82f5fc3d 100644 --- a/lib/cuckoo/core/data/guests.py +++ b/lib/cuckoo/core/data/guests.py @@ -65,12 +65,20 @@ def __init__(self, name, label, platform, manager, task_id): class GuestsMixIn: def guest_get_status(self, task_id: int): """Gets the status for a given guest.""" + try: + task_id = int(task_id) + except (TypeError, ValueError): + return None stmt = select(Guest).where(Guest.task_id == task_id) guest = self.session.scalar(stmt) return guest.status if guest else None def guest_set_status(self, task_id: int, status: str): """Sets the status for a given guest.""" + try: + task_id = int(task_id) + except (TypeError, ValueError): + return stmt = select(Guest).where(Guest.task_id == task_id) guest = self.session.scalar(stmt) if guest is not None: diff --git a/lib/cuckoo/core/data/samples.py b/lib/cuckoo/core/data/samples.py index b95e838b26a..bb2b53f204c 100644 --- a/lib/cuckoo/core/data/samples.py +++ b/lib/cuckoo/core/data/samples.py @@ -474,6 +474,10 @@ def get_source_url(self, sample_id: int = None) -> Optional[str]: def get_parent_sample_from_task(self, task_id: int) -> Optional[Sample]: """Finds the Parent Sample using the ID of the child's Task.""" + try: + task_id = int(task_id) + except (TypeError, ValueError): + return None # This query joins the Sample table (as the parent) to the # association object and filters by the task_id. diff --git a/lib/cuckoo/core/data/tasking.py b/lib/cuckoo/core/data/tasking.py index d935c7ebf2c..4dd1f3c49b0 100644 --- a/lib/cuckoo/core/data/tasking.py +++ b/lib/cuckoo/core/data/tasking.py @@ -919,6 +919,10 @@ def add_url( ) def set_vnc_port(self, task_id: int, port: int): + try: + task_id = int(task_id) + except (TypeError, ValueError): + return stmt = select(Task).where(Task.id == task_id) task = self.session.scalar(stmt) @@ -1680,6 +1684,10 @@ def view_task(self, task_id, details=False) -> Optional[Task]: @param task_id: ID of the task to query. @return: details on the task. """ + try: + task_id = int(task_id) + except (TypeError, ValueError): + return None query = select(Task).where(Task.id == task_id) if details: query = query.options( @@ -1755,6 +1763,10 @@ def tasks_reprocess(self, task_id: int): def view_errors(self, task_id: int) -> List[Error]: """Gets all errors related to a task.""" + try: + task_id = int(task_id) + except (TypeError, ValueError): + return [] stmt = select(Error).where(Error.task_id == task_id) return self.session.scalars(stmt).all() diff --git a/modules/processing/behavior.py b/modules/processing/behavior.py index d6d2598b4a3..633b888cade 100644 --- a/modules/processing/behavior.py +++ b/modules/processing/behavior.py @@ -1153,29 +1153,6 @@ class ProcessTree: def __init__(self): self.processes = [] - self.tree = [] - - def add_node(self, node, tree): - """Add a node to a process tree. - @param node: node to add. - @param tree: processes tree. - @return: boolean with operation success status. - """ - # Walk through the existing tree. - ret = False - for process in tree: - # If the current process has the same ID of the parent process of - # the provided one, append it the children. - if process["pid"] == node["parent_id"]: - process["children"].append(node) - ret = True - break - # Otherwise try with the children of the current process. - else: - if self.add_node(node, process["children"]): - ret = True - break - return ret def event_apicall(self, call, process): for entry in self.processes: @@ -1195,34 +1172,54 @@ def event_apicall(self, call, process): ) def run(self): - children = [] - - # Walk through the generated list of processes. - for process in self.processes: - has_parent = False - # Walk through the list again. - for process_again in self.processes: - if process_again == process: - continue - # If we find a parent for the first process, we mark it as - # as a child. - if process_again["pid"] == process["parent_id"]: - has_parent = True - break - - # If the process has a parent, add it to the children list. - if has_parent: - children.append(process) - # Otherwise it's an orphan and we add it to the tree root. + # Index processes by PID. + # This implementation uses an iterative approach to build the tree and detects cycles + # to prevent infinite recursion or excessive depth that causes JSON serialization issues. + node_lookup = {p["pid"]: p for p in self.processes} + roots = [] + + # Initialize children list for all processes + for p in self.processes: + p["children"] = [] + + for p in self.processes: + parent_pid = p.get("parent_id") + + # Check if parent exists and is not self (self-parenting treated as root) + if parent_pid in node_lookup and parent_pid != p["pid"]: + parent = node_lookup[parent_pid] + + # Cycle Detection: Traverse ancestry to ensure 'p' is not an ancestor of 'parent' + curr = parent + is_cycle = False + # Use a simple counter or set to avoid infinite checks if the map has internal loops + depth = 0 + max_depth = 100 + + while depth < max_depth: + if curr is p: + is_cycle = True + break + + # Move up to the next parent + curr_parent_pid = curr.get("parent_id") + if curr_parent_pid in node_lookup and curr_parent_pid != curr["pid"]: + curr = node_lookup[curr_parent_pid] + depth += 1 + else: + # Reached a root or unknown parent + break + + if not is_cycle: + parent["children"].append(p) + else: + # Cycle detected or depth limit hit, treat as root to avoid breaking JSON + log.warning("Cycle or deep nesting detected for process %s (parent %s). treating as root.", p["pid"], parent_pid) + roots.append(p) else: - self.tree.append(process) - - # Now we loop over the remaining child processes. - for process in children: - if not self.add_node(process, self.tree): - self.tree.append(process) + roots.append(p) - return self.tree + return roots class NetworkMap: diff --git a/tests/test_database.py b/tests/test_database.py index 3b8d3f2fd15..0da02ba471c 100644 --- a/tests/test_database.py +++ b/tests/test_database.py @@ -1591,3 +1591,33 @@ def test_filter_machines_to_task(self, task, machines, expected_result, db: _Dat assert len(output_machines) == expected_result else: assert output_machines.count() == expected_result + + def test_id_coercion_robustness(self, db: _Database, temp_filename: str): + with db.session.begin(): + t1 = db.add_path(temp_filename) + + with db.session.begin(): + # Test view_task with string task_id + task = db.view_task(str(t1)) + assert task is not None + assert task.id == t1 + + # Test view_task with invalid task_id + assert db.view_task("not-an-int") is None + + # Test view_errors with invalid task_id + assert db.view_errors("not-an-int") == [] + + # Test set_vnc_port with invalid task_id (should not raise exception) + db.set_vnc_port("not-an-int", 5901) + + with db.session.begin(): + # Test guest_get_status/set_status with string and invalid task_id + assert db.guest_get_status(str(t1)) is None + assert db.guest_get_status("not-an-int") is None + db.guest_set_status("not-an-int", "running") + + with db.session.begin(): + assert db.get_parent_sample_from_task(str(t1)) is None + assert db.get_parent_sample_from_task("not-an-int") is None + diff --git a/web/audit/urls.py b/web/audit/urls.py index 13d423430c4..c4c391c0f3e 100644 --- a/web/audit/urls.py +++ b/web/audit/urls.py @@ -2,21 +2,21 @@ # This file is part of Cuckoo Sandbox - http://www.cuckoosandbox.org # See the file "docs/LICENSE" for copying permission. -from django.urls import re_path, path +from django.urls import path from audit import views urlpatterns = [ - re_path(r"^$", views.audit_index, name="audit_index"), - re_path(r"^page/(?P\d+)/$", views.audit_index, name="audit_index"), - re_path(r"^session/(?P\d+)/$", views.session_index, name="test_session"), - re_path(r"^session/(?P\d+)/status$", views.session_status, name="session_status"), - re_path(r"^session/(?P\d+)/run_update//", views.get_run_update, name="get_run_update"), - re_path(r"^reload_available_tests/", views.reload_available_tests, name="reload_available_tests"), - re_path(r"^create_test_session/$", views.create_test_session, name="create_test_session"), - re_path(r"^delete_test_session/(?P\d+)/$", views.delete_test_session, name="delete_test_session"), - path(r"session//queue_tests/", views.queue_all_tests, name="queue_all_tests"), - path(r"session//unqueue_tests/", views.unqueue_all_tests, name="unqueue_all_tests"), - path(r"session//queue_tests//", views.queue_test, name="queue_test"), - path(r"session//unqueue_tests//", views.unqueue_test, name="unqueue_test"), - re_path(r"^update_task_config/(?P\d+)/$", views.update_task_config, name="update_task_config") + path("", views.audit_index, name="audit_index"), + path("page//", views.audit_index, name="audit_index"), + path("session//", views.session_index, name="test_session"), + path("session//status", views.session_status, name="session_status"), + path("session//run_update//", views.get_run_update, name="get_run_update"), + path("reload_available_tests/", views.reload_available_tests, name="reload_available_tests"), + path("create_test_session/", views.create_test_session, name="create_test_session"), + path("delete_test_session//", views.delete_test_session, name="delete_test_session"), + path("session//queue_tests/", views.queue_all_tests, name="queue_all_tests"), + path("session//unqueue_tests/", views.unqueue_all_tests, name="unqueue_all_tests"), + path("session//queue_tests//", views.queue_test, name="queue_test"), + path("session//unqueue_tests//", views.unqueue_test, name="unqueue_test"), + path("update_task_config//", views.update_task_config, name="update_task_config") ] diff --git a/web/compare/urls.py b/web/compare/urls.py index 2b64058fcc0..9107857daee 100644 --- a/web/compare/urls.py +++ b/web/compare/urls.py @@ -2,14 +2,14 @@ # This file is part of Cuckoo Sandbox - http://www.cuckoosandbox.org # See the file "docs/LICENSE" for copying permission. -from django.urls import re_path +from django.urls import path from compare import views urlpatterns = [ - re_path(r"^(?P\d+)/$", views.left, name="compare_left"), - re_path(r"^(?P\d+)/(?P\d+)/$", views.both, name="compare_both"), - re_path(r"^(?P\d+)/(?P\d+)/diff/$", views.diff, name="compare_diff"), - re_path(r"^(?P\d+)/(?P\d+)/diff/data/$", views.diff_data, name="compare_diff_data"), - re_path(r"^(?P\d+)/(?P\w+)/$", views.hash, name="compare_hash"), + path("/", views.left, name="compare_left"), + path("//", views.both, name="compare_both"), + path("//diff/", views.diff, name="compare_diff"), + path("//diff/data/", views.diff_data, name="compare_diff_data"), + path("//", views.hash, name="compare_hash"), ] diff --git a/web/guac/urls.py b/web/guac/urls.py index cfe93d680c6..d60b542f7c7 100644 --- a/web/guac/urls.py +++ b/web/guac/urls.py @@ -1,9 +1,9 @@ -from django.urls import path, re_path +from django.urls import path from guac import views urlpatterns = [ - re_path(r"^(?P\d+)/(?P[\w=]+)/$", views.index, name="index"), + path("//", views.index, name="index"), path("direct/vnc///", views.direct_vnc_host_port, name="direct_vnc_host_port"), path("direct/vnc//", views.direct_vnc_vm, name="direct_vnc_vm"), path("direct/vnc//start/", views.direct_vnc_vm_start, name="direct_vnc_vm_start"), diff --git a/web/submission/urls.py b/web/submission/urls.py index a2b2f8fde69..921516a196a 100644 --- a/web/submission/urls.py +++ b/web/submission/urls.py @@ -2,13 +2,13 @@ # This file is part of Cuckoo Sandbox - http://www.cuckoosandbox.org # See the file 'docs/LICENSE' for copying permission. -from django.urls import re_path +from django.urls import path from submission import views urlpatterns = [ - re_path(r"^$", views.index, name="submission"), - re_path(r"^resubmit/(?P\d+)/(?P[\w\d]{64})/$", views.index, name="submission"), - re_path(r"status/(?P\d+)/$", views.status, name="submission_status"), - re_path(r"remote_session/(?P\d+)/$", views.remote_session, name="remote_session"), + path("", views.index, name="submission"), + path("resubmit///", views.index, name="submission"), + path("status//", views.status, name="submission_status"), + path("remote_session//", views.remote_session, name="remote_session"), ] diff --git a/web/templates/analysis/behavior/_tree.html b/web/templates/analysis/behavior/_tree.html index 5aa6ebefd75..0addca4a8a7 100644 --- a/web/templates/analysis/behavior/_tree.html +++ b/web/templates/analysis/behavior/_tree.html @@ -18,7 +18,7 @@
Proces {{process.name}} ({{process.pid}}) {% if process.commandline %} - {{ process.commandline }} + {{ process.commandline }} {% endif %} {% if detections2pid|get_detection_by_pid:process.pid %} {{ detections2pid|get_detection_by_pid:process.pid }}