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>
</li>
<li class="menu-item">
<span @click="$router.push('/iocs/misp')">MISP IOCs</span>
<span @click="$router.push('/iocs/misp')">MISP Instances</span>
</li>
<li class="menu-item">
<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: */
display: grid;
/* specifying a 0.2em gutter/gap between adjacent elements: */
gap: 0.2em;
gap: 0.4em;
overflow:auto;
grid-template-columns: 10em 0.5em 1fr;
width: 100%;
border:0.05rem solid #cecece;
border-radius:.1rem;
margin-bottom:.4rem;
padding:.4rem;
margin-bottom: .8rem;
}
.misp-label {
/* placing all <label> elements in the grid column 1 (the first): */
grid-column: 1;
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 {
font-size: 1rem;
font-family: "Roboto-Bold";
@ -704,3 +720,36 @@ h4, h5 {
by the name of '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>
<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>
<h3 class="s-title">Manage MISP instances</h3>
<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>
@ -16,361 +11,137 @@
</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>
<label class="misp-label">Instance name</label><span></span>
<input class="form-input" type="text" ref="misp_name" placeholder="CYBERACME MISP" v-model="mispinst.name" required>
<label class="misp-label">Instance URL</label><span></span>
<input class="form-input" type="text" ref="misp_url" placeholder="https://misp.cyberacme.com" v-model="mispinst.url" required>
<label class="misp-label">Authentication key</label><span></span>
<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>
<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>
<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">
<button class="btn-primary btn col-12" v-on:click="add_instance()">Add MISP instance</button>
<div class="form-group" v-if="added">
<div class="toast toast-success">
MISP instance added successfully.
</div>
</div>
<div v-if="errorsInstance.length>0">
<div class="form-group">
<div class="form-group" v-if="error">
<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>
MISP instance not added. {{error}}
</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">
<div v-if="instances.length">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Name</th>
<th>URL</th>
<th>API key</th>
<th>Reason</th>
<th>Server</th>
<th>Authkey</th>
<th>Status</th>
<th>Action</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 v-for="i in instances" v-bind:key="i.id">
<td>{{ i.name }}</td>
<td>{{ i.url.replace('https://', '') .replace('http://', '') }}</td>
<td>{{ i.apikey.slice(0,5) }} [...] {{ i.apikey.slice(35,40) }}</td>
<td>
<span v-if="i.connected" class="misp-online"> Online</span>
<span v-else class="misp-offline"> Offline</span>
</td>
<td><button class="btn btn-sm" v-on:click="delete_instance(i)">Delete</button></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 v-if="loading">
<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>
</template>
<script>
import axios from 'axios'
export default {
name: 'managemisp',
data() {
return {
errors:[],
imported:[],
errorsInstance:[],
addedInstance:[],
error:false,
loading:false,
added:false,
mispinst:{ name:'', url:'',key:'', ssl:false },
mispInstances:[],
instances:[],
tabs: { "addmisp" : true, "instances" : false },
jwt:"",
type_tag_error: false
jwt:""
}
},
props: { },
methods: {
add_misp_instance: function()
add_instance: function()
{
this.errors = [];
this.imported = [];
this.errorsInstance = []
this.addedInstance = []
if (this.mispinst["name"] != "" && this.mispinst["url"] != "" && this.mispinst["key"] != "")
this.added = false;
this.error = false;
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);
this.added = true;
} else {
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)
delete_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; });
this.instances = this.instances.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 = []
this.loading = true;
this.instances = []
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);
}
if(response.data.results) this.instances = response.data.results;
this.loading = false
})
.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();
}
if (key == "instances") this.get_misp_instances();
} else {
this.tabs[key] = false
}
@ -388,15 +159,6 @@ export default {
},
created: function() {
this.get_jwt();
this.get_misp_instances();
if (this.mispInstances.length>0)
{
this.tabs["addmisp"] = false;
this.tabs["instances"] = true;
}
}
}
</script>

View File

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

View File

@ -17,12 +17,17 @@ class MISP(object):
def __init__(self):
return None
def add_instance(self, name, url, apikey, verify):
def add_instance(self, instance):
"""
Parse and add a MISP instance to the database.
: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(
MISPInst.url == url, MISPInst.apikey == apikey)
if sameinstances.count():