Skip to content

Commit

Permalink
Add the ability to import events and threats from threatr
Browse files Browse the repository at this point in the history
  • Loading branch information
U039b committed Feb 23, 2024
1 parent 0d92137 commit 96ccdfb
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 30 deletions.
5 changes: 3 additions & 2 deletions colander/core/artifact_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ def import_file_as_artifact(input_file, owner, case, artifact_type, name, mimety
sha1=sha1,
sha256=sha256,
md5=md5,
size_in_bytes=size_in_bytes,
file=File(file=input_file, name=name)
size_in_bytes=size_in_bytes
)
artifact.save()
artifact.file = File(file=input_file, name=name)
artifact.save()
return artifact
4 changes: 3 additions & 1 deletion colander/core/management/commands/data/event_types.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@
{"short_name": "COMPROMISE", "name": "Compromise", "description": "", "svg_icon": "", "nf_icon": "nf-mdi-disk_alert"},
{"short_name": "INFECTION", "name": "Infection", "description": "", "svg_icon": "", "nf_icon": "nf-mdi-disk_alert"},
{"short_name": "ALERT", "name": "Alert", "description": "", "svg_icon": "", "nf_icon": "nf-oct-alert"},
{"short_name": "HIT", "name": "Hit", "description": "", "svg_icon": "", "nf_icon": "nf-mdi-white_balance_sunny"}
{"short_name": "AV_DETECTION", "name": "AntiVirus detection", "description": "", "svg_icon": "", "nf_icon": "nf-oct-alert"},
{"short_name": "HIT", "name": "Hit", "description": "", "svg_icon": "", "nf_icon": "nf-mdi-white_balance_sunny"},
{"short_name": "PASSIVE_DNS", "name": "Passive DNS", "description": "", "svg_icon": "", "nf_icon": "nf-mdi-white_balance_sunny"}
]
97 changes: 83 additions & 14 deletions colander/core/rest/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@

from colander.core import datasets
from colander.core.graph.serializers import GraphRelationSerializer
from colander.core.models import Case, EntityRelation, Entity, ObservableType, Observable
from colander.core.models import Case, EntityRelation, Entity, ObservableType, Observable, Threat, ThreatType, Event, \
EventType
from colander.core.rest.serializers import DetailedEntitySerializer
from colander.core.views.views import get_active_case

Expand Down Expand Up @@ -46,9 +47,6 @@ def get_queryset(self):
def perform_create(self, serializer):
case_id = self.request.data.pop("case_id")

print( "perform_create data", self.request.data )
print( "perform_create validated_data", serializer.validated_data )

# Bug-0004 : 'upgrade' obj_form and obj_to value to their respective concrete class
abstract_from = serializer.validated_data['obj_from']
abstract_to = serializer.validated_data['obj_to']
Expand Down Expand Up @@ -99,6 +97,16 @@ def get_threatr_entity_type(entity):
return Observable, ObservableType.objects.get(short_name=entity['type']['short_name'])
except Exception:
return None, None
if entity['super_type']['short_name'] == 'THREAT':
try:
return Threat, ThreatType.objects.get(short_name=entity['type']['short_name'])
except Exception:
return None, None
if entity['super_type']['short_name'] == 'EVENT':
try:
return Event, EventType.objects.get(short_name=entity['type']['short_name'])
except Exception:
return None, None
return None, None


Expand All @@ -115,6 +123,34 @@ def update_or_create_entity(entity: dict, model: type, entity_type, case: Case,
'source_url': entity.get('source_url'),
}
)
if hasattr(obj, 'attributes'):
obj_attributes = obj.attributes
if obj_attributes:
obj.attributes.update(entity.get('attributes'))
obj.save()
elif entity.get('attributes'):
obj.attributes = entity.get('attributes')
obj.save()
return obj, created


def update_or_create_event(entity: dict, entity_type, root_entity, case: Case, owner):
obj, created = Event.objects.update_or_create(
type=entity_type,
name=entity.get('name'),
case=case,
defaults={
'owner': owner,
'first_seen': entity.get('first_seen'),
'last_seen': entity.get('last_seen'),
'count': entity.get('count'),
'description': entity.get('description'),
'source_url': entity.get('source_url'),
}
)
if root_entity.super_type == 'Observable':
obj.involved_observables.add(root_entity)
obj.save()
obj_attributes = obj.attributes
if obj_attributes:
obj.attributes.update(entity.get('attributes'))
Expand All @@ -125,9 +161,32 @@ def update_or_create_entity(entity: dict, model: type, entity_type, case: Case,
return obj, created


def __create_immutable_relation(obj_from, obj_to, relation):
attr_name = relation.get('name').strip().lower().replace(' ', '_')
if hasattr(obj_to, attr_name) and not getattr(obj_to, attr_name):
try:
setattr(obj_to, attr_name, obj_from)
obj_to.save()
return True
except:
pass
elif hasattr(obj_from, attr_name) and not getattr(obj_from, attr_name):
try:
setattr(obj_from, attr_name, obj_to)
obj_from.save()
return True
except:
pass
return False


def update_or_create_entity_relation(obj_from, obj_to, relation, case: Case, owner):
obj = None
created = True

if __create_immutable_relation(obj_from, obj_to, relation):
return obj, created

existing_relations = EntityRelation.objects.filter(
name=relation.get('name'),
case=case,
Expand Down Expand Up @@ -162,19 +221,29 @@ def import_entity_from_threatr(request):
case_id = data.get('case_id', None)
root = data.get('root', None)
entity = data.get('entity', None)
event = data.get('event', None)
relation = data.get('relation', None)
case = Case.objects.get(pk=case_id)
if root and entity and relation:
if root :
root_model, root_type = get_threatr_entity_type(root)
entity_model, entity_type = get_threatr_entity_type(entity)
root_obj, _ = update_or_create_entity(root, root_model, root_type, case, request.user)
entity_obj, _ = update_or_create_entity(entity, entity_model, entity_type, case, request.user)
if relation.get('obj_from') == root.get('id'):
obj_from = root_obj
obj_to = entity_obj
else:
obj_from = entity_obj
obj_to = root_obj
relation_obj, _ = update_or_create_entity_relation(obj_from, obj_to, relation, case, request.user)
if event:
_, entity_type = get_threatr_entity_type(event)
try:
update_or_create_event(event, entity_type, root_obj, case, request.user)
except:
return JsonResponse({'status': -1})
elif entity and relation:
entity_model, entity_type = get_threatr_entity_type(entity)
if not entity_model:
return JsonResponse({'status': -1})
entity_obj, _ = update_or_create_entity(entity, entity_model, entity_type, case, request.user)
if relation.get('obj_from') == root.get('id'):
obj_from = root_obj
obj_to = entity_obj
else:
obj_from = entity_obj
obj_to = root_obj
update_or_create_entity_relation(obj_from, obj_to, relation, case, request.user)
return JsonResponse({'status': 0})
return JsonResponse({'status': -1})
4 changes: 3 additions & 1 deletion colander/core/views/investigate_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,10 @@ def investigate_search_view(request):
wait = response.status_code == 201
if response.status_code == 200:
results = response.json()
entity_super_type = results.get('root_entity').get('super_type').get('name')
root_entity = results.get('root_entity')
entity_super_type = root_entity.get('super_type').get('name')
if entity_super_type in colander_models:
types_to_display[root_entity.get('super_type').get('short_name')] = root_entity.get('super_type')
model = colander_models[entity_super_type]
if model in color_scheme:
results['root_entity']['color'] = color_scheme[model]
Expand Down
33 changes: 29 additions & 4 deletions colander/templates/pages/investigate/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ <h3>Related observables</h3>
</tr>
</thead>
<tbody>
{% if root_entity %}
{% include "pages/investigate/m_observable_table_row.html" with observable=root_entity %}
{% endif %}
{% for entity in results.entities.values|dictsort:"type.name" %}
{% if entity.super_type.short_name == "OBSERVABLE" %}
{% include "pages/investigate/m_observable_table_row.html" with observable=entity %}
Expand All @@ -79,6 +82,7 @@ <h3>Related events</h3>
<th>Type</th>
<th>Event</th>
<th>Time period</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
Expand All @@ -91,6 +95,13 @@ <h3>Related events</h3>
</td>
<td>{{ entity.name }}</td>
<td>{{ entity.first_seen }} - {{ entity.last_seen }} ({{ entity.count }} time{{ entity.count|pluralize }})</td>
<td class="text-end">
<button class="btn btn-sm bg-primary text-white investigate-add-entity-btn"
data-bs-toggle="tooltip" data-bs-title="Import to the current case"
type="button" data-obj-id="{{ entity.id }}">
<i class="nf nf-fa-plus"></i>
</button>
</td>
</tr>
{% endfor %}
</tbody>
Expand Down Expand Up @@ -162,14 +173,27 @@ <h2>No results found.</h2>
const data = JSON.parse($('#results-data').text());
const id = $(this).attr('data-obj-id');
const root = data['root_entity'];
const entity = data['entities'][id];
var relation = undefined
data['relations'].forEach(function (r){
if((r.obj_from == root.id && r.obj_to == entity.id) || (r.obj_to == root.id && r.obj_from == entity.id)) {
let relation = undefined;
let entity = undefined;
let event = undefined;
data['events'].forEach(function (e) {
if(e.id === id){
event = e;
event.super_type = {name:'Event', short_name:'EVENT'}
}
return
})
if (id !== root['id']) {
entity = data['entities'][id];
if(entity !== undefined) {
data['relations'].forEach(function (r) {
if ((r.obj_from == root.id && r.obj_to == entity.id) || (r.obj_to == root.id && r.obj_from == entity.id)) {
relation = r;
return
}
})
}
}

const response = await fetch(`/rest/threatr_entity`, {
method: 'POST',
Expand All @@ -182,6 +206,7 @@ <h2>No results found.</h2>
'case_id': "{{ contextual_case.id }}",
'root': root,
'entity': entity,
'event': event,
'relation': relation
})
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,22 @@
<div class="mb-2">
{% include "icons/pills.html" with elt=entity %}
{% if entity.name %}
<samp>{{ entity.name }}</samp> (<i>{{ entity.type.name }}</i>)
<samp>{{ entity.name }}</samp> (<i>{{ entity.type.name }}</i>)
{% else %}
<samp>{{ entity.value }}</samp> (<i>{{ entity.type.name }}</i>)
{% endif %}
{% if entity.attributes.category or entity.attributes.tags %}
<div>
{% if entity.attributes.category %}
{% for t in entity.attributes.category|split:"," %}
<span class="badge bg-info"><i class="nf nf-fa-tag"></i> {{ t|striptags }}</span>
{% endfor %}
{% endif %}
{% if entity.attributes.tags %}
{% if entity.attributes.category %}
{% for t in entity.attributes.category|split:"," %}
<span class="badge bg-info"><i class="nf nf-fa-tag"></i> {{ t|striptags }}</span>
{% endfor %}
{% endif %}
{% if entity.attributes.tags %}
{% for t in entity.attributes.tags|split:"," %}
<span class="badge bg-dark"><i class="nf nf-fa-tag"></i> {{ t }}</span>
{% endfor %}
{% endif %}
{% endif %}
</div>
{% endif %}
</div>
Expand All @@ -43,6 +43,15 @@
{% endfor %}
{% endif %}
</ul>
{% if entity.super_type.short_name in "THREAT" %}
<div class="text-end">
<button class="btn btn-sm bg-primary text-white investigate-add-entity-btn"
data-bs-toggle="tooltip" data-bs-title="Import to the current case"
type="button" data-obj-id="{{ entity.id }}">
<i class="nf nf-fa-plus"></i>
</button>
</div>
{% endif %}
{% endif %}


1 change: 1 addition & 0 deletions compose/local/django/start
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ set -o nounset


python manage.py migrate --skip-checks
python manage.py insert_default_data --skip-checks

python manage.py runserver_plus 0.0.0.0:8000
1 change: 1 addition & 0 deletions compose/production/django/start
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@ if compress_enabled; then
fi

python manage.py migrate --skip-checks
python manage.py insert_default_data --skip-checks
/usr/local/bin/gunicorn config.wsgi --bind 0.0.0.0:5000 --chdir=/app

0 comments on commit 96ccdfb

Please sign in to comment.