Merge pull request #70 from JulAkx/misp
New Feature : Import IoCs from an added MISP instance.
This commit is contained in:
commit
c1b8f4a447
@ -39,6 +39,9 @@
|
|||||||
<li class="menu-item">
|
<li class="menu-item">
|
||||||
<span @click="$router.push('/iocs/manage')">Manage IOCs</span>
|
<span @click="$router.push('/iocs/manage')">Manage IOCs</span>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="menu-item">
|
||||||
|
<span @click="$router.push('/iocs/misp')">MISP IOCs</span>
|
||||||
|
</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>
|
||||||
</li>
|
</li>
|
||||||
@ -95,4 +98,4 @@
|
|||||||
.fade-leave-active {
|
.fade-leave-active {
|
||||||
opacity: 0
|
opacity: 0
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -650,4 +650,57 @@ h4, h5 {
|
|||||||
|
|
||||||
.upper {
|
.upper {
|
||||||
text-transform: uppercase;
|
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'),
|
component: () => import('../views/iocs-manage.vue'),
|
||||||
props: true
|
props: true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/iocs/misp',
|
||||||
|
name: 'iocs-manage',
|
||||||
|
component: () => import('../views/iocs-misp.vue'),
|
||||||
|
props: true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/iocs/search',
|
path: '/iocs/search',
|
||||||
name: '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
|
M2Crypto
|
||||||
pyOpenSSL
|
pyOpenSSL
|
||||||
pydig
|
pydig
|
||||||
|
pymisp
|
||||||
netaddr
|
netaddr
|
||||||
pyyaml
|
pyyaml
|
||||||
flask
|
flask
|
||||||
@ -14,4 +15,4 @@ wifi
|
|||||||
qrcode
|
qrcode
|
||||||
netifaces
|
netifaces
|
||||||
weasyprint
|
weasyprint
|
||||||
python-whois
|
python-whois
|
||||||
|
@ -17,3 +17,14 @@ CREATE TABLE "whitelist" (
|
|||||||
"added_on" INTEGER NOT NULL,
|
"added_on" INTEGER NOT NULL,
|
||||||
PRIMARY KEY("id" AUTOINCREMENT)
|
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)
|
||||||
|
);
|
||||||
|
@ -26,6 +26,20 @@ def add(ioc_type, ioc_tag, ioc_tlp, ioc_value):
|
|||||||
return jsonify(res)
|
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'])
|
@ioc_bp.route('/delete/<ioc_id>', methods=['GET'])
|
||||||
@require_header_token
|
@require_header_token
|
||||||
def delete(ioc_id):
|
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()
|
db.session.commit()
|
||||||
return {"status": True,
|
return {"status": True,
|
||||||
"message": "IOC added",
|
"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:
|
else:
|
||||||
return {"status": False,
|
return {"status": False,
|
||||||
"message": "Wrong IOC format",
|
"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.source = source
|
||||||
self.added_on = added_on
|
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(Whitelist, db.Table('whitelist', db.metadata, autoload=True))
|
||||||
db.mapper(Ioc, db.Table('iocs', 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