Skip to content

Commit

Permalink
Add screenshot capture for URLs
Browse files Browse the repository at this point in the history
  • Loading branch information
U039b committed Apr 26, 2023
1 parent e8f35a9 commit aeb5d19
Show file tree
Hide file tree
Showing 18 changed files with 226 additions and 85 deletions.
31 changes: 31 additions & 0 deletions colander/core/artifact_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import os

from django.core.files import File

from colander.core.models import Artifact
from colander.core.utils import hash_file


def import_file_as_artifact(input_file, owner, case, artifact_type, name, mimetype, tlp, pap) -> Artifact:
sha256, sha1, md5, size = hash_file(input_file)
input_file.seek(0)
input_file.seek(0, os.SEEK_END)
size_in_bytes = input_file.tell()
input_file.seek(0)
artifact = Artifact(
type=artifact_type,
name=name,
original_name=name,
owner=owner,
case=case,
tlp=tlp,
pap=pap,
mime_type=mimetype,
sha1=sha1,
sha256=sha256,
md5=md5,
size_in_bytes=size_in_bytes,
file=File(file=input_file, name=name)
)
artifact.save()
return artifact
1 change: 1 addition & 0 deletions colander/core/management/commands/data/artifact_types.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
{"short_name": "IOS_SAMPLE", "name": "iOS sample", "description": "", "svg_icon": "", "nf_icon": "nf-mdi-apple_ios"},
{"short_name": "F_DUMP", "name": "Forensic dump", "description": "", "svg_icon": "", "nf_icon": "nf-mdi-harddisk"},
{"short_name": "PCAP", "name": "PCAP file", "description": "", "svg_icon": "", "nf_icon": "nf-mdi-download_network"},
{"short_name": "HAR", "name": "HAR file", "description": "", "svg_icon": "", "nf_icon": "nf-mdi-download_network"},
{"short_name": "SOCKET_T", "name": "Socket activity trace", "description": "", "svg_icon": "", "nf_icon": "nf-mdi-plus_network"},
{"short_name": "CRYPTO_T", "name": "Cryptographic activity trace", "description": "", "svg_icon": "", "nf_icon": "nf-cod-key"},
{"short_name": "SSLKEYLOG", "name": "SSL keylog file", "description": "", "svg_icon": "", "nf_icon": "nf-cod-key"},
Expand Down
4 changes: 4 additions & 0 deletions colander/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,10 @@ def has_been_processed(self):
def value(self):
return self.name

@property
def can_be_displayed(self):
return self.type.short_name in ['IMAGE', 'VIDEO']

@property
def icon(self):
c = self.__class__
Expand Down
57 changes: 57 additions & 0 deletions colander/core/observable_tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from tempfile import NamedTemporaryFile
from zipfile import ZipFile

import requests

from colander.core.artifact_utils import import_file_as_artifact
from colander.core.models import Observable, EntityRelation, ArtifactType


def capture_url(observable_id):
url_object = Observable.objects.get(id=observable_id)
payload = {
'url': url_object.name
}
response = requests.post('http://playwright:80/capture', json=payload)
if response.status_code == 200:
with NamedTemporaryFile() as out:
out.write(response.content)
with ZipFile(out.name, 'r') as archive:
with archive.open('screenshot.png') as screenshot_file:
screenshot = import_file_as_artifact(
screenshot_file,
url_object.owner,
url_object.case,
ArtifactType.objects.get(short_name='IMAGE'),
'webpage_screenshot.png',
'image/png',
url_object.tlp,
url_object.pap,
)
relation = EntityRelation(
name='screenshot of',
owner=url_object.owner,
case=url_object.case,
obj_from=screenshot,
obj_to=url_object
)
relation.save()
with archive.open('capture.har') as screenshot_file:
har = import_file_as_artifact(
screenshot_file,
url_object.owner,
url_object.case,
ArtifactType.objects.get(short_name='HAR'),
'webpage_traffic.har',
'application/json',
url_object.tlp,
url_object.pap,
)
relation = EntityRelation(
name='HTTP traffic of',
owner=url_object.owner,
case=url_object.case,
obj_from=har,
obj_to=url_object
)
relation.save()
7 changes: 7 additions & 0 deletions colander/core/views/artifact_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,13 @@ def download_artifact(request, pk):
response['Content-Disposition'] = 'attachment; filename=' + content.name
return response

@login_required
def view_artifact(request, pk):
content = Artifact.objects.get(id=pk)
response = StreamingHttpResponse(content.file, content_type=content.mime_type)
response['Content-Disposition'] = 'inline; filename=' + content.name
return response

@login_required
def download_artifact_signature(request, pk):
content = Artifact.objects.get(id=pk)
Expand Down
10 changes: 4 additions & 6 deletions colander/core/views/experiment_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,9 +178,8 @@ def start_decryption(request, pk):
experiment = PiRogueExperiment.objects.get(id=pk)
if experiment.pcap and experiment.sslkeylog:
messages.success(request, 'Traffic decryption is in progress, refresh this page in a few minutes.')
save_decrypted_traffic(pk)
# ToDo switch to async task
# async_task(save_decrypted_traffic, pk)
# save_decrypted_traffic(pk)
async_task(save_decrypted_traffic, pk)
else:
messages.error(request, 'Cannot decrypt traffic since your experiment does not have both a PCAP file and an SSL keylog file.')
return redirect(request.META.get('HTTP_REFERER'))
Expand All @@ -192,9 +191,8 @@ def start_detection(request, pk):
experiment = PiRogueExperiment.objects.get(id=pk)
if experiment.analysis:
messages.success(request, 'Traffic analysis is in progress, refresh this page in a few minutes.')
apply_detection_rules(pk)
# ToDo switch to async task
# async_task(apply_detection_rules, pk)
# apply_detection_rules(pk)
async_task(apply_detection_rules, pk)
else:
messages.error(request, 'Cannot analyze traffic since the traffic has not been decrypted yet.')
return redirect(request.META.get('HTTP_REFERER'))
12 changes: 12 additions & 0 deletions colander/core/views/obversable_views.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from django.forms.widgets import Textarea, RadioSelect
from django.shortcuts import redirect
from django.urls import reverse_lazy
from django.utils.safestring import mark_safe
from django.views.generic import CreateView, UpdateView, DetailView
from django_q.tasks import async_task

from colander.core.forms import CommentForm
from colander.core.models import Observable, ObservableRelation, ObservableType, Artifact, Threat, Actor
from colander.core.observable_tasks import capture_url
from colander.core.views.views import get_active_case, CaseRequiredMixin


Expand Down Expand Up @@ -147,3 +150,12 @@ def delete_observable_view(request, pk):
obj = Observable.objects.get(id=pk)
obj.delete()
return redirect("collect_observable_create_view")


@login_required()
def capture_observable_view(request, pk):
obj = Observable.objects.get(id=pk)
if obj.type.short_name == 'URL':
async_task(capture_url, obj.id)
messages.success(request, 'The capture of this URL has started, refresh this page in a few minutes.')
return redirect(request.META.get('HTTP_REFERER'))
25 changes: 1 addition & 24 deletions colander/templates/actor/details.html
Original file line number Diff line number Diff line change
Expand Up @@ -121,30 +121,7 @@ <h3>{% translate "Operated observables" %}</h3>
</div>
{% endif %}

{% with targets=actor.out_relations origins=actor.in_relations %}
{% if origins or targets %}
<div class="col-md-12 mt-2">
<h3>{% translate "Related entities" %}</h3>
<table class="table table-sm">
<thead class="bg-secondary-light">
<tr>
<td>Type</td>
<td>Entity</td>
<td></td>
</tr>
</thead>
<tbody>
{% for relation in origins %}
{% include "entity_relation/table_item.html" with direction="from" %}
{% endfor %}
{% for relation in targets %}
{% include "entity_relation/table_item.html" with direction="to" %}
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
{% endwith %}
{% include "entity_relation/related_entities.html" with entity=actor %}

<div class="col-md-12 mt-2">
<h3>{% translate "Comments" %}</h3>
Expand Down
8 changes: 7 additions & 1 deletion colander/templates/artifact/controls.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,17 @@
{% translate "Edit" %}
</a>
{% endif %}
{% if "play" not in exclude and artifact.can_be_displayed %}
<a href="{% url "collect_artifact_view_view" pk=artifact.id %}" class="btn btn-primary {{ btn_class }}" target="_blank">
<i class="nf nf-fa-play"></i>
{% translate "Play" %}
</a>
{% endif %}
<a href="{% url "collect_artifact_download_view" pk=artifact.id %}" class="btn btn-primary text-white {{ btn_class }} {% if not artifact.has_been_processed %}disabled{% endif %}">
<i class="nf nf-fa-download"></i>
{% translate "Download" %}
</a>
{% if artifact.has_been_processed %}
{% if artifact.has_been_processed and "delete" not in exclude %}
<a href="{% url "collect_artifact_delete_view" pk=artifact.id %}" class="delete-entity-btn btn btn-danger text-white {{ btn_class }}">
<i class="nf nf-fa-trash"></i>
Delete
Expand Down
70 changes: 43 additions & 27 deletions colander/templates/artifact/details.html
Original file line number Diff line number Diff line change
Expand Up @@ -94,52 +94,52 @@ <h3>{% translate "Details" %}</h3>
<td>MD5</td>
<td>
{% if artifact.has_been_processed %}
{% include "icons/id_icon.html" with c="text-primary" %}
<span class="font-monospace">
{% include "icons/id_icon.html" with c="text-primary" %}
<span class="font-monospace">
{{ artifact.md5 }}
</span>
{% else %}
<span class="badge bg-info">Processing ...</span>
<span class="badge bg-info">Processing ...</span>
{% endif %}
</td>
</tr>
<tr>
<td>SHA1</td>
<td>
{% if artifact.has_been_processed %}
{% include "icons/id_icon.html" with c="text-primary" %}
<span class="font-monospace">
{% include "icons/id_icon.html" with c="text-primary" %}
<span class="font-monospace">
{{ artifact.sha1 }}
</span>
{% else %}
<span class="badge bg-info">Processing ...</span>
<span class="badge bg-info">Processing ...</span>
{% endif %}
</td>
</tr>
<tr>
<td>SHA256</td>
<td>
{% if artifact.has_been_processed %}
{% include "icons/id_icon.html" with c="text-primary" %}
<span class="font-monospace">
{% include "icons/id_icon.html" with c="text-primary" %}
<span class="font-monospace">
{{ artifact.sha256 }}
</span>
{% else %}
<span class="badge bg-info">Processing ...</span>
<span class="badge bg-info">Processing ...</span>
{% endif %}
</td>
</tr>
<tr>
<td>Signature</td>
<td>
{% if artifact.has_been_processed %}
{% if artifact.has_valid_signature %}
<span class="badge bg-success">Valid signature</span>
{% else %}
<span class="badge bg-danger">Incorrect signature (file potentially tempered)</span>
{% endif %}
{% if artifact.has_valid_signature %}
<span class="badge bg-success">Valid signature</span>
{% else %}
<span class="badge bg-danger">Incorrect signature (file potentially tempered)</span>
{% endif %}
{% else %}
<span class="badge bg-info">Processing ...</span>
<span class="badge bg-info">Processing ...</span>
{% endif %}
</td>
</tr>
Expand Down Expand Up @@ -195,27 +195,43 @@ <h3 class="card-title ">Artifact integrity check</h3>
</div>
<div class="card-footer text-center">
{% if artifact.has_been_processed %}
<a href="{% url "collect_cases_download_key_view" pk=artifact.case.id %}"
class="btn btn-sm btn-primary text-white">
Download case key
</a>
<a href="{% url "collect_artifact_download_view" pk=artifact.id %}" class="btn btn-sm btn-primary text-white">
Download artifact
</a>
<a href="{% url "collect_artifact_download_signature_view" pk=artifact.id %}"
class="btn btn-sm btn-primary text-white">
Download artifact signature
</a>
<a href="{% url "collect_cases_download_key_view" pk=artifact.case.id %}"
class="btn btn-sm btn-primary text-white">
Download case key
</a>
<a href="{% url "collect_artifact_download_view" pk=artifact.id %}" class="btn btn-sm btn-primary text-white">
Download artifact
</a>
<a href="{% url "collect_artifact_download_signature_view" pk=artifact.id %}"
class="btn btn-sm btn-primary text-white">
Download artifact signature
</a>
{% else %}
<span class="badge bg-info">Processing ...</span>
<span class="badge bg-info">Processing ...</span>
{% endif %}
</div>
</div>
</div>
</div>

<div class="row justify-content-center">
<div class="col-md-4 mt-2">
{% if artifact.type.short_name == "IMAGE" %}
<img src="{% url "collect_artifact_view_view" pk=artifact.id %}" class="img-thumbnail" alt="{{ artifact.name }}">
{% elif artifact.type.short_name == "VIDEO" %}
<video controls>
<source src="{% url "collect_artifact_view_view" pk=artifact.id %}">
Your browser does not support the video tag.
</video>
{% endif %}
</div>
</div>

<div class="row justify-content-center">
{% include "helpers/extra_attributes.html" with attributes=artifact.attributes %}

{% include "entity_relation/related_entities.html" with entity=observable %}

<div class="col-md-12 mt-2">
<h3>{% translate "Comments" %}</h3>
{% for comment in artifact.sorted_comments %}
Expand Down
2 changes: 2 additions & 0 deletions colander/templates/device/details.html
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ <h3>{% translate "Details" %}</h3>

{% include "helpers/extra_attributes.html" with attributes=device.attributes %}

{% include "entity_relation/related_entities.html" with entity=device %}

<div class="col-md-12 mt-2">
<h3>{% translate "Comments" %}</h3>
{% for comment in device.sorted_comments %}
Expand Down
26 changes: 26 additions & 0 deletions colander/templates/entity_relation/related_entities.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{% load i18n %}

{% with targets=entity.out_relations origins=entity.in_relations %}
{% if origins or targets %}
<div class="col-md-12 mt-2">
<h3>{% translate "Related entities" %}</h3>
<table class="table table-sm">
<thead class="bg-secondary-light">
<tr>
<td>Type</td>
<td>Entity</td>
<td></td>
</tr>
</thead>
<tbody>
{% for relation in origins %}
{% include "entity_relation/table_item.html" with direction="from" %}
{% endfor %}
{% for relation in targets %}
{% include "entity_relation/table_item.html" with direction="to" %}
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
{% endwith %}
Loading

0 comments on commit aeb5d19

Please sign in to comment.