Code modifications regarding MISP integration

This commit is contained in:
Félix Aime 2021-06-09 18:24:37 +02:00
parent a481e88251
commit f189f2e100
5 changed files with 138 additions and 325 deletions

View File

@ -40,7 +40,7 @@
<span @click="$router.push('/iocs/manage')">Manage IOCs</span> <span @click="$router.push('/iocs/manage')">Manage IOCs</span>
</li> </li>
<li class="menu-item"> <li class="menu-item">
<span @click="$router.push('/iocs/misp')">MISP IOCs</span> <span @click="$router.push('/iocs/misp')">MISP Instances</span>
</li> </li>
<li class="menu-item"> <li class="menu-item">
<span @click="$router.push('/iocs/search')">Search IOCs</span> <span @click="$router.push('/iocs/search')">Search IOCs</span>

View File

@ -658,22 +658,38 @@ h4, h5 {
/* Using CSS Grid to lay out the elements in two-dimensions: */ /* Using CSS Grid to lay out the elements in two-dimensions: */
display: grid; display: grid;
/* specifying a 0.2em gutter/gap between adjacent elements: */ /* specifying a 0.2em gutter/gap between adjacent elements: */
gap: 0.2em; gap: 0.4em;
overflow:auto; overflow:auto;
grid-template-columns: 10em 0.5em 1fr; grid-template-columns: 10em 0.5em 1fr;
width: 100%; width: 100%;
border:0.05rem solid #cecece;
border-radius:.1rem; border-radius:.1rem;
margin-bottom:.4rem; margin-bottom: .8rem;
padding:.4rem;
} }
.misp-label { .misp-label {
/* placing all <label> elements in the grid column 1 (the first): */ /* placing all <label> elements in the grid column 1 (the first): */
grid-column: 1; grid-column: 1;
text-align: left; text-align: left;
padding-top: .3em;
} }
.misp-offline {
background-color: #e85600;
color: #FFF;
font-size: 11px;
border-radius: 3px;
padding:3px;
text-transform: uppercase;
}
.misp-online {
background-color: #64c800;
color: #FFF;
font-size: 11px;
border-radius: 3px;
padding:3px;
text-transform: uppercase;
}
.misp-name { .misp-name {
font-size: 1rem; font-size: 1rem;
font-family: "Roboto-Bold"; font-family: "Roboto-Bold";
@ -704,3 +720,36 @@ h4, h5 {
by the name of 'submit': */ by the name of 'submit': */
grid-area: submit; grid-area: submit;
} }
.loading {
margin-bottom: 12px;
display: block;
}
.loading::after {
animation: loading .5s infinite linear;
background: 0 0;
border: .1rem solid #66758c;
border-radius: 50%;
border-right-color: transparent;
border-top-color: transparent;
content: "";
display: block;
height: .8rem;
left: 50%;
margin-left: -.4rem;
margin-top: -.4rem;
opacity: 1;
padding: 0;
position: absolute;
top: 50%;
width: .8rem;
z-index: 1;
}
.loading.loading-lg::after {
height: 1.6rem;
margin-left: -.8rem;
margin-top: -.8rem;
width: 1.6rem;
}

View File

@ -1,12 +1,7 @@
<template> <template>
<div class="backend-content" id="content"> <div class="backend-content" id="content">
<div class="column col-6 col-xs-12"> <div class="column col-6 col-xs-12">
<h3 class="s-title">Manage MISP IOCs</h3> <h3 class="s-title">Manage MISP instances</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"> <ul class="tab tab-block">
<li class="tab-item"> <li class="tab-item">
<a href="#" v-on:click="switch_tab('addmisp')" v-bind:class="{ active: tabs.addmisp }">Add instance</a> <a href="#" v-on:click="switch_tab('addmisp')" v-bind:class="{ active: tabs.addmisp }">Add instance</a>
@ -16,361 +11,137 @@
</li> </li>
</ul> </ul>
<div v-if="tabs.addmisp"> <div v-if="tabs.addmisp">
<h5>Add a new MISP instance</h5>
<div class="misp-form"> <div class="misp-form">
<label class="misp-label">Name</label><span>:</span> <label class="misp-label">Instance 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> <input class="form-input" type="text" ref="misp_name" placeholder="CYBERACME MISP" v-model="mispinst.name" required>
<label class="misp-label">URL</label><span>:</span> <label class="misp-label">Instance URL</label><span></span>
<input class="misp-input" type="text" ref="misp_url" placeholder="Enter your MISP instance URL" v-model="mispinst.url" required> <input class="form-input" type="text" ref="misp_url" placeholder="https://misp.cyberacme.com" v-model="mispinst.url" required>
<label class="misp-label">API key</label><span>:</span> <label class="misp-label">Authentication 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> <input class="form-input" type="text" ref="misp_key" placeholder="OqHSMyAuth3ntic4t10nK3y3iiH" v-model="mispinst.key" required>
<label class="misp-label">Verify certificate</label><span>:</span> <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 style="flex:50%"><label class="form-switch">
<input type="checkbox" @change="switch_config('frontend', 'kiosk_mode')" v-model="mispinst.ssl">
<i class="form-icon"></i>
</label></div>
</div> </div>
<button class="btn-primary btn col-12" v-on:click="add_misp_instance()">Add MISP instance</button> <button class="btn-primary btn col-12" v-on:click="add_instance()">Add MISP instance</button>
<div class="form-group" v-if="addedInstance.length>0"> <div class="form-group" v-if="added">
<div class="toast toast-success"> <div class="toast toast-success">
MISP instance added successfully. MISP instance added successfully.
</div> </div>
</div> </div>
<div v-if="errorsInstance.length>0"> <div class="form-group" v-if="error">
<div class="form-group"> <div class="toast toast-error">
<div class="toast toast-error"> MISP instance not added. {{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>
</div> </div>
<div class="form-group" v-if="tabs.instances"> <div class="form-group" v-if="tabs.instances">
<div v-if="mispInstances.length>0"> <div v-if="instances.length">
<div v-for="r in mispInstances" v-bind:key="r.id"> <table class="table table-striped table-hover">
<div style="position: relative"> <thead>
<input class="misp-name" :id="r.id + 'name'" v-bind:value="r.name" v-model="r.name" disabled="disabled" required> <tr>
<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> <th>Name</th>
<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> <th>Server</th>
</div> <th>Authkey</th>
<div class="misp-form"> <th>Status</th>
<label class="misp-label">URL</label><span>:</span> <th>Action</th>
<input class="misp-input" :id="r.id + 'insturl'" v-bind:value="r.url" v-model="r.url" disabled="disabled" required> </tr>
<label class="misp-label">API Key</label><span>:</span> </thead>
<input class="misp-input" :id="r.id + 'instkey'" v-bind:value="r.apikey" v-model="r.apikey" disabled="disabled" required> <tbody>
<label class="misp-label">Verify certificate</label><span>:</span> <tr v-for="i in instances" v-bind:key="i.id">
<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> <td>{{ i.name }}</td>
<label class="misp-label">Limit</label><span>:</span> <td>{{ i.url.replace('https://', '') .replace('http://', '') }}</td>
<input class="misp-input" type="number" step="1" min="0" :id="r.id + 'limit'" placeholder="Enter the maximum number of IOCs to retrieve"> <td>{{ i.apikey.slice(0,5) }} [...] {{ i.apikey.slice(35,40) }}</td>
<label class="misp-label">Page index</label><span>:</span> <td>
<input class="misp-input" type="number" step="1" min="0" :id="r.id + 'page'" placeholder="Enter the page index where to start retrieving IOCs"> <span v-if="i.connected" class="misp-online"> Online</span>
<button class="btn btn-sm" :id="r.id + 'import'" v-on:click="import_misp_iocs(r)">Import IOCs</button> <span v-else class="misp-offline"> Offline</span>
</div> </td>
</div> <td><button class="btn btn-sm" v-on:click="delete_instance(i)">Delete</button></td>
<div class="form-group" v-if="addedInstance.length>0"> </tr>
<div class="toast toast-success"> </tbody>
MISP instance edited successfully. </table>
</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>
<div v-else> <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"> <div class="empty">
<p class="empty-title h5">Please select a tag and a type.</p> <div v-if="loading">
<p class="empty-subtitle">If different IOCs types, select "Unknown (regex parsing)".</p> <p class="empty-title h5">
<span class="loading loading-lg"></span>
</p>
<p class="empty-subtitle">Testing and loading your MISP instances.</p>
</div>
<div v-else>
<p class="empty-title h5">No MISP instance found.</p>
<p class="empty-subtitle">Do not hesitate to add a MISP instance.</p>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import axios from 'axios' import axios from 'axios'
export default { export default {
name: 'managemisp', name: 'managemisp',
data() { data() {
return { return {
errors:[], error:false,
imported:[], loading:false,
errorsInstance:[], added:false,
addedInstance:[], mispinst:{ name:'', url:'',key:'', ssl:false },
mispinst:{name:'', url:'',key:'', ssl:false}, instances:[],
mispInstances:[],
tabs: { "addmisp" : true, "instances" : false }, tabs: { "addmisp" : true, "instances" : false },
jwt:"", jwt:""
type_tag_error: false
} }
}, },
props: { }, props: { },
methods: { methods: {
add_misp_instance: function() add_instance: function()
{ {
this.errors = []; this.added = false;
this.imported = []; this.error = false;
this.errorsInstance = [] if (this.mispinst.name && this.mispinst.url && this.mispinst.key)
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 => { axios.post(`/api/misp/add`, { data: { instance: this.mispinst } }, { headers: {'X-Token': this.jwt} }).then(response => {
if(response.data.status){ if(response.data.status){
this.addedInstance.push(response.data); this.added = true;
} else if (response.data.message){ } else {
this.errorsInstance.push(response.data); this.error = response.data.message;
}
})
.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))) .catch(err => (console.log(err)))
} }
}, },
cancel_edit_misp(elem) delete_instance(elem)
{ {
document.getElementById(elem.id+'edit').innerText = 'Edit instance'; axios.get(`/api/misp/delete/${elem.id}`, { timeout: 10000, headers: {'X-Token': this.jwt} })
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 => { .then(response => {
if(response.data.results.length>0){ if(response.data.status){
console.log(response.data.results); this.instances = this.instances.filter(function(el) { return el != elem; });
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))) .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() get_misp_instances()
{ {
this.errorsInstance = [] this.loading = true;
this.addedInstance = [] this.instances = []
this.mispInstances = []
axios.get(`/api/misp/get_all`, { timeout: 10000, headers: {'X-Token': this.jwt} }) axios.get(`/api/misp/get_all`, { timeout: 10000, headers: {'X-Token': this.jwt} })
.then(response => { .then(response => {
console.log(response.data); if(response.data.results) this.instances = response.data.results;
if(response.data.results.length>0){ this.loading = false
this.mispInstances = [].concat(this.mispInstances, response.data.results);
}
}) })
.catch(err => (console.log(err))) .catch(err => (console.log(err)))
}, },
switch_tab: function(tab) { switch_tab: function(tab) {
this.errors = []
this.errorsInstance = []
this.addedInstance = []
Object.keys(this.tabs).forEach(key => { Object.keys(this.tabs).forEach(key => {
if( key == tab ){ if( key == tab ){
this.tabs[key] = true this.tabs[key] = true
if (key == "instances") if (key == "instances") this.get_misp_instances();
{
this.get_misp_instances();
}
} else { } else {
this.tabs[key] = false this.tabs[key] = false
} }
@ -388,15 +159,6 @@ export default {
}, },
created: function() { created: function() {
this.get_jwt(); this.get_jwt();
this.get_misp_instances();
if (this.mispInstances.length>0)
{
this.tabs["addmisp"] = false;
this.tabs["instances"] = true;
}
} }
} }
</script> </script>

View File

@ -20,12 +20,9 @@ def add_instance():
""" """
data = json.loads(request.data) data = json.loads(request.data)
instance = data["data"]["instance"] instance = data["data"]["instance"]
res = misp.add_instance(instance)
res = MISP.add_instance(instance["name"], instance["url"],
instance["key"], instance["ssl"])
return jsonify(res) return jsonify(res)
@misp_bp.route('/delete/<misp_id>', methods=['GET']) @misp_bp.route('/delete/<misp_id>', methods=['GET'])
@require_header_token @require_header_token
def delete_instance(misp_id): def delete_instance(misp_id):
@ -33,16 +30,16 @@ def delete_instance(misp_id):
Delete a MISP instance by its id to the database. Delete a MISP instance by its id to the database.
:return: status of the operation in JSON :return: status of the operation in JSON
""" """
res = MISP.delete_instance(misp_id) res = misp.delete_instance(misp_id)
return jsonify(res) return jsonify(res)
@misp_bp.route('/get_all', methods=['GET']) @misp_bp.route('/get_all', methods=['GET'])
# @require_header_token @require_header_token
def get_all(): def get_all():
""" """
Retreive a list of all MISP instances. Retreive a list of all MISP instances.
:return: list of MISP instances in JSON. :return: list of MISP instances in JSON.
""" """
res = MISP().get_instances() res = misp.get_instances()
return jsonify({"results": [i for i in res]}) return jsonify({"results": [i for i in res]})

View File

@ -17,12 +17,17 @@ class MISP(object):
def __init__(self): def __init__(self):
return None return None
def add_instance(self, name, url, apikey, verify): def add_instance(self, instance):
""" """
Parse and add a MISP instance to the database. Parse and add a MISP instance to the database.
:return: status of the operation in JSON :return: status of the operation in JSON
""" """
url = instance["url"]
name = instance["name"]
apikey = instance["key"]
verify = instance["ssl"]
sameinstances = db.session.query(MISPInst).filter( sameinstances = db.session.query(MISPInst).filter(
MISPInst.url == url, MISPInst.apikey == apikey) MISPInst.url == url, MISPInst.apikey == apikey)
if sameinstances.count(): if sameinstances.count():