Skip to content

Commit

Permalink
Merge pull request #309 from GhostManager/hotfix/minor-bug-fixes
Browse files Browse the repository at this point in the history
Release: v3.2.7
  • Loading branch information
chrismaddalena authored May 1, 2023
2 parents 251e063 + ebed401 commit 526d7c2
Show file tree
Hide file tree
Showing 20 changed files with 481 additions and 43 deletions.
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [v3.2.7] - 1 May 2023

### Added

* Added support for exporting and importing tags for the current import/export models (log entries, domains, servers, and findings)

### Changed

* The legacy REST API key notification for new activity logs now displays the log's ID to be used with the API and extensions like `mythic_sync` and `cobalt_sync`
* When creating a new activity log from the project dashboard, that project will now be automatically selected for the new log

### Fixed

* Fixed sidebar search boxes not working as intended following changes in v3.2.3 (Closes #294)

## [v3.2.6] - 10 April 2023

### Changed
Expand Down
4 changes: 2 additions & 2 deletions VERSION
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
v3.2.6
10 April 2023
v3.2.7
1 May 2023
4 changes: 2 additions & 2 deletions config/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
# 3rd Party Libraries
import environ

__version__ = "3.2.6"
__version__ = "3.2.7"
VERSION = __version__
RELEASE_DATE = "10 April 2023"
RELEASE_DATE = "1 May 2023"

ROOT_DIR = Path(__file__).resolve(strict=True).parent.parent.parent
APPS_DIR = ROOT_DIR / "ghostwriter"
Expand Down
24 changes: 19 additions & 5 deletions ghostwriter/modules/custom_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
# Standard Libraries
from datetime import datetime

# 3rd Party Libraries
import pytz
from bs4 import BeautifulSoup

# Django Imports
from django.conf import settings
from django.utils import dateformat

# 3rd Party Libraries
import pytz
from bs4 import BeautifulSoup
from rest_framework import serializers
from rest_framework.serializers import (
RelatedField,
Expand All @@ -21,7 +21,7 @@

# Ghostwriter Libraries
from ghostwriter.commandcenter.models import CompanyInformation
from ghostwriter.oplog.models import OplogEntry
from ghostwriter.oplog.models import Oplog, OplogEntry
from ghostwriter.reporting.models import (
Evidence,
Finding,
Expand Down Expand Up @@ -680,6 +680,19 @@ class Meta:
fields = "__all__"


class OplogSerializer(TaggitSerializer, CustomModelSerializer):
"""Serialize :model:`oplog.Oplog` entries."""

entries = OplogEntrySerializer(
many=True,
exclude=["id", "oplog_id"],
)

class Meta:
model = Oplog
fields = "__all__"


class ReportDataSerializer(CustomModelSerializer):
"""Serialize :model:`rolodex:Project` and all related entries."""

Expand Down Expand Up @@ -730,6 +743,7 @@ class ReportDataSerializer(CustomModelSerializer):
"client",
]
)
logs = OplogSerializer(source="project.oplog_set", many=True, exclude=["id", "mute_notifications", "project"])
company = SerializerMethodField("get_company_info")
tools = SerializerMethodField("get_tools")

Expand Down
68 changes: 68 additions & 0 deletions ghostwriter/modules/linting_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,74 @@
"doc_type": 2,
"tags": ["tag1", "tag2", "tag3"],
},
"logs": [
{
"entries": [
{
"tags": ["tag1", "tag2", "tag3"],
"start_date": "2023-03-23T17:09:00Z",
"end_date": "2023-03-23T17:10:02Z",
"source_ip": "DEBIAN-DEV (192.168.85.132)",
"dest_ip": "",
"tool": "poseidon",
"user_context": "cmaddalena",
"command": "help",
"description": "",
"output": "",
"comments": "",
"operator_name": "mythic_admin",
},
{
"tags": ["tag1", "tag2", "tag3"],
"start_date": "2023-03-20T21:32:31Z",
"end_date": "2023-03-20T21:32:31Z",
"source_ip": "DEBIAN-DEV (192.168.85.132)",
"dest_ip": "",
"tool": "poseidon",
"user_context": "cmaddalena",
"command": "help ",
"description": "",
"output": "",
"comments": "",
"operator_name": "mythic_admin",
},
],
"name": "SpecterOps Red Team Logs",
},
{
"entries": [
{
"tags": ["tag1", "tag2", "tag3"],
"start_date": "2023-03-23T17:09:00Z",
"end_date": "2023-03-23T17:10:02Z",
"source_ip": "DEBIAN-DEV (192.168.85.132)",
"dest_ip": "",
"tool": "poseidon",
"user_context": "cmaddalena",
"command": "help",
"description": "",
"output": "",
"comments": "",
"operator_name": "mythic_admin",
},
{
"tags": ["tag1", "tag2", "tag3"],
"start_date": "2023-03-20T21:32:31Z",
"end_date": "2023-03-20T21:32:31Z",
"source_ip": "DEBIAN-DEV (192.168.85.132)",
"dest_ip": "",
"tool": "poseidon",
"user_context": "cmaddalena",
"command": "help ",
"description": "",
"output": "",
"comments": "",
"operator_name": "mythic_admin",
},
],
"name": "SpecterOps Red Team Log #2",
},
],
"company": {
"name": "SpecterOps",
"twitter": "@specterops",
Expand Down
45 changes: 45 additions & 0 deletions ghostwriter/modules/shared.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""This contains shared model resources used by multiple applications."""

# 3rd Party Libraries
from import_export import widgets
from import_export.fields import Field
from taggit.forms import TagField
from taggit.models import Tag

# The following is based on the following StackOverflow answer:
# https://stackoverflow.com/questions/59582619/how-to-import-django-taggit-tag-in-django-import-export


class TagWidget(widgets.ManyToManyWidget):
def render(self, value, obj=None):
return self.separator.join([obj.name for obj in value.all()])

def clean(self, value, row=None, *args, **kwargs):
values = TagField().clean(value)
return [Tag.objects.get_or_create(name=tag)[0] for tag in values]


class TagFieldImport(Field):
def save(self, obj, data, is_m2m=False):
if not self.readonly:
attrs = self.attribute.split("__")
for attr in attrs[:-1]:
obj = getattr(obj, attr, None)
cleaned = self.clean(data)
if cleaned is not None or self.saves_null_values:
if not is_m2m:
setattr(obj, attrs[-1], cleaned)
else:
getattr(obj, attrs[-1]).set(cleaned, clear=True)


def taggit_before_import_row(row):
"""Check if the ``tags`` field is empty and set it to a comma (nothing) if it is."""
# The ``django-import-export`` app looks at the ``tags`` field to determine if the field can be null or blank
# and will throw an error if it is not. The field doesn't have Django's ``null`` attribute set to ``True``
# so imports will fail. ``TaggableManager`` doesn't set that attribute so this is a workaround to set the field to a
# comma if it is null. A comma is effectively null for the purposes of this application.
if "tags" in row.keys():
# A blank field may be blank ("") or ``None`` depending on the import file format.
if row["tags"] == "" or row["tags"] is None:
row["tags"] = ","
1 change: 1 addition & 0 deletions ghostwriter/oplog/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from import_export import resources
from import_export.admin import ImportExportModelAdmin

# Ghostwriter Libraries
from ghostwriter.oplog.models import Oplog, OplogEntry
from ghostwriter.oplog.resources import OplogEntryResource

Expand Down
27 changes: 17 additions & 10 deletions ghostwriter/oplog/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,24 +28,31 @@ class Meta:

def __init__(self, project=None, *args, **kwargs):
super().__init__(*args, **kwargs)
# If this is an update, mark project as read-only
# If this is an update, mark the project field as read-only
instance = getattr(self, "instance", None)
if instance and instance.pk:
self.fields["project"].disabled = True

for field in self.fields:
self.fields[field].widget.attrs["autocomplete"] = "off"
self.fields["name"].widget.attrs["placeholder"] = "Descriptive Name for Identification"
self.fields["name"].label = "Name for the Log"
self.fields["name"].help_text = "Enter a name for this log that will help you identify it"

# Limit the list to just projects not marked as complete
# Limit the list to the selected project and disable the field if this is log created for a specific project
self.project_instance = project
active_projects = Project.objects.filter(complete=False).order_by("-start_date")
if active_projects:
self.fields["project"].empty_label = "-- Select an Active Project --"
else:
self.fields["project"].empty_label = "-- No Active Projects --"
self.fields["project"].queryset = active_projects
for field in self.fields:
self.fields[field].widget.attrs["autocomplete"] = "off"
if project:
self.fields["project"].queryset = Project.objects.filter(pk=project.pk)
self.fields["project"].disabled = True

# Limit the list to active projects if this is a new log made from the sidebar
if not project:
active_projects = Project.objects.filter(complete=False).order_by("-start_date")
if active_projects:
self.fields["project"].empty_label = "-- Select an Active Project --"
else:
self.fields["project"].empty_label = "-- No Active Projects --"
self.fields["project"].queryset = active_projects

# Design form layout with Crispy's ``FormHelper``
self.helper = FormHelper()
Expand Down
16 changes: 13 additions & 3 deletions ghostwriter/oplog/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,22 @@

# 3rd Party Libraries
from import_export import resources
from taggit.models import Tag

# Ghostwriter Libraries
from ghostwriter.modules.shared import TagFieldImport, TagWidget, taggit_before_import_row
from ghostwriter.oplog.models import OplogEntry


class OplogEntryResource(resources.ModelResource):
"""
Import and export for :model:`oplog.OplogEntry`.
"""

tags = TagFieldImport(attribute="tags", column_name="tags", widget=TagWidget(Tag, separator=","), default="")

def before_import_row(self, row, **kwargs):
taggit_before_import_row(row)
if "start_date" in row.keys():
try:
timestamp = int(float(row["start_date"]))
Expand All @@ -29,9 +38,9 @@ def before_import_row(self, row, **kwargs):

class Meta:
model = OplogEntry
skip_unchanged = True
exclude = ("id",)
import_id_fields = (
skip_unchanged = False
export_order = (
"id",
"oplog_id",
"start_date",
"end_date",
Expand All @@ -44,4 +53,5 @@ class Meta:
"output",
"comments",
"operator_name",
"tags",
)
2 changes: 1 addition & 1 deletion ghostwriter/oplog/templates/oplog/oplog_import.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ <h4 class="alert-heading">CSV Headers</h4>
<p>Your csv file must have these headers:</p>
<em>
start_date, end_date, source_ip, dest_ip, tool, user_context, command, description, output,
comments, operator_name
comments, operator_name, tags, oplog_id
</em>
</div>
</div>
Expand Down
6 changes: 2 additions & 4 deletions ghostwriter/oplog/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,14 +240,12 @@ def form_valid(self, form):
form.save()
# Create new API key for this oplog
try:
project = form.instance.project.id
oplog_name = form.instance.name
api_key_name = oplog_name
api_key, key = APIKey.objects.create_key(name=api_key_name[:50])
_, key = APIKey.objects.create_key(name=oplog_name[:50])
# Pass the API key via the messages framework
messages.info(
self.request,
f'The logging API key for project {project} and log "{api_key}" is: {key}\r\nPlease store it somewhere safe: you will not be able to see it again.',
f"The logging API key for your log (ID #{form.instance.id}) is: {key}\r\nPlease store it somewhere safe: you will not be able to see it again.",
extra_tags="api-key no-toast",
)
except Exception:
Expand Down
10 changes: 9 additions & 1 deletion ghostwriter/reporting/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
from import_export import resources
from import_export.fields import Field
from import_export.widgets import ForeignKeyWidget
from taggit.models import Tag

# Ghostwriter Libraries
from ghostwriter.modules.shared import TagFieldImport, TagWidget, taggit_before_import_row
from ghostwriter.reporting.models import Finding, FindingType, Severity


Expand All @@ -24,10 +26,14 @@ class FindingResource(resources.ModelResource):
column_name="finding_type",
widget=ForeignKeyWidget(FindingType, "finding_type"),
)
tags = TagFieldImport(attribute="tags", column_name="tags", widget=TagWidget(Tag, separator=","))

def before_import_row(self, row, **kwargs):
taggit_before_import_row(row)

class Meta:
model = Finding
skip_unchanged = True
skip_unchanged = False
fields = (
"id",
"severity",
Expand All @@ -41,6 +47,7 @@ class Meta:
"network_detection_techniques",
"references",
"finding_guidance",
"tags",
)
export_order = (
"id",
Expand All @@ -55,4 +62,5 @@ class Meta:
"network_detection_techniques",
"references",
"finding_guidance",
"tags",
)
Loading

0 comments on commit 526d7c2

Please sign in to comment.