From 6262f2c8e50be884e020cd07f796a723fd11f20b Mon Sep 17 00:00:00 2001 From: Stephen Tomkinson Date: Thu, 10 Oct 2024 20:14:49 +0100 Subject: [PATCH] Incorperating command output from CS logs Removed deduplication filter from CS logs as it was hiding cases where a command failed initially then worked on retry. --- cobalt_strike_monitor/models.py | 15 +++++++++++++++ .../cobalt_strike_monitor/archive_list.html | 8 +++++++- event_tracker/views.py | 19 +++++++++++-------- 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/cobalt_strike_monitor/models.py b/cobalt_strike_monitor/models.py index 1bca8a3..6008754 100644 --- a/cobalt_strike_monitor/models.py +++ b/cobalt_strike_monitor/models.py @@ -3,6 +3,7 @@ from django.contrib.contenttypes.fields import GenericRelation from django.db import models +from django.db.models import Q from django.utils.html import format_html, escape from django.utils.timezone import now @@ -250,6 +251,20 @@ def associated_beaconlog_input(self): return BeaconLog.objects.filter(type="input", when__lte=self.when, beacon=self.beacon) \ .order_by("-when").first() + @property + def associated_beaconlog_output(self): + next_output_generator = BeaconLog.objects.filter(Q(type="input") | Q(type="task")) \ + .filter(when__gt=timedelta(seconds=1) + self.when, beacon=self.beacon) \ + .order_by("when") + + outputs_between = BeaconLog.objects.filter(Q(type="output") | Q(type="error")) \ + .filter(id__gte=self.id, beacon=self.beacon) + + if next_output_generator.exists(): + outputs_between = outputs_between.filter(id__lt=next_output_generator.first().id) + + return outputs_between + @property def associated_archive_tasks(self): return Archive.objects.filter(type="task", when__gte=self.when, when__lt=self.when + timedelta(seconds=2), beacon=self.beacon) \ diff --git a/event_tracker/templates/cobalt_strike_monitor/archive_list.html b/event_tracker/templates/cobalt_strike_monitor/archive_list.html index 9acd11e..ecb99a9 100644 --- a/event_tracker/templates/cobalt_strike_monitor/archive_list.html +++ b/event_tracker/templates/cobalt_strike_monitor/archive_list.html @@ -12,6 +12,7 @@ {% block head %} {% include "base/external-libs/jquery.html" %} {% include "base/external-libs/datatables-pdfexport.html" %} + {% endblock head %} @@ -171,7 +176,8 @@ null, null, { orderable: false }, - ] + ], + drawCallback: function(settings) { $('.output').expander({slicePoint: 200}); } } ) }) diff --git a/event_tracker/views.py b/event_tracker/views.py index 99e8fe7..d94613b 100644 --- a/event_tracker/views.py +++ b/event_tracker/views.py @@ -27,7 +27,7 @@ from django.http import JsonResponse, HttpResponse, HttpRequest from django.shortcuts import render, get_object_or_404, redirect from django.template.defaultfilters import truncatechars_html -from django.utils import timezone +from django.utils import timezone, html from django.utils.dateparse import parse_datetime from django.utils.html import escape from django.utils.safestring import mark_safe @@ -945,12 +945,10 @@ class CSLogsListJSON(PermissionRequiredMixin, FilterableDatatableView): filter_column_mapping = {'Timestamp': 'when'} def apply_row_filter(self, qs): - # Removed hidden beacons, prefetches beacon data, and dedupes data - return (qs.filter(beacon__in=Beacon.visible_beacons()) # Hide filtered beacons - .select_related("beacon").select_related("beacon__listener") # Prefetch beacon data - .exclude(data="") # Remove empty rows - .annotate(prev_data=Window(expression=Lag('data', default=Value('')), partition_by=F("beacon")), - ).exclude(data=F("prev_data"))) # Deduplicate data + # Removed hidden beacons, prefetches beacon data, remove empty rows + return qs.filter(beacon__in=Beacon.visible_beacons()) \ + .select_related("beacon").select_related("beacon__listener") \ + .exclude(data="") def get_initial_queryset(self): # Rows with type task where there is no input at the same time nor 1 second earlier @@ -1001,6 +999,8 @@ def render_column(self, row, column): if row.type == "input": result += f"
{row.data}
" + result += f"
{html.escape("\n".join(row.associated_beaconlog_output.values_list('data', flat=True)))}
"
+
             return result
         elif column == '':  # The column with button in
             if row.event_mappings.exists() and self.request.user.has_perm('event_tracker.change_event'):
@@ -1395,6 +1395,9 @@ def get_initial(self):
         else:
             operator = None
 
+        input_evidence = cs_archive.data
+        output_evidence = "\n".join(cs_archive.associated_beaconlog_output.values_list('data', flat=True))
+
         return {
             "task": task,
             "timestamp": timezone.localtime(cs_archive.when).strftime("%Y-%m-%dT%H:%M"),
@@ -1405,7 +1408,7 @@ def get_initial(self):
             "mitre_attack_technique": technique,
             "mitre_attack_subtechnique": subtechnique,
             "description": cs_archive.associated_archive_tasks_description,
-            "raw_evidence": cs_archive.data if cs_archive.type == "input" else None
+            "raw_evidence": f"{input_evidence}{'\n\n' + output_evidence if output_evidence else ''}" if cs_archive.type == "input" else None
         }
 
     def get_context_data(self, **kwargs):