New Feature : It is now possible to import IoCs from an added MISP instance.
This commit is contained in:
parent
033d751e31
commit
24be446598
@ -39,6 +39,9 @@
|
||||
<li class="menu-item">
|
||||
<span @click="$router.push('/iocs/manage')">Manage IOCs</span>
|
||||
</li>
|
||||
<li class="menu-item">
|
||||
<span @click="$router.push('/iocs/misp')">MISP IOCs</span>
|
||||
</li>
|
||||
<li class="menu-item">
|
||||
<span @click="$router.push('/iocs/search')">Search IOCs</span>
|
||||
</li>
|
||||
@ -95,4 +98,4 @@
|
||||
.fade-leave-active {
|
||||
opacity: 0
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
@ -650,4 +650,57 @@ h4, h5 {
|
||||
|
||||
.upper {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
|
||||
/*** MISP CSS ***/
|
||||
|
||||
.misp-form {
|
||||
/* Using CSS Grid to lay out the elements in two-dimensions: */
|
||||
display: grid;
|
||||
/* specifying a 0.2em gutter/gap between adjacent elements: */
|
||||
gap: 0.2em;
|
||||
overflow:auto;
|
||||
grid-template-columns: 10em 0.5em 1fr;
|
||||
width: 100%;
|
||||
border:0.05rem solid #cecece;
|
||||
border-radius:.1rem;
|
||||
margin-bottom:.4rem;
|
||||
padding:.4rem;
|
||||
}
|
||||
|
||||
.misp-label {
|
||||
/* placing all <label> elements in the grid column 1 (the first): */
|
||||
grid-column: 1;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.misp-name {
|
||||
font-size: 1rem;
|
||||
font-family: "Roboto-Bold";
|
||||
color: #484848;
|
||||
}
|
||||
|
||||
.misp-name:disabled {
|
||||
border-style: none;
|
||||
color:inherit;
|
||||
background-color: inherit;
|
||||
padding: unset;
|
||||
}
|
||||
|
||||
.misp-input {
|
||||
grid-column: 3;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.misp-input:disabled {
|
||||
border-style: none;
|
||||
color:inherit;
|
||||
background-color: inherit;
|
||||
padding: unset;
|
||||
}
|
||||
|
||||
.misp-button {
|
||||
/* positioning the <button> element in the grid-area identified
|
||||
by the name of 'submit': */
|
||||
grid-area: submit;
|
||||
}
|
||||
|
@ -34,6 +34,12 @@ const routes = [
|
||||
component: () => import('../views/iocs-manage.vue'),
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: '/iocs/misp',
|
||||
name: 'iocs-manage',
|
||||
component: () => import('../views/iocs-misp.vue'),
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: '/iocs/search',
|
||||
name: 'iocs-search',
|
||||
|
402
app/backend/src/views/iocs-misp.vue
Normal file
402
app/backend/src/views/iocs-misp.vue
Normal file
@ -0,0 +1,402 @@
|
||||
<template>
|
||||
<div class="backend-content" id="content">
|
||||
<div class="column col-6 col-xs-12">
|
||||
<h3 class="s-title">Manage MISP IOCs</h3>
|
||||
<div>
|
||||
Here you can add IOCs from your MISP instances. To do so, you first need to fullfil the "Add a new MISP instance" form. Then go to the "Existing instances" tab and scroll to the desired instance.
|
||||
Finally, just fill the parameters as you wish and click on the "Import IOCs" button. All the IOCs that are not already in the database will be added.
|
||||
Note that only IOCs (attributes) that belongs to the "Network activity" category will be inserted.
|
||||
</div>
|
||||
<ul class="tab tab-block">
|
||||
<li class="tab-item">
|
||||
<a href="#" v-on:click="switch_tab('addmisp')" v-bind:class="{ active: tabs.addmisp }">Add instance</a>
|
||||
</li>
|
||||
<li class="tab-item">
|
||||
<a href="#" v-on:click="switch_tab('instances')" v-bind:class="{ active: tabs.instances }">Existing instances</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div v-if="tabs.addmisp">
|
||||
<h5>Add a new MISP instance</h5>
|
||||
<div class="misp-form">
|
||||
<label class="misp-label">Name</label><span>:</span>
|
||||
<input class="misp-input" type="text" ref="misp_name" placeholder="Enter the name to give to your MISP instance" v-model="mispinst.name" required>
|
||||
<label class="misp-label">URL</label><span>:</span>
|
||||
<input class="misp-input" type="text" ref="misp_url" placeholder="Enter your MISP instance URL" v-model="mispinst.url" required>
|
||||
<label class="misp-label">API key</label><span>:</span>
|
||||
<input class="misp-input" type="text" ref="misp_key" placeholder="Enter the API key to use" v-model="mispinst.key" required>
|
||||
<label class="misp-label">Verify certificate</label><span>:</span>
|
||||
<div style="flex:50%"><input class="misp-input" style="margin-right: 5px;" type="checkbox" id="checkbox" v-model="mispinst.ssl"><label for="checkbox">{{ mispinst.ssl }}</label></div>
|
||||
</div>
|
||||
<button class="btn-primary btn col-12" v-on:click="add_misp_instance()">Add MISP instance</button>
|
||||
<div class="form-group" v-if="addedInstance.length>0">
|
||||
<div class="toast toast-success">
|
||||
✓ MISP instance added successfully.
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="errorsInstance.length>0">
|
||||
<div class="form-group">
|
||||
<div class="toast toast-error">
|
||||
✗ MISP instance not added, see details below.
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>URL</th>
|
||||
<th>API key</th>
|
||||
<th>Reason</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="e in errorsInstance" v-bind:key="e.name">
|
||||
<td>{{ e.name }}</td>
|
||||
<td>{{ e.url }}</td>
|
||||
<td>{{ e.apikey }}</td>
|
||||
<td>{{ e.message }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" v-if="tabs.instances">
|
||||
<div v-if="mispInstances.length>0">
|
||||
<div v-for="r in mispInstances" v-bind:key="r.id">
|
||||
<div style="position: relative">
|
||||
<input class="misp-name" :id="r.id + 'name'" v-bind:value="r.name" v-model="r.name" disabled="disabled" required>
|
||||
<button class="btn btn-sm" :id="r.id + 'edit'" :ref="r.id + 'edit'" v-on:click="edit_misp_instance(r)" style="position: absolute; right:120px;">Edit instance</button>
|
||||
<button class="btn btn-sm" :id="r.id + 'delete'" :ref="r.id + 'delete'" v-on:click="remove_or_cancel_edit_misp_instance(r)" style="position: absolute; right:0;">Delete instance</button>
|
||||
</div>
|
||||
<div class="misp-form">
|
||||
<label class="misp-label">URL</label><span>:</span>
|
||||
<input class="misp-input" :id="r.id + 'insturl'" v-bind:value="r.url" v-model="r.url" disabled="disabled" required>
|
||||
<label class="misp-label">API Key</label><span>:</span>
|
||||
<input class="misp-input" :id="r.id + 'instkey'" v-bind:value="r.apikey" v-model="r.apikey" disabled="disabled" required>
|
||||
<label class="misp-label">Verify certificate</label><span>:</span>
|
||||
<div style="flex:50%;"><input class="misp-input" :id="r.id + 'check'" type="checkbox" v-bind:value="r.verifycert" v-model="r.verifycert" style="visibility: hidden; width: 0;"><label v-bind:value="r.verifycert">{{ r.verifycert == 0 ? 'false' : 'true'}}</label></div>
|
||||
<label class="misp-label">Limit</label><span>:</span>
|
||||
<input class="misp-input" type="number" step="1" min="0" :id="r.id + 'limit'" placeholder="Enter the maximum number of IOCs to retrieve">
|
||||
<label class="misp-label">Page index</label><span>:</span>
|
||||
<input class="misp-input" type="number" step="1" min="0" :id="r.id + 'page'" placeholder="Enter the page index where to start retrieving IOCs">
|
||||
<button class="btn btn-sm" :id="r.id + 'import'" v-on:click="import_misp_iocs(r)">Import IOCs</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" v-if="addedInstance.length>0">
|
||||
<div class="toast toast-success">
|
||||
✓ MISP instance edited successfully.
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="errorsInstance.length>0">
|
||||
<div class="form-group">
|
||||
<div class="toast toast-error">
|
||||
✗ MISP instance count not be edited, see details below.
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>URL</th>
|
||||
<th>API key</th>
|
||||
<th>Reason</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="e in errorsInstance" v-bind:key="e.name">
|
||||
<td>{{ e.name }}</td>
|
||||
<td>{{ e.url }}</td>
|
||||
<td>{{ e.apikey }}</td>
|
||||
<td>{{ e.message }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<p>No MISP instance found. Click the "Add" button to add new MISP instance.</p>
|
||||
<button class="btn btn-sm" v-on:click="switch_tab('addmisp')" v-bind:class="{ active: tabs.addmisp }">Add a new instance</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" v-if="imported.length>0">
|
||||
<div class="toast toast-success">
|
||||
✓ {{imported.length}} IOC<span v-if="imported.length>1">s</span> imported successfully.
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Indicator</th>
|
||||
<th>Type</th>
|
||||
<th>Tag</th>
|
||||
<th>TLP</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="i in imported" v-bind:key="i.ioc">
|
||||
<td>{{ i.ioc }}</td>
|
||||
<td>{{ i.type }}</td>
|
||||
<td>{{ i.tag }}</td>
|
||||
<td>{{ i.tlp }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="errors.length>0">
|
||||
<div class="form-group">
|
||||
<div class="toast toast-error">
|
||||
✗ {{errors.length}} IOC<span v-if="errors.length>1">s</span> not imported, see details below.
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Indicator</th>
|
||||
<th>Importation error</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="e in errors" v-bind:key="e.ioc">
|
||||
<td>{{ e.ioc }}</td>
|
||||
<td>{{ e.message }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="type_tag_error==true">
|
||||
<div class="form-group">
|
||||
<div class="toast toast-error">
|
||||
✗ IOC(s) not imported, see details below.
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="empty">
|
||||
<p class="empty-title h5">Please select a tag and a type.</p>
|
||||
<p class="empty-subtitle">If different IOCs types, select "Unknown (regex parsing)".</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'managemisp',
|
||||
data() {
|
||||
return {
|
||||
errors:[],
|
||||
imported:[],
|
||||
errorsInstance:[],
|
||||
addedInstance:[],
|
||||
mispinst:{name:'', url:'',key:'', ssl:false},
|
||||
mispInstances:[],
|
||||
tabs: { "addmisp" : true, "instances" : false },
|
||||
jwt:"",
|
||||
type_tag_error: false
|
||||
}
|
||||
},
|
||||
props: { },
|
||||
methods: {
|
||||
add_misp_instance: function()
|
||||
{
|
||||
this.errors = [];
|
||||
this.imported = [];
|
||||
this.errorsInstance = []
|
||||
this.addedInstance = []
|
||||
if (this.mispinst["name"] != "" && this.mispinst["url"] != "" && this.mispinst["key"] != "")
|
||||
{
|
||||
axios.post(`/api/misp/add`, { data: { instance: this.mispinst } }, { headers: {'X-Token': this.jwt} }).then(response => {
|
||||
if(response.data.status){
|
||||
this.addedInstance.push(response.data);
|
||||
} else if (response.data.message){
|
||||
this.errorsInstance.push(response.data);
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
}
|
||||
else
|
||||
{
|
||||
console.log(this.mispinst["name"]);
|
||||
console.log(this.mispinst["url"]);
|
||||
console.log(this.mispinst["key"]);
|
||||
}
|
||||
},
|
||||
edit_misp_instance (elem)
|
||||
{
|
||||
if (document.getElementById(elem.id+'insturl').disabled == false)
|
||||
{ // The misp instance was in edit mode
|
||||
|
||||
this.errors = [];
|
||||
this.imported = [];
|
||||
this.errorsInstance = []
|
||||
this.addedInstance = []
|
||||
if (elem["name"] != "" && elem["url"] != "" && elem["key"] != "")
|
||||
{
|
||||
axios.post(`/api/misp/edit`, { data: { instance: elem } }, { headers: {'X-Token': this.jwt} }).then(response => {
|
||||
if(response.data.status){
|
||||
this.addedInstance.push(response.data);
|
||||
} else if (response.data.message){
|
||||
this.errorsInstance.push(response.data);
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
}
|
||||
this.cancel_edit_misp(elem);
|
||||
}
|
||||
else
|
||||
{ // the misp instance should enter in edit mode
|
||||
|
||||
document.getElementById(elem.id+'edit').innerText = 'Validate edit';
|
||||
document.getElementById(elem.id+'delete').innerText = 'Cancel edit';
|
||||
document.getElementById(elem.id+'name').disabled = false;
|
||||
document.getElementById(elem.id+'insturl').disabled = false;
|
||||
document.getElementById(elem.id+'instkey').disabled = false;
|
||||
document.getElementById(elem.id+'limit').disabled = true;
|
||||
document.getElementById(elem.id+'page').disabled = true;
|
||||
document.getElementById(elem.id+'import').disabled = true;
|
||||
document.getElementById(elem.id+'check').style = "margin-right: 5px;";
|
||||
}
|
||||
},
|
||||
remove_or_cancel_edit_misp_instance(elem)
|
||||
{
|
||||
if (document.getElementById(elem.id+'insturl').disabled == false)
|
||||
{ // The misp instance was in edit mode
|
||||
|
||||
this.cancel_edit_misp(elem)
|
||||
}
|
||||
else
|
||||
{ // The misp instance should be delete
|
||||
|
||||
axios.get(`/api/misp/delete/${elem.id}`, { timeout: 10000, headers: {'X-Token': this.jwt} })
|
||||
.then(response => {
|
||||
if(response.data.status){
|
||||
this.mispInstances = this.mispInstances.filter(function(el) { return el != elem; });
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
}
|
||||
},
|
||||
cancel_edit_misp(elem)
|
||||
{
|
||||
document.getElementById(elem.id+'edit').innerText = 'Edit instance';
|
||||
document.getElementById(elem.id+'delete').innerText = 'Delete instance';
|
||||
document.getElementById(elem.id+'name').disabled = true;
|
||||
document.getElementById(elem.id+'insturl').disabled = true;
|
||||
document.getElementById(elem.id+'instkey').disabled = true;
|
||||
document.getElementById(elem.id+'limit').disabled = false;
|
||||
document.getElementById(elem.id+'page').disabled = false;
|
||||
document.getElementById(elem.id+'import').disabled = false;
|
||||
document.getElementById(elem.id+'check').style = "visibility: hidden; width: 0;";
|
||||
},
|
||||
import_misp_iocs(elem)
|
||||
{
|
||||
this.errors = [];
|
||||
this.imported = [];
|
||||
this.errorsInstance = []
|
||||
this.addedInstance = []
|
||||
|
||||
axios.post(`/api/misp/get_iocs`, { data: { misp_id: elem.id, page: document.getElementById(elem.id+'page').value, limit: document.getElementById(elem.id+'limit').value } }, { headers: {'X-Token': this.jwt} })
|
||||
.then(response => {
|
||||
if(response.data.results.length>0){
|
||||
console.log(response.data.results);
|
||||
response.data.results.forEach(ioc => {
|
||||
this.import_ioc(ioc["tag"], ioc["type"], ioc["tlp"], ioc["value"], elem.name + "_" + elem.id);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
console.log(response);
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
},
|
||||
import_ioc: function(tag, type, tlp, ioc, source) {
|
||||
if (ioc != "" && ioc.slice(0,1) != "#"){
|
||||
if("alert " != ioc.slice(0,6)) {
|
||||
ioc = ioc.trim()
|
||||
ioc = ioc.replace(" ", "")
|
||||
ioc = ioc.replace("[", "")
|
||||
ioc = ioc.replace("]", "")
|
||||
ioc = ioc.replace("\\", "")
|
||||
ioc = ioc.replace("(", "")
|
||||
ioc = ioc.replace(")", "")
|
||||
}
|
||||
|
||||
let finalioc = {ioc_tag: tag, ioc_type: type, ioc_tlp: tlp, ioc_value: ioc, ioc_source: "misp_" + source}
|
||||
|
||||
axios.post(`/api/ioc/add_post`, { data: { ioc: finalioc } }, { headers: {'X-Token': this.jwt} })
|
||||
.then(response => {
|
||||
if(response.data.status){
|
||||
this.imported.push(response.data);
|
||||
} else if (response.data.message){
|
||||
this.errors.push(response.data);
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
}
|
||||
},
|
||||
get_misp_instances()
|
||||
{
|
||||
this.errorsInstance = []
|
||||
this.addedInstance = []
|
||||
this.mispInstances = []
|
||||
axios.get(`/api/misp/get_all`, { timeout: 10000, headers: {'X-Token': this.jwt} })
|
||||
.then(response => {
|
||||
console.log(response.data);
|
||||
if(response.data.results.length>0){
|
||||
this.mispInstances = [].concat(this.mispInstances, response.data.results);
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
},
|
||||
switch_tab: function(tab) {
|
||||
this.errors = []
|
||||
this.errorsInstance = []
|
||||
this.addedInstance = []
|
||||
|
||||
Object.keys(this.tabs).forEach(key => {
|
||||
if( key == tab ){
|
||||
this.tabs[key] = true
|
||||
if (key == "instances")
|
||||
{
|
||||
this.get_misp_instances();
|
||||
}
|
||||
} else {
|
||||
this.tabs[key] = false
|
||||
}
|
||||
});
|
||||
},
|
||||
get_jwt(){
|
||||
axios.get(`/api/get-token`, { timeout: 10000 })
|
||||
.then(response => {
|
||||
if(response.data.token){
|
||||
this.jwt = response.data.token
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
}
|
||||
},
|
||||
created: function() {
|
||||
this.get_jwt();
|
||||
this.get_misp_instances();
|
||||
if (this.mispInstances.length>0)
|
||||
{
|
||||
this.tabs["addmisp"] = false;
|
||||
this.tabs["instances"] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
</script>
|
@ -2,6 +2,7 @@ ipwhois
|
||||
M2Crypto
|
||||
pyOpenSSL
|
||||
pydig
|
||||
pymisp
|
||||
netaddr
|
||||
pyyaml
|
||||
flask
|
||||
@ -14,4 +15,4 @@ wifi
|
||||
qrcode
|
||||
netifaces
|
||||
weasyprint
|
||||
python-whois
|
||||
python-whois
|
||||
|
@ -17,3 +17,14 @@ CREATE TABLE "whitelist" (
|
||||
"added_on" INTEGER NOT NULL,
|
||||
PRIMARY KEY("id" AUTOINCREMENT)
|
||||
);
|
||||
|
||||
CREATE TABLE "mispinstance" (
|
||||
"id" INTEGER UNIQUE,
|
||||
"name" TEXT,
|
||||
"url" TEXT NOT NULL,
|
||||
"apikey" TEXT NOT NULL,
|
||||
"verifycert" INTEGER NOT NULL DEFAULT 0,
|
||||
"source" TEXT NOT NULL,
|
||||
"added_on" NUMERIC NOT NULL,
|
||||
PRIMARY KEY("id" AUTOINCREMENT)
|
||||
);
|
||||
|
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from flask import Blueprint, jsonify, Response
|
||||
from flask import Blueprint, jsonify, Response, request
|
||||
from app.decorators import require_header_token, require_get_token
|
||||
from app.classes.iocs import IOCs
|
||||
|
||||
@ -23,6 +23,20 @@ def add(ioc_type, ioc_tag, ioc_tlp, ioc_value):
|
||||
return jsonify(res)
|
||||
|
||||
|
||||
@ioc_bp.route('/add_post', methods=['POST'])
|
||||
@require_header_token
|
||||
def add_post():
|
||||
"""
|
||||
Parse and add an IOC to the database using the post method.
|
||||
:return: status of the operation in JSON
|
||||
"""
|
||||
|
||||
data = json.loads(request.data)
|
||||
ioc = data["data"]["ioc"]
|
||||
res = IOCs.add(ioc["ioc_type"], ioc["ioc_tag"], ioc["ioc_tlp"], ioc["ioc_value"], ioc["ioc_source"])
|
||||
return jsonify(res)
|
||||
|
||||
|
||||
@ioc_bp.route('/delete/<ioc_id>', methods=['GET'])
|
||||
@require_header_token
|
||||
def delete(ioc_id):
|
||||
|
76
server/backend/app/blueprints/misp.py
Normal file
76
server/backend/app/blueprints/misp.py
Normal file
@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from flask import Blueprint, jsonify, Response, request
|
||||
from app.decorators import require_header_token, require_get_token
|
||||
from app.classes.mispobj import MISPObj
|
||||
|
||||
import json
|
||||
|
||||
misp_bp = Blueprint("misp", __name__)
|
||||
misp = MISPObj()
|
||||
|
||||
|
||||
@misp_bp.route('/add', methods=['POST'])
|
||||
@require_header_token
|
||||
def add ():
|
||||
"""
|
||||
Parse and add a MISP instance to the database.
|
||||
:return: status of the operation in JSON
|
||||
"""
|
||||
data = json.loads(request.data)
|
||||
instance = data["data"]["instance"]
|
||||
|
||||
source = "backend"
|
||||
res = MISPObj.add(instance["name"], instance["url"], instance["key"], instance["ssl"], source)
|
||||
return jsonify(res)
|
||||
|
||||
@misp_bp.route('/delete/<misp_id>', methods=['GET'])
|
||||
@require_header_token
|
||||
def delete(misp_id):
|
||||
"""
|
||||
Delete a MISP instance by its id to the database.
|
||||
:return: status of the operation in JSON
|
||||
"""
|
||||
res = MISPObj.delete(misp_id)
|
||||
return jsonify(res)
|
||||
|
||||
@misp_bp.route('/get_all', methods=['GET'])
|
||||
@require_header_token
|
||||
def get_all():
|
||||
"""
|
||||
Retreive a list of all MISP instances.
|
||||
:return: list of MISP instances in JSON.
|
||||
"""
|
||||
res = MISPObj.get_all()
|
||||
return jsonify({"results": [i for i in res]})
|
||||
|
||||
|
||||
@misp_bp.route('/get_iocs', methods=['POST'])
|
||||
#@require_header_token
|
||||
def get_iocs():
|
||||
"""
|
||||
Retreive a list of all MISP instances.
|
||||
:return: list of MISP instances in JSON.
|
||||
"""
|
||||
|
||||
data = json.loads(request.data)
|
||||
data = data["data"]
|
||||
|
||||
res = MISPObj.get_iocs(data["misp_id"], data["limit"], data["page"])
|
||||
print(res)
|
||||
return jsonify(res)
|
||||
|
||||
|
||||
@misp_bp.route('/edit', methods=['POST'])
|
||||
@require_header_token
|
||||
def edit ():
|
||||
"""
|
||||
Parse and edit the desired MISP instance.
|
||||
:return: status of the operation in JSON
|
||||
"""
|
||||
data = json.loads(request.data)
|
||||
instance = data["data"]["instance"]
|
||||
print(instance)
|
||||
res = MISPObj.edit(instance["id"], instance["name"], instance["url"], instance["apikey"], instance["verifycert"])
|
||||
return jsonify(res)
|
@ -56,7 +56,12 @@ class IOCs(object):
|
||||
db.session.commit()
|
||||
return {"status": True,
|
||||
"message": "IOC added",
|
||||
"ioc": escape(ioc_value)}
|
||||
"ioc": escape(ioc_value),
|
||||
"type": escape(ioc_type),
|
||||
"tlp": escape(ioc_tlp),
|
||||
"tag": escape(ioc_tag),
|
||||
"source": escape(source),
|
||||
"added_on": escape(added_on)}
|
||||
else:
|
||||
return {"status": False,
|
||||
"message": "Wrong IOC format",
|
||||
|
240
server/backend/app/classes/mispobj.py
Normal file
240
server/backend/app/classes/mispobj.py
Normal file
@ -0,0 +1,240 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from app import db
|
||||
from app.db.models import MISPInst
|
||||
from sqlalchemy.sql import exists
|
||||
from app.definitions import definitions
|
||||
from urllib.parse import unquote
|
||||
from flask import escape
|
||||
from pymisp import PyMISP
|
||||
import re
|
||||
import time
|
||||
import sys
|
||||
|
||||
|
||||
class MISPObj(object):
|
||||
def __init__(self):
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def add(misp_name, misp_url, misp_key, misp_verifycert, source):
|
||||
"""
|
||||
Parse and add a MISP"instance to the database.
|
||||
:return: status of the operation in JSON
|
||||
"""
|
||||
|
||||
sameinstances = db.session.query(MISPInst).filter(MISPInst.url == misp_url, MISPInst.apikey == misp_key)
|
||||
if sameinstances.count() > 0:
|
||||
return {"status": False,
|
||||
"message": "This MISP instance already exists",
|
||||
"name": escape(misp_name),
|
||||
"url": escape(misp_url),
|
||||
"apikey": escape(misp_key),
|
||||
"verifycert": escape(misp_verifycert)}
|
||||
elif misp_name != "":
|
||||
if misp_url != "":
|
||||
if re.match(r"^(?:(?:http|https)://)", misp_url):
|
||||
if misp_key != "":
|
||||
added_on = int(time.time())
|
||||
db.session.add(MISPInst(misp_name, escape(misp_url), misp_key, misp_verifycert, source, added_on))
|
||||
db.session.commit()
|
||||
return {"status": True,
|
||||
"message": "MISP instance added",
|
||||
"name": escape(misp_name),
|
||||
"url": escape(misp_url),
|
||||
"apikey": escape(misp_key),
|
||||
"verifycert": escape(misp_verifycert)}
|
||||
else:
|
||||
return {"status": False,
|
||||
"message": "The key can't be empty",
|
||||
"name": escape(misp_name),
|
||||
"url": escape(misp_url),
|
||||
"apikey": "",
|
||||
"verifycert": escape(misp_verifycert)}
|
||||
else:
|
||||
return {"status": False,
|
||||
"message": "The url must begin with http:// or https://",
|
||||
"name": escape(misp_name),
|
||||
"url": escape(misp_url),
|
||||
"apikey": escape(misp_key),
|
||||
"verifycert": escape(misp_verifycert)}
|
||||
else:
|
||||
return {"status": False,
|
||||
"message": "The url can't be empty",
|
||||
"name": escape(misp_name),
|
||||
"url": "",
|
||||
"apikey": escape(misp_key),
|
||||
"verifycert": escape(misp_verifycert)}
|
||||
else:
|
||||
return {"status": False,
|
||||
"message": "The MISP instance name can't be empty",
|
||||
"name":"",
|
||||
"url": escape(misp_url),
|
||||
"apikey": escape(misp_key),
|
||||
"verifycert": escape(misp_verifycert)}
|
||||
|
||||
@staticmethod
|
||||
def edit(misp_id, misp_name, misp_url, misp_key, misp_verifycert):
|
||||
"""
|
||||
Parse and edit the desired MISP instance.
|
||||
:return: status of the operation in JSON
|
||||
"""
|
||||
mispinstance = MISPInst.query.get(int(misp_id))
|
||||
otherinstances = db.session.query(MISPInst).filter(MISPInst.id != int(misp_id), MISPInst.url == misp_url, MISPInst.apikey == misp_key)
|
||||
if mispinstance is None:
|
||||
return {"status": False,
|
||||
"message": "Can't find the MISP instance"}
|
||||
if otherinstances.count() > 0:
|
||||
return {"status": False,
|
||||
"message": "This MISP instance already exists",
|
||||
"name": escape(misp_name),
|
||||
"url": escape(misp_url),
|
||||
"apikey": escape(misp_key),
|
||||
"verifycert": escape(misp_verifycert)}
|
||||
elif misp_name != "":
|
||||
if misp_url != "":
|
||||
if re.match(r"^(?:(?:http|https)://)", misp_url):
|
||||
if misp_key != "":
|
||||
mispinstance.name = misp_name
|
||||
mispinstance.url = misp_url
|
||||
mispinstance.apikey = misp_key
|
||||
mispinstance.verifycert = misp_verifycert
|
||||
db.session.commit()
|
||||
return {"status": True,
|
||||
"message": "MISP instance edited",
|
||||
"name": escape(misp_name),
|
||||
"url": escape(misp_url),
|
||||
"apikey": escape(misp_key),
|
||||
"verifycert": escape(misp_verifycert)}
|
||||
else:
|
||||
return {"status": False,
|
||||
"message": "The key can't be empty",
|
||||
"name": escape(misp_name),
|
||||
"url": escape(misp_url),
|
||||
"apikey": "",
|
||||
"verifycert": escape(misp_verifycert)}
|
||||
else:
|
||||
return {"status": False,
|
||||
"message": "The url must begin with http:// or https://",
|
||||
"name": escape(misp_name),
|
||||
"url": escape(misp_url),
|
||||
"apikey": escape(misp_key),
|
||||
"verifycert": escape(misp_verifycert)}
|
||||
else:
|
||||
return {"status": False,
|
||||
"message": "The url can't be empty",
|
||||
"name": escape(misp_name),
|
||||
"url": "",
|
||||
"apikey": escape(misp_key),
|
||||
"verifycert": escape(misp_verifycert)}
|
||||
else:
|
||||
return {"status": False,
|
||||
"message": "The MISP instance name can't be empty",
|
||||
"name":"",
|
||||
"url": escape(misp_url),
|
||||
"apikey": escape(misp_key),
|
||||
"verifycert": escape(misp_verifycert)}
|
||||
|
||||
|
||||
@staticmethod
|
||||
def delete(misp_id):
|
||||
"""
|
||||
Delete a MISP instance by its id in the database.
|
||||
:return: status of the operation in JSON
|
||||
"""
|
||||
if db.session.query(exists().where(MISPInst.id == misp_id)).scalar():
|
||||
db.session.query(MISPInst).filter_by(id=misp_id).delete()
|
||||
db.session.commit()
|
||||
return {"status": True,
|
||||
"message": "MISP instance deleted"}
|
||||
else:
|
||||
return {"status": False,
|
||||
"message": "MISP instance not found"}
|
||||
|
||||
@staticmethod
|
||||
def get_all():
|
||||
"""
|
||||
Get all MISP instances from the database
|
||||
:return: generator of the records.
|
||||
"""
|
||||
for mispinstance in db.session.query(MISPInst).all():
|
||||
mispinstance = mispinstance.__dict__
|
||||
yield {"id": mispinstance["id"],
|
||||
"name": mispinstance["name"],
|
||||
"url": mispinstance["url"],
|
||||
"apikey": mispinstance["apikey"],
|
||||
"verifycert": mispinstance["verifycert"]}
|
||||
|
||||
@staticmethod
|
||||
def get_iocs(misp_id, limit, page):
|
||||
"""
|
||||
Get all IOCs from the desired MISP instance
|
||||
:return: generator of the records.
|
||||
"""
|
||||
mispinstance = MISPInst.query.get(int(misp_id))
|
||||
if mispinstance is not None:
|
||||
if mispinstance.url != "":
|
||||
if mispinstance.apikey != "":
|
||||
try:
|
||||
# Connects to the desired MISP instance
|
||||
mispinstance = PyMISP(mispinstance.url, mispinstance.apikey, mispinstance.verifycert)
|
||||
|
||||
# Retreives the attributes (or IOCs) that are supported by Tinycheck
|
||||
attributes = mispinstance.search('attributes', category='Network activity', limit=limit, page=page, metadata=True)
|
||||
|
||||
|
||||
if 'Attribute' in attributes:
|
||||
iocs = []
|
||||
for attribute in attributes['Attribute']:
|
||||
#print(attribute)
|
||||
if 'value' in attribute and attribute['value'] != '':
|
||||
# We have a valid value
|
||||
ioc_value = attribute['value']
|
||||
ioc_type = "unknown"
|
||||
ioc_tag = "No tag"
|
||||
ioc_tlp = "white"
|
||||
isFirstTag = True
|
||||
|
||||
if 'Tag' in attribute:
|
||||
# We have some tags
|
||||
#print (attribute['Tag'])
|
||||
for tag in attribute['Tag']:
|
||||
tlp = re.search(r"^(?:tlp:)(red|green|amber|white)", tag['name'])
|
||||
if tlp:
|
||||
# The current tag is the tlp level
|
||||
ioc_tlp = tlp.group(1)
|
||||
#print(ioc_tlp)
|
||||
elif isFirstTag:
|
||||
# It is the first retreived tag that is not a tlp
|
||||
isFirstTag = False
|
||||
ioc_tag = tag['name']
|
||||
else:
|
||||
# It is another tag and not the first one
|
||||
ioc_tag += ", " + tag['name']
|
||||
|
||||
ioc = { "value": ioc_value,
|
||||
"type": ioc_type,
|
||||
"tag": ioc_tag,
|
||||
"tlp": ioc_tlp }
|
||||
iocs.append(ioc)
|
||||
return { "status":True,
|
||||
"results": iocs}
|
||||
else:
|
||||
return { "status":False,
|
||||
"message":"No valid IOCs found."}
|
||||
except TypeError as error:
|
||||
print (error)
|
||||
pass
|
||||
except:
|
||||
print("An exception has been raised: ", sys.exc_info()[0])
|
||||
pass
|
||||
else:
|
||||
{"status": False,
|
||||
"message": "The api key can't be empty"}
|
||||
else:
|
||||
return {"status": False,
|
||||
"message": "The url can't be empty"}
|
||||
else:
|
||||
return {"status": False,
|
||||
"message": "Unknown MISP instance."}
|
@ -16,5 +16,15 @@ class Whitelist(db.Model):
|
||||
self.source = source
|
||||
self.added_on = added_on
|
||||
|
||||
class MISPInst(db.Model):
|
||||
def __init__(self, name, url, key, ssl, source, added_on):
|
||||
self.name = name
|
||||
self.url = url
|
||||
self.apikey = key
|
||||
self.verifycert = ssl
|
||||
self.source = source
|
||||
self.added_on = added_on
|
||||
|
||||
db.mapper(Whitelist, db.Table('whitelist', db.metadata, autoload=True))
|
||||
db.mapper(Ioc, db.Table('iocs', db.metadata, autoload=True))
|
||||
db.mapper(MISPInst, db.Table('mispinstance', db.metadata, autoload=True))
|
||||
|
Loading…
Reference in New Issue
Block a user