Skip to content

Commit

Permalink
Add TheHive & MISP Export Functionality in Twisted DNS Batch #16
Browse files Browse the repository at this point in the history
  • Loading branch information
10063374 committed Nov 25, 2020
1 parent abd5ff3 commit b5031df
Show file tree
Hide file tree
Showing 12 changed files with 442 additions and 10 deletions.
18 changes: 17 additions & 1 deletion Watcher/Watcher/dns_finder/api.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from .models import DnsMonitored, DnsTwisted, Alert
from rest_framework import viewsets, permissions
from .serializers import AlertSerializer, DnsMonitoredSerializer, DnsTwistedSerializer
from .serializers import AlertSerializer, DnsMonitoredSerializer, DnsTwistedSerializer, ThehiveSerializer, MISPSerializer


# DnsMonitored Viewset
Expand Down Expand Up @@ -28,3 +28,19 @@ class AlertViewSet(viewsets.ModelViewSet):
permissions.IsAuthenticated
]
serializer_class = AlertSerializer


# Thehive Viewset
class ThehiveViewSet(viewsets.ModelViewSet):
permission_classes = [
permissions.IsAuthenticated
]
serializer_class = ThehiveSerializer


# MISP Viewset
class MISPViewSet(viewsets.ModelViewSet):
permission_classes = [
permissions.IsAuthenticated
]
serializer_class = MISPSerializer
23 changes: 23 additions & 0 deletions Watcher/Watcher/dns_finder/migrations/0006_auto_20201125_1725.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 3.1.3 on 2020-11-25 17:25

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('dns_finder', '0005_auto_20201123_1812'),
]

operations = [
migrations.AddField(
model_name='dnstwisted',
name='misp_event_id',
field=models.IntegerField(blank=True, null=True, unique=True),
),
migrations.AddField(
model_name='dnstwisted',
name='the_hive_case_id',
field=models.CharField(blank=True, max_length=100, null=True, unique=True),
),
]
2 changes: 2 additions & 0 deletions Watcher/Watcher/dns_finder/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ class DnsTwisted(models.Model):
domain_name = models.CharField(max_length=100, unique=True)
dns_monitored = models.ForeignKey(DnsMonitored, on_delete=models.CASCADE)
fuzzer = models.CharField(max_length=100, blank=True, null=True)
the_hive_case_id = models.CharField(max_length=100, unique=True, blank=True, null=True)
misp_event_id = models.IntegerField(unique=True, blank=True, null=True)
created_at = models.DateTimeField(default=timezone.now)

class Meta:
Expand Down
180 changes: 180 additions & 0 deletions Watcher/Watcher/dns_finder/serializers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,26 @@
from rest_framework import serializers
from django.conf import settings
from django.utils import timezone

from .models import Alert, DnsMonitored, DnsTwisted

from site_monitoring.models import Site
from site_monitoring.core import monitoring_init

import requests
from rest_framework.exceptions import NotFound, AuthenticationFailed

from thehive4py.api import TheHiveApi
from thehive4py.models import Case
from site_monitoring.thehive import create_observables, update_observables

from pymisp import ExpandedPyMISP, MISPEvent
from site_monitoring.misp import create_misp_tags, create_attributes, update_attributes

import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)


# DnsMonitored Serializer
class DnsMonitoredSerializer(serializers.ModelSerializer):
Expand All @@ -25,3 +45,163 @@ class AlertSerializer(serializers.ModelSerializer):
class Meta:
model = Alert
fields = '__all__'


# MISP Serializer
class MISPSerializer(serializers.Serializer):
id = serializers.IntegerField()

def save(self):
alert_id = self.validated_data['id']
alert = Alert.objects.get(pk=alert_id)

dns_twisted = DnsTwisted.objects.get(pk=alert.dns_twisted.pk)

# Getting IOCs related to the new twisted domain
if Site.objects.filter(domain_name=dns_twisted.domain_name):
already_in_monitoring = True
site = Site.objects.get(domain_name=dns_twisted.domain_name)
else:
already_in_monitoring = False
site = Site.objects.create(domain_name=dns_twisted.domain_name, rtir=-999999999)
monitoring_init(site)

site = Site.objects.get(pk=site.pk)

# We now hav the IOCs related to the domain, we can remove it from monitoring
if not already_in_monitoring:
Site.objects.filter(pk=site.pk).delete()

if site.misp_event_id is None:
site.misp_event_id = dns_twisted.misp_event_id

# Test MISP instance connection
try:
requests.get(settings.MISP_URL, verify=settings.MISP_VERIFY_SSL)
except requests.exceptions.SSLError as e:
print(str(timezone.now()) + " - ", e)
raise AuthenticationFailed("SSL Error: " + settings.MISP_URL)
except requests.exceptions.RequestException as e:
print(str(timezone.now()) + " - ", e)
raise NotFound("Not Found: " + settings.MISP_URL)

misp_api = ExpandedPyMISP(settings.MISP_URL, settings.MISP_KEY, settings.MISP_VERIFY_SSL)

if site.misp_event_id is not None:
# If the event already exist, then we update IOCs
update_attributes(misp_api, site)
else:
# If the event does not exist, then we create it

# Prepare MISP Event
event = MISPEvent()
event.distribution = 0
event.threat_level_id = 2
event.analysis = 0
event.info = "Suspicious domain name " + site.domain_name
event.tags = create_misp_tags(misp_api)

# Create MISP Event
print(str(timezone.now()) + " - " + 'Create MISP Event')
print('-----------------------------')
event = misp_api.add_event(event, pythonify=True)

# Store Event Id in database
DnsTwisted.objects.filter(pk=dns_twisted.pk).update(misp_event_id=event.id)
if Site.objects.filter(domain_name=dns_twisted.domain_name):
Site.objects.filter(pk=site.pk).update(misp_event_id=event.id)

# Create MISP Attributes
create_attributes(misp_api, event.id, site)

@property
def data(self):
# just return success dictionary. you can change this to your need, but i dont think output should be user data after password change
alert_id = self.validated_data['id']
alert = Alert.objects.get(pk=alert_id)
return {'id': alert_id, 'misp_event_id': DnsTwisted.objects.get(pk=alert.dns_twisted.pk).misp_event_id}


# Thehive Serializer
class ThehiveSerializer(serializers.Serializer):
id = serializers.IntegerField()

def save(self):
alert_id = self.validated_data['id']
alert = Alert.objects.get(pk=alert_id)

dns_twisted = DnsTwisted.objects.get(pk=alert.dns_twisted.pk)

# Getting IOCs related to the new twisted domain
if Site.objects.filter(domain_name=dns_twisted.domain_name):
already_in_monitoring = True
site = Site.objects.get(domain_name=dns_twisted.domain_name)
else:
already_in_monitoring = False
site = Site.objects.create(domain_name=dns_twisted.domain_name, rtir=-999999999)
monitoring_init(site)

site = Site.objects.get(pk=site.pk)

# We now hav the IOCs related to the domain, we can remove it from monitoring
if not already_in_monitoring:
Site.objects.filter(pk=site.pk).delete()

if site.the_hive_case_id is None:
site.the_hive_case_id = dns_twisted.the_hive_case_id

# Test The Hive instance connection
try:
requests.get(settings.THE_HIVE_URL)
except requests.exceptions.SSLError as e:
print(str(timezone.now()) + " - ", e)
raise AuthenticationFailed("SSL Error: " + settings.THE_HIVE_URL)
except requests.exceptions.RequestException as e:
print(str(timezone.now()) + " - ", e)
raise NotFound("Not Found: " + settings.THE_HIVE_URL)

hive_api = TheHiveApi(settings.THE_HIVE_URL, settings.THE_HIVE_KEY, cert=True)

if site.the_hive_case_id is not None:
# If the case already exist, then we update IOCs
update_observables(hive_api, site)
else:
# If the case does not exist, then we create it

# Prepare the case
case = Case(title='Suspicious domain name ' + site.domain_name,
owner=settings.THE_HIVE_CASE_ASSIGNEE,
severity=2,
tlp=2,
pap=2,
flag=False,
tags=['Watcher', 'Impersonation', 'Malicious Domain', 'Typosquatting'],
description='Suspicious domain name ' + site.domain_name)

# Create the case
print(str(timezone.now()) + " - " + 'Create Case')
print('-----------------------------')
response = hive_api.create_case(case)

if response.status_code == 201:
print(str(timezone.now()) + " - " + "OK")
case_id = response.json()['id']

# Save the case id in database
DnsTwisted.objects.filter(pk=dns_twisted.pk).update(the_hive_case_id=case_id)
if Site.objects.filter(domain_name=dns_twisted.domain_name):
Site.objects.filter(pk=site.pk).update(the_hive_case_id=case_id)

# Create all IOCs observables
create_observables(hive_api, case_id, site)
else:
print(str(timezone.now()) + " - " + 'ko: {}/{}'.format(response.status_code, response.text))
data = {'detail': response.json()['type'] + ": " + response.json()['message']}
raise serializers.ValidationError(data)

@property
def data(self):
# just return success dictionary. you can change this to your need, but i dont think output should be user data after password change
alert_id = self.validated_data['id']
alert = Alert.objects.get(pk=alert_id)
return {'id': alert_id, 'the_hive_case_id': DnsTwisted.objects.get(pk=alert.dns_twisted.pk).the_hive_case_id}
4 changes: 3 additions & 1 deletion Watcher/Watcher/dns_finder/urls.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
from rest_framework import routers
from .api import DnsMonitoredViewSet, DnsTwistedViewSet, AlertViewSet
from .api import DnsMonitoredViewSet, DnsTwistedViewSet, AlertViewSet, ThehiveViewSet, MISPViewSet

from .core import start_scheduler

router = routers.DefaultRouter()
router.register('api/dns_finder/dns_monitored', DnsMonitoredViewSet, 'dns_monitored')
router.register('api/dns_finder/dns_twisted', DnsTwistedViewSet, 'dns_twisted')
router.register('api/dns_finder/alert', AlertViewSet, 'alert')
router.register('api/dns_finder/thehive', ThehiveViewSet, 'thehive')
router.register('api/dns_finder/misp', MISPViewSet, 'misp')

urlpatterns = router.urls

Expand Down
36 changes: 35 additions & 1 deletion Watcher/Watcher/frontend/src/actions/DnsFinder.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import {
DELETE_DNS_MONITORED,
ADD_DNS_MONITORED,
PATCH_DNS_MONITORED,
UPDATE_DNS_FINDER_ALERT
UPDATE_DNS_FINDER_ALERT,
EXPORT_THE_HIVE_DNS_FINDER,
EXPORT_MISP_DNS_FINDER
} from "./types";
import {createMessage, returnErrors} from "./messages";
import {tokenConfig} from "./auth";
Expand Down Expand Up @@ -103,4 +105,36 @@ export const updateAlertStatus = (id, status) => (dispatch, getState) => {
.catch(err =>
dispatch(returnErrors(err.response.data, err.response.status))
);
};

// EXPORT TO THE HIVE
export const exportToTheHive = (site) => (dispatch, getState) => {
axios
.post(`/api/dns_finder/thehive/`, site, tokenConfig(getState))
.then(res => {
dispatch(createMessage({add: `Twisted DNS Exported to Thehive`}));
dispatch({
type: EXPORT_THE_HIVE_DNS_FINDER,
payload: res.data
});
})
.catch(err =>
dispatch(returnErrors(err.response.data, err.response.status))
);
};

// EXPORT TO MISP
export const exportToMISP = (site) => (dispatch, getState) => {
axios
.post(`/api/dns_finder/misp/`, site, tokenConfig(getState))
.then(res => {
dispatch(createMessage({add: `Twisted DNS Exported to MISP`}));
dispatch({
type: EXPORT_MISP_DNS_FINDER,
payload: res.data
});
})
.catch(err =>
dispatch(returnErrors(err.response.data, err.response.status))
);
};
4 changes: 3 additions & 1 deletion Watcher/Watcher/frontend/src/actions/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,6 @@ export const GET_DNS_FINDER_ALERTS = "GET_DNS_FINDER_ALERTS";
export const DELETE_DNS_MONITORED = "DELETE_DNS_MONITORED";
export const ADD_DNS_MONITORED = "ADD_DNS_MONITORED";
export const PATCH_DNS_MONITORED = "PATCH_DNS_MONITORED";
export const UPDATE_DNS_FINDER_ALERT = "UPDATE_DNS_FINDER_ALERT";
export const UPDATE_DNS_FINDER_ALERT = "UPDATE_DNS_FINDER_ALERT";
export const EXPORT_THE_HIVE_DNS_FINDER = "EXPORT_THE_HIVE_DNS_FINDER";
export const EXPORT_MISP_DNS_FINDER = "EXPORT_MISP_DNS_FINDER";
Loading

0 comments on commit b5031df

Please sign in to comment.