First commit!
This commit is contained in:
177
app/backend/src/App.vue
Executable file
177
app/backend/src/App.vue
Executable file
@@ -0,0 +1,177 @@
|
||||
<template>
|
||||
<div class="backend-container off-canvas off-canvas-sidebar-show">
|
||||
<div class="backend-navbar">
|
||||
<a class="off-canvas-toggle btn btn-link btn-action" href="#sidebar">
|
||||
<i class="icon icon-menu"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="backend-sidebar off-canvas-sidebar" id="sidebar">
|
||||
<div class="backend-brand">
|
||||
<h2 @click="goto_frontend()" class="title">{{ title }}</h2>
|
||||
<span class="version" v-if="current_version">{{current_version}}</span>
|
||||
</div>
|
||||
<div class="backend-nav">
|
||||
<div class="accordion-container">
|
||||
<div class="accordion">
|
||||
<input id="accordion-configuration" type="checkbox" name="backend-accordion-checkbox" hidden="">
|
||||
<label class="accordion-header c-hand" for="accordion-configuration">Manage Device</label>
|
||||
<div class="accordion-body">
|
||||
<ul class="menu menu-nav">
|
||||
<li class="menu-item">
|
||||
<span @click="$router.push('/device/configuration')">Device config</span>
|
||||
</li>
|
||||
<li class="menu-item">
|
||||
<span @click="$router.push('/engine/configuration')">Analysis engine</span>
|
||||
</li>
|
||||
<li class="menu-item">
|
||||
<span @click="$router.push('/device/network')">Network config</span>
|
||||
</li>
|
||||
<li class="menu-item">
|
||||
<span @click="$router.push('/device/db')">Manage database</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion">
|
||||
<input id="accordion-iocs" type="checkbox" name="backend-accordion-checkbox" hidden="">
|
||||
<label class="accordion-header c-hand" for="accordion-iocs">Manage IOCs</label>
|
||||
<div class="accordion-body">
|
||||
<ul class="menu menu-nav">
|
||||
<li class="menu-item">
|
||||
<span @click="$router.push('/iocs/manage')">Manage IOCs</span>
|
||||
</li>
|
||||
<li class="menu-item">
|
||||
<span @click="$router.push('/iocs/search')">Search IOCs</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion">
|
||||
<input id="accordion-whitelist" type="checkbox" name="backend-accordion-checkbox" hidden=""/>
|
||||
<label class="accordion-header c-hand" for="accordion-whitelist">Manage Whitelist</label>
|
||||
<div class="accordion-body">
|
||||
<ul class="menu menu-nav">
|
||||
<li class="menu-item">
|
||||
<span @click="$router.push('/whitelist/manage')">Manage elements</span>
|
||||
</li>
|
||||
<li class="menu-item">
|
||||
<span @click="$router.push('/whitelist/search')">Search elements</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion">
|
||||
<input id="accordion-instances" type="checkbox" name="backend-accordion-checkbox" hidden=""/>
|
||||
<label class="accordion-header c-hand" for="accordion-instances">External sources</label>
|
||||
<div class="accordion-body">
|
||||
<ul class="menu menu-nav">
|
||||
<li class="menu-item">
|
||||
<span @click="$router.push('/instances/watchers')">Watchers Instances</span>
|
||||
</li>
|
||||
<li class="menu-item">
|
||||
<span @click="$router.push('/instances/misp')">MISP Instances</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a class="off-canvas-overlay" href="#close"></a>
|
||||
<div class="off-canvas-content">
|
||||
<div id="update-banner" v-if="update_available" @click="$router.push('/update')">A new version is available, click on the banner to install it.</div>
|
||||
<transition name="fade" mode="out-in">
|
||||
<router-view/>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
@import './assets/spectre.min.css';
|
||||
@import './assets/spectre-exp.min.css';
|
||||
@import './assets/spectre-icons.min.css';
|
||||
@import './assets/custom.css';
|
||||
/* Face style for router stuff. */
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition-duration: 0.3s;
|
||||
transition-property: opacity;
|
||||
transition-timing-function: ease;
|
||||
}
|
||||
|
||||
.fade-enter,
|
||||
.fade-leave-active {
|
||||
opacity: 0
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
document.title = 'SpyGuard Backend'
|
||||
|
||||
export default {
|
||||
name: 'backend',
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
title: "SPYGUARD",
|
||||
current_version: false,
|
||||
jwt: "",
|
||||
update_available: false,
|
||||
letters: ["SSS§ṠSSSSS","PPPþ⒫PPPP","YYYÿYYYÿYȲYY","GGḠGGGǤG¬G","UÚUUÜUɄUUU", "AAAAÄA¬AAA", "RЯRɌRRRɌʭR", "DD¬DDDDƋDD"]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
generate_random: function(min = 0, max = 1000) {
|
||||
let difference = max - min;
|
||||
let rand = Math.random();
|
||||
rand = Math.floor( rand * difference);
|
||||
rand = rand + min;
|
||||
return rand;
|
||||
},
|
||||
goto_frontend: function() {
|
||||
window.location.href= `http://${location.hostname}:8000`
|
||||
},
|
||||
async get_jwt() {
|
||||
await axios.get(`/api/get-token`, { timeout: 10000 })
|
||||
.then(response => {
|
||||
if (response.data.token) {
|
||||
this.jwt = response.data.token
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
},
|
||||
get_version: function() {
|
||||
axios.get('/api/update/get-version', { timeout: 60000, headers: { 'X-Token': this.jwt } })
|
||||
.then(response => {
|
||||
if(response.data.status) this.current_version = response.data.current_version
|
||||
})
|
||||
.catch(error => { console.log(error) });
|
||||
},
|
||||
check_update: function() {
|
||||
axios.get('/api/update/check', { timeout: 60000, headers: { 'X-Token': this.jwt } })
|
||||
.then(response => {
|
||||
if(response.data.message == "A new version is available"){
|
||||
this.update_available = true;
|
||||
}
|
||||
})
|
||||
.catch(error => { console.log(error) });
|
||||
}
|
||||
},
|
||||
created: function() {
|
||||
this.get_jwt().then(() => {
|
||||
this.get_version();
|
||||
this.check_update();
|
||||
});
|
||||
setInterval(function(){
|
||||
let res = ""
|
||||
this.letters.forEach(l => { res += l.charAt(this.generate_random(0, 9)) })
|
||||
this.title = res;
|
||||
setTimeout(function(){
|
||||
this.title = "SPYGUARD";
|
||||
}.bind(this), this.generate_random(30, 100));
|
||||
}.bind(this), this.generate_random(500, 10000));
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
805
app/backend/src/assets/custom.css
Executable file
805
app/backend/src/assets/custom.css
Executable file
@@ -0,0 +1,805 @@
|
||||
/*
|
||||
This CSS was forked from the awsome Spectre.css docs.
|
||||
Spectre.css Docs | MIT License | github.com/picturepan2/spectre
|
||||
*/
|
||||
|
||||
|
||||
.off-canvas .off-canvas-toggle {
|
||||
font-size: 1rem;
|
||||
left: 1.5rem;
|
||||
position: fixed;
|
||||
top: 1rem
|
||||
}
|
||||
|
||||
.off-canvas .off-canvas-sidebar {
|
||||
width: 12rem
|
||||
}
|
||||
|
||||
.off-canvas .off-canvas-content {
|
||||
padding: 0
|
||||
}
|
||||
|
||||
.backend-container {
|
||||
min-height: 100vh
|
||||
}
|
||||
|
||||
.backend-navbar {
|
||||
height: 3.8rem;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 0;
|
||||
z-index: 100
|
||||
}
|
||||
|
||||
.backend-navbar .btns {
|
||||
position: absolute;
|
||||
right: 1.5rem;
|
||||
top: 1rem;
|
||||
width: 14rem
|
||||
}
|
||||
|
||||
.backend-navbar .algolia-autocomplete {
|
||||
-ms-flex: 1 1 auto;
|
||||
flex: 1 1 auto
|
||||
}
|
||||
|
||||
.backend-sidebar .backend-nav {
|
||||
bottom: 1.5rem;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
overflow-y: auto;
|
||||
padding: .5rem 1.5rem;
|
||||
position: fixed;
|
||||
top: 3.5rem;
|
||||
width: 12rem
|
||||
}
|
||||
|
||||
.backend-sidebar .accordion {
|
||||
margin-bottom: .75rem
|
||||
}
|
||||
|
||||
.backend-sidebar .accordion input~.accordion-header {
|
||||
color: #d9d9d9;
|
||||
font-size: .65rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase
|
||||
}
|
||||
|
||||
.backend-sidebar .accordion input:checked~.accordion-header {
|
||||
color: #d9d9d9;
|
||||
}
|
||||
|
||||
.backend-sidebar .accordion .menu .menu-item {
|
||||
font-size: .7rem;
|
||||
padding-left: 1rem;
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
.backend-sidebar .accordion .menu .menu-item>span {
|
||||
background: 0 0;
|
||||
color: #FAFAFA;
|
||||
display: inline-block
|
||||
}
|
||||
|
||||
.backend-content {
|
||||
-ms-flex: 1 1 auto;
|
||||
flex: 1 1 auto;
|
||||
padding: 0 4rem;
|
||||
width: calc(100vw - 12rem);
|
||||
margin-bottom: 10vh;
|
||||
}
|
||||
|
||||
.backend-content>.container {
|
||||
margin-left: 0;
|
||||
max-width: 800px;
|
||||
padding-bottom: 1.5rem
|
||||
}
|
||||
|
||||
.backend-content .anchor {
|
||||
color: #6362dc;
|
||||
display: none;
|
||||
margin-left: .2rem;
|
||||
padding: 0 .2rem
|
||||
}
|
||||
|
||||
.backend-content .anchor:focus,
|
||||
.backend-content .anchor:hover {
|
||||
display: inline;
|
||||
text-decoration: none
|
||||
}
|
||||
|
||||
.backend-content .s-subtitle,
|
||||
.backend-content .s-title {
|
||||
line-height: 1.8rem;
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 1rem;
|
||||
padding-top: 1rem;
|
||||
position: static
|
||||
}
|
||||
|
||||
@supports ((position:-webkit-sticky) or (position:sticky)) {
|
||||
.backend-content .s-subtitle,
|
||||
.backend-content .s-title {
|
||||
position: -webkit-sticky;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 99
|
||||
}
|
||||
.backend-content .s-subtitle::before,
|
||||
.backend-content .s-title::before {
|
||||
background: #fff;
|
||||
bottom: 0;
|
||||
content: "";
|
||||
display: block;
|
||||
left: -10px;
|
||||
position: absolute;
|
||||
right: -10px;
|
||||
top: -5px;
|
||||
z-index: -1
|
||||
}
|
||||
}
|
||||
|
||||
.backend-content .s-subtitle:hover .anchor,
|
||||
.backend-content .s-title:hover .anchor {
|
||||
display: inline
|
||||
}
|
||||
|
||||
.backend-content .s-subtitle+.backend-note,
|
||||
.backend-content .s-title+.backend-note {
|
||||
margin-top: .4rem
|
||||
}
|
||||
|
||||
.backend-content .backend-demo {
|
||||
padding-bottom: 1rem;
|
||||
padding-top: 1rem
|
||||
}
|
||||
|
||||
.backend-content .backend-demo .card {
|
||||
border: 0;
|
||||
box-shadow: 0 .25rem 1rem rgba(48, 55, 66, .15);
|
||||
height: 100%
|
||||
}
|
||||
|
||||
.backend-content .column {
|
||||
padding: .4rem
|
||||
}
|
||||
|
||||
.backend-content .backend-block {
|
||||
border-radius: .1rem;
|
||||
padding: .4rem
|
||||
}
|
||||
|
||||
.backend-content .backend-block.bg-gray {
|
||||
background: #eef0f3
|
||||
}
|
||||
|
||||
.backend-content .backend-shape {
|
||||
height: 4.8rem;
|
||||
line-height: 1.2rem;
|
||||
padding: 1.8rem 0;
|
||||
width: 4.8rem
|
||||
}
|
||||
|
||||
.backend-content .backend-dot {
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
height: .5rem;
|
||||
padding: 0;
|
||||
width: .5rem
|
||||
}
|
||||
|
||||
.backend-content .backend-table td,
|
||||
.backend-content .backend-table th {
|
||||
padding: .75rem .25rem
|
||||
}
|
||||
|
||||
.backend-content .backend-color {
|
||||
border-radius: .1rem;
|
||||
margin: .25rem 0;
|
||||
padding: 5rem .5rem .5rem
|
||||
}
|
||||
|
||||
.backend-content .backend-color .color-subtitle {
|
||||
font-size: .7rem;
|
||||
opacity: .75
|
||||
}
|
||||
|
||||
.backend-content .code .hljs-tag {
|
||||
color: #505c6e
|
||||
}
|
||||
|
||||
.backend-content .code .hljs-comment {
|
||||
color: #bcc3ce
|
||||
}
|
||||
|
||||
.backend-content .code .hljs-class,
|
||||
.backend-content .code .hljs-number,
|
||||
.backend-content .code .hljs-string,
|
||||
.backend-content .code .hljs-title {
|
||||
color: #5755d9
|
||||
}
|
||||
|
||||
.backend-content .code .hljs-attribute,
|
||||
.backend-content .code .hljs-built_in,
|
||||
.backend-content .code .hljs-keyword,
|
||||
.backend-content .code .hljs-name,
|
||||
.backend-content .code .hljs-variable {
|
||||
color: #d73e48
|
||||
}
|
||||
|
||||
.backend-content .code .hljs-hexcolor,
|
||||
.backend-content .code .hljs-value {
|
||||
color: #505c6e
|
||||
}
|
||||
|
||||
.backend-content .c-select-all {
|
||||
-webkit-user-select: all;
|
||||
-moz-user-select: all;
|
||||
-ms-user-select: all;
|
||||
user-select: all
|
||||
}
|
||||
|
||||
.backend-content .panel {
|
||||
height: 75vh
|
||||
}
|
||||
|
||||
.backend-content .panel .tile {
|
||||
margin: .75rem 0
|
||||
}
|
||||
|
||||
.backend-content .parallax {
|
||||
margin: 2rem auto
|
||||
}
|
||||
|
||||
.backend-content .form-autocomplete .menu {
|
||||
position: static
|
||||
}
|
||||
|
||||
.backend-content .example-tile-icon {
|
||||
align-content: space-around;
|
||||
align-items: center;
|
||||
background: #5755d9;
|
||||
border-radius: .1rem;
|
||||
color: #fff;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-ms-flex-align: center;
|
||||
-ms-flex-line-pack: distribute;
|
||||
font-size: 1.2rem;
|
||||
height: 2rem;
|
||||
width: 2rem
|
||||
}
|
||||
|
||||
.backend-content .example-tile-icon .icon {
|
||||
margin: auto
|
||||
}
|
||||
|
||||
.backend-content .comparison-slider {
|
||||
height: auto;
|
||||
padding-bottom: 56.2222%
|
||||
}
|
||||
|
||||
.backend-content .comparison-slider .filter-grayscale {
|
||||
filter: grayscale(75%)
|
||||
}
|
||||
|
||||
.backend-content .off-canvas {
|
||||
position: relative
|
||||
}
|
||||
|
||||
.backend-content .off-canvas .off-canvas-toggle {
|
||||
left: .4rem;
|
||||
position: absolute;
|
||||
top: .4rem;
|
||||
z-index: 1
|
||||
}
|
||||
|
||||
.backend-brand {
|
||||
color: #CCC;
|
||||
height: 2rem;
|
||||
left: 1.5rem;
|
||||
position: fixed;
|
||||
top: .85rem;
|
||||
width: 72%;
|
||||
position: sticky;
|
||||
}
|
||||
|
||||
.backend-brand .backend-logo {
|
||||
align-items: center;
|
||||
border-radius: .1rem;
|
||||
display: -ms-inline-flexbox;
|
||||
display: inline-flex;
|
||||
-ms-flex-align: center;
|
||||
font-size: .7rem;
|
||||
height: 2rem;
|
||||
padding: .2rem;
|
||||
width: auto
|
||||
}
|
||||
|
||||
.backend-brand .backend-logo:focus,
|
||||
.backend-brand .backend-logo:hover {
|
||||
text-decoration: none
|
||||
}
|
||||
|
||||
.backend-brand .backend-logo img {
|
||||
display: inline-block;
|
||||
height: auto;
|
||||
width: 1.6rem
|
||||
}
|
||||
|
||||
.backend-brand .backend-logo h2 {
|
||||
display: inline-block;
|
||||
font-size: .8rem;
|
||||
font-weight: 700;
|
||||
line-height: 1.5rem;
|
||||
margin-bottom: 0;
|
||||
margin-left: .5rem;
|
||||
margin-right: .3rem
|
||||
}
|
||||
|
||||
.backend-footer {
|
||||
color: #bcc3ce;
|
||||
padding: .5rem
|
||||
}
|
||||
|
||||
.backend-footer a {
|
||||
color: #66758c
|
||||
}
|
||||
|
||||
@media (max-width:960px) {
|
||||
.off-canvas .off-canvas-toggle {
|
||||
z-index: 300
|
||||
}
|
||||
.off-canvas .off-canvas-content {
|
||||
width: 100%
|
||||
}
|
||||
.backend-sidebar .backend-brand {
|
||||
margin: .85rem 1.5rem;
|
||||
padding: 0;
|
||||
position: static
|
||||
}
|
||||
.backend-sidebar .backend-nav {
|
||||
margin-top: 1rem;
|
||||
position: static
|
||||
}
|
||||
.backend-sidebar .menu .menu-item>a {
|
||||
padding: .3rem .4rem
|
||||
}
|
||||
.backend-navbar {
|
||||
-webkit-backdrop-filter: blur(5px);
|
||||
backdrop-filter: blur(5px);
|
||||
background: rgba(247, 248, 249, .65);
|
||||
left: 0
|
||||
}
|
||||
.backend-content {
|
||||
min-width: auto;
|
||||
padding: 0 1.5rem;
|
||||
width: 100%
|
||||
}
|
||||
.backend-content .s-subtitle,
|
||||
.backend-content .s-title {
|
||||
padding-top: 5rem;
|
||||
position: static
|
||||
}
|
||||
.backend-content .s-subtitle::before,
|
||||
.backend-content .s-title::before {
|
||||
content: none
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width:600px) {
|
||||
.off-canvas .off-canvas-toggle {
|
||||
left: .5rem
|
||||
}
|
||||
.backend-navbar .btns {
|
||||
right: .9rem
|
||||
}
|
||||
.backend-sidebar .backend-brand {
|
||||
margin: .85rem 1rem
|
||||
}
|
||||
.backend-sidebar .backend-nav {
|
||||
padding: .5rem 1rem
|
||||
}
|
||||
.backend-content {
|
||||
padding: 0 .5rem
|
||||
}
|
||||
.backend-content .backend-block {
|
||||
padding: .4rem .1rem
|
||||
}
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Lobster';
|
||||
font-weight: normal;
|
||||
src: url('fonts/Exo.ttf') format('truetype');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Roboto-Bold";
|
||||
src: url("fonts/Roboto-Bold.eot"); /* IE9 Compat Modes */
|
||||
src: url("fonts/Roboto-Bold.eot?#iefix") format("embedded-opentype"), /* IE6-IE8 */
|
||||
url("fonts/Roboto-Bold.otf") format("opentype"), /* Open Type Font */
|
||||
url("fonts/Roboto-Bold.svg") format("svg"), /* Legacy iOS */
|
||||
url("fonts/Roboto-Bold.ttf") format("truetype"), /* Safari, Android, iOS */
|
||||
url("fonts/Roboto-Bold.woff") format("woff"), /* Modern Browsers */
|
||||
url("fonts/Roboto-Bold.woff2") format("woff2"); /* Modern Browsers */
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.off-canvas .off-canvas-sidebar {
|
||||
background: #3a3a3a;
|
||||
}
|
||||
h1, h2, h3 {
|
||||
font-family: Lobster;
|
||||
color: #484848;
|
||||
}
|
||||
|
||||
h4, h5 {
|
||||
font-family: "Roboto-Bold";
|
||||
color: #484848;
|
||||
}
|
||||
|
||||
.btn {
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.btn:focus, .btn:hover {
|
||||
background: #494949;
|
||||
border-color: #494949;
|
||||
text-decoration: none;
|
||||
color: #DBDBDB;
|
||||
}
|
||||
|
||||
.btn.active, .btn:active {
|
||||
background: #3a3a3a;
|
||||
border-color: #3a3a3a;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.px150 {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.width-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tab-block {
|
||||
margin-bottom: 35px;
|
||||
}
|
||||
|
||||
.toast {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.frame-export {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.form-upload {
|
||||
position: relative;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.form-upload .upload-field {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
z-index:1000;
|
||||
width:100%;
|
||||
height:100%;
|
||||
}
|
||||
|
||||
.tlp-white {
|
||||
font-size: .6rem;
|
||||
height: 1.4rem;
|
||||
padding: .05rem .3rem;
|
||||
background-color: #FFF;
|
||||
line-height: 1.2rem;
|
||||
text-transform: uppercase;
|
||||
font-weight: bold;
|
||||
border: 2px solid #efefef;
|
||||
vertical-align: middle;
|
||||
border-radius: .1rem;
|
||||
}
|
||||
|
||||
.tlp-green {
|
||||
font-size: .6rem;
|
||||
height: 1.4rem;
|
||||
padding: .05rem .3rem;
|
||||
background-color: #199a09cf;
|
||||
line-height: 1.2rem;
|
||||
text-transform: uppercase;
|
||||
font-weight: bold;
|
||||
border: 2px solid #0f8600cf;
|
||||
vertical-align: middle;
|
||||
color:#FFF;
|
||||
border-radius: .1rem;
|
||||
}
|
||||
|
||||
.tlp-amber {
|
||||
font-size: .6rem;
|
||||
height: 1.4rem;
|
||||
padding: .05rem .3rem;
|
||||
background-color: #ffc000cf;
|
||||
line-height: 1.2rem;
|
||||
text-transform: uppercase;
|
||||
font-weight: bold;
|
||||
border: 2px solid #ffc000cf;
|
||||
vertical-align: middle;
|
||||
color:#FFF;
|
||||
border-radius: .1rem;
|
||||
}
|
||||
|
||||
.tlp-red {
|
||||
font-size: .6rem;
|
||||
height: 1.4rem;
|
||||
padding: .05rem .3rem;
|
||||
background-color: #ff0033;
|
||||
line-height: 1.2rem;
|
||||
text-transform: uppercase;
|
||||
font-weight: bold;
|
||||
border: 2px solid #ff0033;
|
||||
vertical-align: middle;
|
||||
color:#FFF;
|
||||
border-radius: .1rem;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-family: 'Lobster';
|
||||
color: #FAFAFA;
|
||||
cursor: pointer;
|
||||
width: fit-content;
|
||||
height: 2rem;
|
||||
left: 1.5rem;
|
||||
position: fixed;
|
||||
top: .85rem;
|
||||
text-transform: uppercase;
|
||||
font-size: 1.3rem;
|
||||
letter-spacing: 0.1rem;
|
||||
}
|
||||
|
||||
#network-thumbnail {
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.interfaces-container {
|
||||
margin-top:20px;
|
||||
margin-bottom:40px;
|
||||
}
|
||||
|
||||
.interface-label {
|
||||
text-transform: uppercase;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
text-align: center;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
border-color: inherit;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.form-input:focus {
|
||||
border-color: inherit;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.tab .tab-item a:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.shortcuts {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 20000;
|
||||
padding: 20px;
|
||||
opacity: .2;
|
||||
}
|
||||
|
||||
.shortcut {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-left: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.whitespace {
|
||||
height:100px;
|
||||
display:block;
|
||||
}
|
||||
|
||||
.alert-toaster-visible {
|
||||
position:fixed;
|
||||
right:15px;
|
||||
top:15px;
|
||||
padding:10px;
|
||||
background-color: #484848;
|
||||
border-radius: 5px;
|
||||
color: #ccc;
|
||||
font-size: 12px;
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
transition: opacity .5s linear;
|
||||
}
|
||||
|
||||
.alert-toaster-hidden {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: visibility 0s .5s, opacity .5s linear;
|
||||
}
|
||||
|
||||
.comment-block {
|
||||
padding: 10px;
|
||||
font-size: 12px;
|
||||
background-color: #FAFAFA;
|
||||
}
|
||||
|
||||
.capi {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.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.4em;
|
||||
overflow:auto;
|
||||
grid-template-columns: 10em 0.5em 1fr;
|
||||
width: 100%;
|
||||
border-radius:.1rem;
|
||||
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;
|
||||
}
|
||||
|
||||
.instance-offline {
|
||||
background-color: #e85600;
|
||||
color: #FFF;
|
||||
font-size: 11px;
|
||||
border-radius: 3px;
|
||||
padding:3px 6px 3px 6px;
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.instance-online {
|
||||
background-color: #64c800;
|
||||
color: #FFF;
|
||||
font-size: 11px;
|
||||
border-radius: 3px;
|
||||
padding:3px 6px 3px 6px;
|
||||
cursor: help;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.tooltip::after{
|
||||
background:rgba(48,55,66,.95);
|
||||
border-radius:3px;
|
||||
bottom:100%;
|
||||
color:#fff;
|
||||
content:attr(data-tooltip);
|
||||
display:block;
|
||||
font-size: 11px;
|
||||
left:50%;
|
||||
max-width:320px;
|
||||
opacity:0;
|
||||
overflow:hidden;
|
||||
padding:3px 6px 3px 6px;
|
||||
pointer-events:none;
|
||||
position:absolute;
|
||||
text-overflow:ellipsis;
|
||||
transform:translate(-50%,.4rem);
|
||||
transition:opacity .2s,transform .2s;
|
||||
white-space:pre;
|
||||
z-index:300
|
||||
}
|
||||
|
||||
.btn.btn-primary {
|
||||
background: #3a3a3a;
|
||||
border-color: #3a3a3a;
|
||||
}
|
||||
|
||||
.read-only {
|
||||
background-color: #F4F4F4;
|
||||
color: #AEADAD;
|
||||
}
|
||||
|
||||
#update-banner {
|
||||
padding: 5px 20px;
|
||||
background-color: #d1ff51;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.version {
|
||||
right: 0;
|
||||
text-align: right;
|
||||
background-color: #FFF;
|
||||
border-radius: 5px;
|
||||
font-size: 9px;
|
||||
padding: 1px 3px;
|
||||
color: #000;
|
||||
position: absolute;
|
||||
}
|
||||
BIN
app/backend/src/assets/fonts/Exo.ttf
Executable file
BIN
app/backend/src/assets/fonts/Exo.ttf
Executable file
Binary file not shown.
BIN
app/backend/src/assets/fonts/Exo.woff2
Executable file
BIN
app/backend/src/assets/fonts/Exo.woff2
Executable file
Binary file not shown.
BIN
app/backend/src/assets/fonts/Roboto-Bold.eot
Executable file
BIN
app/backend/src/assets/fonts/Roboto-Bold.eot
Executable file
Binary file not shown.
BIN
app/backend/src/assets/fonts/Roboto-Bold.otf
Executable file
BIN
app/backend/src/assets/fonts/Roboto-Bold.otf
Executable file
Binary file not shown.
11535
app/backend/src/assets/fonts/Roboto-Bold.svg
Executable file
11535
app/backend/src/assets/fonts/Roboto-Bold.svg
Executable file
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 805 KiB |
BIN
app/backend/src/assets/fonts/Roboto-Bold.ttf
Executable file
BIN
app/backend/src/assets/fonts/Roboto-Bold.ttf
Executable file
Binary file not shown.
BIN
app/backend/src/assets/fonts/Roboto-Bold.woff
Executable file
BIN
app/backend/src/assets/fonts/Roboto-Bold.woff
Executable file
Binary file not shown.
BIN
app/backend/src/assets/fonts/Roboto-Bold.woff2
Executable file
BIN
app/backend/src/assets/fonts/Roboto-Bold.woff2
Executable file
Binary file not shown.
BIN
app/backend/src/assets/fonts/Roboto-Regular.eot
Executable file
BIN
app/backend/src/assets/fonts/Roboto-Regular.eot
Executable file
Binary file not shown.
BIN
app/backend/src/assets/fonts/Roboto-Regular.otf
Executable file
BIN
app/backend/src/assets/fonts/Roboto-Regular.otf
Executable file
Binary file not shown.
11080
app/backend/src/assets/fonts/Roboto-Regular.svg
Executable file
11080
app/backend/src/assets/fonts/Roboto-Regular.svg
Executable file
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 784 KiB |
BIN
app/backend/src/assets/fonts/Roboto-Regular.ttf
Executable file
BIN
app/backend/src/assets/fonts/Roboto-Regular.ttf
Executable file
Binary file not shown.
BIN
app/backend/src/assets/fonts/Roboto-Regular.woff
Executable file
BIN
app/backend/src/assets/fonts/Roboto-Regular.woff
Executable file
Binary file not shown.
BIN
app/backend/src/assets/fonts/Roboto-Regular.woff2
Executable file
BIN
app/backend/src/assets/fonts/Roboto-Regular.woff2
Executable file
Binary file not shown.
BIN
app/backend/src/assets/network.png
Executable file
BIN
app/backend/src/assets/network.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
1
app/backend/src/assets/spectre-exp.min.css
vendored
Executable file
1
app/backend/src/assets/spectre-exp.min.css
vendored
Executable file
File diff suppressed because one or more lines are too long
1
app/backend/src/assets/spectre-icons.min.css
vendored
Executable file
1
app/backend/src/assets/spectre-icons.min.css
vendored
Executable file
File diff suppressed because one or more lines are too long
1
app/backend/src/assets/spectre.min.css
vendored
Executable file
1
app/backend/src/assets/spectre.min.css
vendored
Executable file
File diff suppressed because one or more lines are too long
10
app/backend/src/main.js
Executable file
10
app/backend/src/main.js
Executable file
@@ -0,0 +1,10 @@
|
||||
import Vue from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
Vue.config.productionTip = true
|
||||
Vue.config.devtools = true
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
render: h => h(App)
|
||||
}).$mount('#app')
|
||||
87
app/backend/src/router/index.js
Executable file
87
app/backend/src/router/index.js
Executable file
@@ -0,0 +1,87 @@
|
||||
import Vue from 'vue'
|
||||
import VueRouter from 'vue-router'
|
||||
|
||||
Vue.use(VueRouter)
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'default',
|
||||
component: () => import('../views/home.vue'),
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: '/device/configuration',
|
||||
name: 'device-configuration',
|
||||
component: () => import('../views/edit-configuration.vue'),
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: '/engine/configuration',
|
||||
name: 'engine-configuration',
|
||||
component: () => import('../views/analysis-engine.vue'),
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: '/device/network',
|
||||
name: 'device-network',
|
||||
component: () => import('../views/network-manage.vue'),
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: '/device/db',
|
||||
name: 'db-manage',
|
||||
component: () => import('../views/db-manage.vue'),
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: '/iocs/manage',
|
||||
name: 'iocs-manage',
|
||||
component: () => import('../views/iocs-manage.vue'),
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: '/instances/misp',
|
||||
name: 'instance-misp',
|
||||
component: () => import('../views/instance-misp.vue'),
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: '/instances/watchers',
|
||||
name: 'instance-watchers',
|
||||
component: () => import('../views/instance-watchers.vue'),
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: '/iocs/search',
|
||||
name: 'iocs-search',
|
||||
component: () => import('../views/iocs-search.vue'),
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: '/whitelist/manage',
|
||||
name: 'whitelist-manage',
|
||||
component: () => import('../views/whitelist-manage.vue'),
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: '/whitelist/search',
|
||||
name: 'whitelist-search',
|
||||
component: () => import('../views/whitelist-search.vue'),
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: '/update',
|
||||
name: 'update',
|
||||
component: () => import('../views/update.vue'),
|
||||
props: true
|
||||
}
|
||||
]
|
||||
|
||||
const router = new VueRouter({
|
||||
mode: 'history',
|
||||
base: process.env.BASE_URL,
|
||||
routes
|
||||
})
|
||||
|
||||
export default router
|
||||
143
app/backend/src/views/analysis-engine.vue
Executable file
143
app/backend/src/views/analysis-engine.vue
Executable file
@@ -0,0 +1,143 @@
|
||||
<template>
|
||||
<div class="backend-content" id="content">
|
||||
<div v-bind:class="{ 'alert-toaster-visible' : toaster.show, 'alert-toaster-hidden' : !toaster.show }">{{toaster.message}}</div>
|
||||
<div class="column col-8 col-xs-12">
|
||||
<h3 class="s-title">Detection engine configuration</h3>
|
||||
<h5 class="s-subtitle">Detection methods</h5>
|
||||
<div class="form-group">
|
||||
<label class="form-switch">
|
||||
<input type="checkbox" @change="local_analysis('analysis', 'heuristics')" v-model="config.analysis.heuristics">
|
||||
<i class="form-icon"></i> Use heuristic detection for suspect behaviour.
|
||||
</label>
|
||||
<label class="form-switch">
|
||||
<input type="checkbox" @change="local_analysis('analysis', 'iocs')" v-model="config.analysis.iocs">
|
||||
<i class="form-icon"></i> Use Indicator of Compromise (IoC) based detection.
|
||||
</label>
|
||||
<label class="form-switch">
|
||||
<input type="checkbox" @change="local_analysis('analysis', 'whitelist')" v-model="config.analysis.whitelist">
|
||||
<i class="form-icon"></i> Use whitelist to prevent false positives.
|
||||
</label>
|
||||
<label class="form-switch">
|
||||
<input type="checkbox" @change="local_analysis('analysis', 'active')" v-model="config.analysis.active">
|
||||
<i class="form-icon"></i> Use active analysis (Dig, Whois, OpenSSL...).
|
||||
</label>
|
||||
<label class="form-switch">
|
||||
<input type="checkbox" @change="switch_iocs_types('all')" :checked="config.analysis.indicators_types.includes('all')">
|
||||
<i class="form-icon"></i> Detect threats by using all IOCs.
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group" v-if="!config.analysis.indicators_types.includes('all')">
|
||||
<h5 class="s-subtitle">IOCs categories</h5>
|
||||
<label class="form-switch" v-for="tag in iocs_tags" :key="tag">
|
||||
<input type="checkbox" @change="switch_iocs_types(tag)" :checked="config.analysis.indicators_types.includes(tag)">
|
||||
<i class="form-icon"></i> Use IOCs related to {{ tag.toUpperCase() }} threat.
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'analysis-engine',
|
||||
data() {
|
||||
return {
|
||||
config: {},
|
||||
check_certificate: false,
|
||||
certificate: "",
|
||||
iocs_tags: [],
|
||||
toaster: { show: false, message : "", type : null }
|
||||
}
|
||||
},
|
||||
props: {},
|
||||
methods: {
|
||||
switch_config: function(cat, key) {
|
||||
axios.get(`/api/config/switch/${cat}/${key}`, {
|
||||
timeout: 10000,
|
||||
headers: { 'X-Token': this.jwt }
|
||||
}).then(response => {
|
||||
if (response.data.status) {
|
||||
if (response.data.message == "Key switched to true") {
|
||||
this.toaster = { show : true, message : "Configuration updated", type : "success" }
|
||||
setTimeout(function () { this.toaster = { show : false } }.bind(this), 1000)
|
||||
this.config[cat][key] = true
|
||||
} else if (response.data.message == "Key switched to false") {
|
||||
this.toaster = { show : true, message : "Configuration updated", type : "success" }
|
||||
setTimeout(function () { this.toaster = { show : false } }.bind(this), 1000)
|
||||
this.config[cat][key] = false
|
||||
} else {
|
||||
this.toaster = { show : true, message : "The key doesn't exist", type : "error" }
|
||||
setTimeout(function () { this.toaster = { show : false } }.bind(this), 1000)
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
},
|
||||
local_analysis: function(cat, key) {
|
||||
this.switch_config(cat, key);
|
||||
if (this.config.analysis.remote != false)
|
||||
this.switch_config("analysis", "remote");
|
||||
},
|
||||
load_config: function() {
|
||||
axios.get(`/api/config/list`, {
|
||||
timeout: 10000,
|
||||
headers: { 'X-Token': this.jwt }
|
||||
}).then(response => {
|
||||
if (response.data) {
|
||||
this.config = response.data
|
||||
this.config.backend.password = ""
|
||||
console.log(this.config.analysis.indicators_types);
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
},
|
||||
async get_jwt() {
|
||||
await axios.get(`/api/get-token`, { timeout: 10000 })
|
||||
.then(response => {
|
||||
if (response.data.token) {
|
||||
this.jwt = response.data.token
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
},
|
||||
get_iocs_tags: function() {
|
||||
axios.get(`/api/ioc/get/tags`, {
|
||||
timeout: 10000,
|
||||
headers: {'X-Token': this.jwt}
|
||||
})
|
||||
.then(response => {
|
||||
if(response.data.tags) this.iocs_tags = response.data.tags
|
||||
})
|
||||
.catch(err => (console.log(err)));
|
||||
},
|
||||
switch_iocs_types: function(tag) {
|
||||
if (this.config.analysis.indicators_types.includes(tag)){
|
||||
axios.get(`/api/config/ioc-type/delete/${tag}`, {
|
||||
timeout: 10000,
|
||||
headers: { 'X-Token': this.jwt }
|
||||
}).then(response => {
|
||||
if (response.data.status) { this.load_config(); }
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
} else {
|
||||
axios.get(`/api/config/ioc-type/add/${tag}`, {
|
||||
timeout: 10000,
|
||||
headers: { 'X-Token': this.jwt }
|
||||
}).then(response => {
|
||||
if (response.data.status) { this.load_config(); }
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
this.load_config();
|
||||
}
|
||||
}
|
||||
},
|
||||
created: function() {
|
||||
this.get_jwt().then(() => {
|
||||
this.load_config();
|
||||
this.get_iocs_tags();
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
75
app/backend/src/views/db-manage.vue
Executable file
75
app/backend/src/views/db-manage.vue
Executable file
@@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<div class="backend-content" id="content">
|
||||
<div class="column col-8 col-xs-12">
|
||||
<h3 class="s-title">Manage database</h3>
|
||||
<ul class="tab tab-block">
|
||||
<li class="tab-item">
|
||||
<a href="#" v-on:click="switch_tab('import')" v-bind:class="{ active: tabs.import }">Import database</a>
|
||||
</li>
|
||||
<li class="tab-item">
|
||||
<a href="#" v-on:click="switch_tab('export')" v-bind:class="{ active: tabs.export }">Export database</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div v-if="tabs.export">
|
||||
<iframe :src="export_url" class="frame-export"></iframe>
|
||||
</div>
|
||||
<div v-if="tabs.import">
|
||||
<label class="form-upload empty" for="upload">
|
||||
<input type="file" class="upload-field" id="upload" @change="import_from_file">
|
||||
<p class="empty-title h5">Drop or select a database to import.</p>
|
||||
<p class="empty-subtitle">The database needs to be an export from a SpyGuard instance.</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'db-manage',
|
||||
data() {
|
||||
return {
|
||||
tabs: { "import" : true, "export" : false },
|
||||
jwt:""
|
||||
}
|
||||
},
|
||||
props: { },
|
||||
methods: {
|
||||
switch_tab: function(tab) {
|
||||
Object.keys(this.tabs).forEach(key => {
|
||||
if( key == tab ){
|
||||
this.tabs[key] = true
|
||||
} else {
|
||||
this.tabs[key] = false
|
||||
}
|
||||
});
|
||||
},
|
||||
import_from_file: function(ev) {
|
||||
var formData = new FormData();
|
||||
formData.append("file", ev.target.files[0]);
|
||||
axios.post('/api/config/db/import', formData, {
|
||||
headers: {
|
||||
"Content-Type" : "multipart/form-data",
|
||||
"X-Token" : this.jwt
|
||||
}
|
||||
})
|
||||
},
|
||||
async get_jwt(){
|
||||
await 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().then(() => {
|
||||
this.export_url = `/api/config/db/export?token=${this.jwt}`
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
167
app/backend/src/views/edit-configuration.vue
Executable file
167
app/backend/src/views/edit-configuration.vue
Executable file
@@ -0,0 +1,167 @@
|
||||
<template>
|
||||
<div class="backend-content" id="content">
|
||||
<div v-bind:class="{ 'alert-toaster-visible' : toaster.show, 'alert-toaster-hidden' : !toaster.show }">{{toaster.message}}</div>
|
||||
<div class="column col-8 col-xs-12">
|
||||
<h3 class="s-title">Configuration </h3>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="user-login">Device UUID (read-only)</label>
|
||||
<div class="input-group">
|
||||
<input class="form-input read-only" id="device-id" v-model="config.device_uuid" readonly="readonly">
|
||||
</div>
|
||||
</div>
|
||||
<h5 class="s-subtitle">Device configuration</h5>
|
||||
<div class="form-group">
|
||||
<label class="form-switch">
|
||||
<input type="checkbox" @change="switch_config('frontend', 'virtual_keyboard')" v-model="config.frontend.virtual_keyboard">
|
||||
<i class="form-icon"></i> Use virtual keyboard (for touch screen)
|
||||
</label>
|
||||
<label class="form-switch">
|
||||
<input type="checkbox" @change="switch_config('frontend', 'shutdown_option')" v-model="config.frontend.shutdown_option">
|
||||
<i class="form-icon"></i> Allow the end-user to shutdown the device.
|
||||
</label>
|
||||
<label class="form-switch">
|
||||
<input type="checkbox" @change="switch_config('frontend', 'backend_option')" v-model="config.frontend.backend_option">
|
||||
<i class="form-icon"></i> Allow the end-user to access to the backend.
|
||||
</label>
|
||||
<label class="form-switch">
|
||||
<input type="checkbox" @change="switch_config('network', 'tokenized_ssids')" v-model="config.network.tokenized_ssids">
|
||||
<i class="form-icon"></i> Use tokenized SSIDs (eg. [ssid-name]-[hex-str]).
|
||||
</label>
|
||||
<label class="form-switch">
|
||||
<input type="checkbox" @change="switch_config('frontend', 'download_links')" v-model="config.frontend.download_links">
|
||||
<i class="form-icon"></i> Use in-browser download for network captures.
|
||||
</label>
|
||||
<label class="form-switch">
|
||||
<input type="checkbox" @change="switch_config('frontend', 'sparklines')" v-model="config.frontend.sparklines">
|
||||
<i class="form-icon"></i> Show background sparklines during the capture.
|
||||
</label>
|
||||
<label class="form-switch">
|
||||
<input type="checkbox" @change="switch_config('frontend', 'remote_access')" v-model="config.frontend.remote_access">
|
||||
<i class="form-icon"></i> Allow remote access to the frontend.
|
||||
</label>
|
||||
<label class="form-switch">
|
||||
<input type="checkbox" @change="switch_config('backend', 'remote_access')" v-model="config.backend.remote_access">
|
||||
<i class="form-icon"></i> Allow remote access to the backend.
|
||||
</label>
|
||||
</div>
|
||||
<h5 class="s-subtitle">User credentials</h5>
|
||||
<div class="form-group">
|
||||
<div class="column col-10 col-xs-12">
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="user-login">User login</label>
|
||||
<div class="input-group">
|
||||
<input class="form-input" id="user-login" type="text" v-model="config.backend.login">
|
||||
<button class="btn btn-primary input-group-btn px150" @click="change_login()">Update it</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="user-login">User password</label>
|
||||
<div class="input-group">
|
||||
<input class="form-input" id="user-login" type="password" placeholder="●●●●●●" v-model="config.backend.password">
|
||||
<button class="btn btn-primary input-group-btn px150" @click="change_password()">Update it</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="whitespace"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'edit-configuration',
|
||||
data() {
|
||||
return {
|
||||
config: {},
|
||||
check_certificate: false,
|
||||
certificate: "",
|
||||
iocs_tags: [],
|
||||
toaster: { show: false, message : "", type : null }
|
||||
}
|
||||
},
|
||||
props: {},
|
||||
methods: {
|
||||
switch_config: function(cat, key) {
|
||||
axios.get(`/api/config/switch/${cat}/${key}`, {
|
||||
timeout: 10000,
|
||||
headers: { 'X-Token': this.jwt }
|
||||
}).then(response => {
|
||||
if (response.data.status) {
|
||||
if (response.data.message == "Key switched to true") {
|
||||
this.toaster = { show : true, message : "Configuration updated", type : "success" }
|
||||
setTimeout(function () { this.toaster = { show : false } }.bind(this), 1000)
|
||||
this.config[cat][key] = true
|
||||
} else if (response.data.message == "Key switched to false") {
|
||||
this.toaster = { show : true, message : "Configuration updated", type : "success" }
|
||||
setTimeout(function () { this.toaster = { show : false } }.bind(this), 1000)
|
||||
this.config[cat][key] = false
|
||||
} else {
|
||||
this.toaster = { show : true, message : "The key doesn't exist", type : "error" }
|
||||
setTimeout(function () { this.toaster = { show : false } }.bind(this), 1000)
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
},
|
||||
load_config: function() {
|
||||
axios.get(`/api/config/list`, {
|
||||
timeout: 10000,
|
||||
headers: { 'X-Token': this.jwt }
|
||||
}).then(response => {
|
||||
if (response.data) {
|
||||
this.config = response.data
|
||||
this.config.backend.password = ""
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
},
|
||||
async get_jwt() {
|
||||
await axios.get(`/api/get-token`, { timeout: 10000 })
|
||||
.then(response => {
|
||||
if (response.data.token) {
|
||||
this.jwt = response.data.token
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
},
|
||||
change_login: function() {
|
||||
axios.get(`/api/config/edit/backend/login/${this.config.backend.login}`, {
|
||||
timeout: 10000,
|
||||
headers: { 'X-Token': this.jwt }
|
||||
}).then(response => {
|
||||
if (response.data.status) {
|
||||
this.toaster = { show : true, message : "Login changed", type : "success" }
|
||||
setTimeout(function () { this.toaster = { show : false } }.bind(this), 1000)
|
||||
} else {
|
||||
this.toaster = { show : true, message : "Login not changed", type : "error" }
|
||||
setTimeout(function () { this.toaster = { show : false } }.bind(this), 1000)
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
},
|
||||
change_password: function() {
|
||||
axios.get(`/api/config/edit/backend/password/${this.config.backend.password}`, {
|
||||
timeout: 10000,
|
||||
headers: { 'X-Token': this.jwt }
|
||||
}).then(response => {
|
||||
if (response.data.status) {
|
||||
this.toaster = { show : true, message : "Password changed", type : "success" }
|
||||
setTimeout(function () { this.toaster = { show : false } }.bind(this), 1000)
|
||||
} else {
|
||||
this.toaster = { show : true, message : "Password not changed", type : "error" }
|
||||
setTimeout(function () { this.toaster = { show : false } }.bind(this), 1000)
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
}
|
||||
},
|
||||
created: function() {
|
||||
this.get_jwt().then(() => {
|
||||
this.load_config();
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
18
app/backend/src/views/home.vue
Executable file
18
app/backend/src/views/home.vue
Executable file
@@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<div class="backend-content" id="content">
|
||||
<div class="column col-8 col-xs-12">
|
||||
<div class="container">
|
||||
<h3 class="s-title">Getting started</h3>
|
||||
<br />
|
||||
<p>SpyGuard is a forked and enhanced version of TinyCheck, an application developed by Kaspersky. SpyGuard's main objective is to detect signs of compromise by monitoring network flows transmitted by a device.</p>
|
||||
<p>As it uses WiFi, SpyGuard can be used against a wide range of devices, such as smartphones, laptops, IOTs or workstations. To do its job, the analysis engine of SpyGuard is using Indicators of Compromise (IOCs), anomaly detection and is supported by Suricata. </p>
|
||||
|
||||
<p>This backend lets you configure your SpyGuard instance. You can push some IOCs for detection and whitelist elements which can be seen during legit communications in order to prevent false positives.</p>
|
||||
<p>_</p>
|
||||
</div>
|
||||
<div class="backend-footer container grid-lg" id="copyright">
|
||||
<p>For any question, bug report or feedback, please contact the <a href="mailto:spyguard@protonmail.com" target="_blank">SpyGuard's Team</a> or open an issue on the <a href="https://github.com/SpyGuard/spyguard/issues" target="_blank">SpyGuard Github repository</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
175
app/backend/src/views/instance-misp.vue
Executable file
175
app/backend/src/views/instance-misp.vue
Executable file
@@ -0,0 +1,175 @@
|
||||
<template>
|
||||
<div class="backend-content" id="content">
|
||||
<div class="column col-8 col-xs-12">
|
||||
<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>
|
||||
</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">
|
||||
<div class="misp-form">
|
||||
<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="OqHSMyAuth3ntic4t10nK3y0MyAuth3ntic4t10nK3y3iiH" v-model="mispinst.key" required>
|
||||
<label class="misp-label" v-if="mispinst.url.startsWith('https://')">Verify certificate? </label><span v-if="mispinst.url.startsWith('https://')"></span>
|
||||
<div style="flex:50%" v-if="mispinst.url.startsWith('https://')"><label class="form-switch">
|
||||
<input type="checkbox" v-model="mispinst.ssl">
|
||||
<i class="form-icon"></i>
|
||||
</label></div>
|
||||
</div>
|
||||
<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. Redirecting to instances in 2 seconds.
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" v-if="error">
|
||||
<div class="toast toast-error">
|
||||
✗ MISP instance not added. {{error}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" v-if="tabs.instances">
|
||||
<div v-if="instances.length">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Server</th>
|
||||
<th>Authkey</th>
|
||||
<th>Status</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<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="instance-online tooltip" :data-tooltip="i.lastsync">✓ ONLINE</span>
|
||||
<span v-else class="instance-offline tooltip" :data-tooltip="i.lastsync">⚠ OFFLINE</span>
|
||||
</td>
|
||||
<td><button class="btn btn-sm" v-on:click="delete_instance(i)">Delete</button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="empty">
|
||||
<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 {
|
||||
error:false,
|
||||
loading:false,
|
||||
added:false,
|
||||
mispinst:{ name:'', url:'',key:'', ssl:false },
|
||||
instances:[],
|
||||
tabs: { "addmisp" : true, "instances" : false },
|
||||
jwt:""
|
||||
}
|
||||
},
|
||||
props: { },
|
||||
methods: {
|
||||
add_instance: function()
|
||||
{
|
||||
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.added = true;
|
||||
setTimeout(function (){
|
||||
this.switch_tab('instances')
|
||||
this.mispinst = { name:'', url:'',key:'', ssl:false }
|
||||
this.added = false
|
||||
}.bind(this), 2000);
|
||||
} else {
|
||||
this.error = response.data.message;
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
}
|
||||
},
|
||||
delete_instance(elem)
|
||||
{
|
||||
axios.get(`/api/misp/delete/${elem.id}`, { timeout: 10000, headers: {'X-Token': this.jwt} })
|
||||
.then(response => {
|
||||
if(response.data.status){
|
||||
this.instances = this.instances.filter(function(el) { return el != elem; });
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
},
|
||||
get_misp_instances()
|
||||
{
|
||||
this.loading = true;
|
||||
this.instances = []
|
||||
axios.get(`/api/misp/get_all`, { timeout: 10000, headers: {'X-Token': this.jwt} })
|
||||
.then(response => {
|
||||
if(response.data.results){
|
||||
this.instances = response.data.results;
|
||||
this.instances.forEach(e => {
|
||||
var lastsync = parseInt((Date.now()/1000 - e.lastsync) / 86400)
|
||||
e.lastsync = (!lastsync)? "Synchronized today" : `Synchronized ${lastsync} day(s) ago`
|
||||
} )
|
||||
}
|
||||
this.loading = false
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
},
|
||||
switch_tab: function(tab) {
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
166
app/backend/src/views/instance-watchers.vue
Executable file
166
app/backend/src/views/instance-watchers.vue
Executable file
@@ -0,0 +1,166 @@
|
||||
<template>
|
||||
<div class="backend-content" id="content">
|
||||
<div class="column col-8 col-xs-12">
|
||||
<h3 class="s-title">Manage watchers instances</h3>
|
||||
<ul class="tab tab-block">
|
||||
<li class="tab-item">
|
||||
<a href="#" v-on:click="switch_tab('addwatcher')" v-bind:class="{ active: tabs.addwatcher }">Add watcher</a>
|
||||
</li>
|
||||
<li class="tab-item">
|
||||
<a href="#" v-on:click="switch_tab('instances')" v-bind:class="{ active: tabs.instances }">Existing watchers</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div v-if="tabs.addwatcher">
|
||||
<div class="misp-form">
|
||||
<label class="misp-label">Watcher name</label><span></span>
|
||||
<input class="form-input" type="text" ref="watcher_name" placeholder="My incredible watcher" v-model="watcher.name" required>
|
||||
<label class="misp-label">Watcher URL</label><span></span>
|
||||
<input class="form-input" type="text" ref="watcher_url" placeholder="https://url.of.my.watcher.com/watcher.json" v-model="watcher.url" required>
|
||||
<label class="misp-label">Watcher Type</label><span></span>
|
||||
<select class="form-select width-full" placeholder="test" v-model="watcher.type">
|
||||
<option value="iocs">IOCs</option>
|
||||
<option value="whitelist">Whitelist</option>
|
||||
</select>
|
||||
</div>
|
||||
<button class="btn-primary btn col-12" v-on:click="add_instance()">Add watcher</button>
|
||||
<div class="form-group" v-if="added">
|
||||
<div class="toast toast-success">
|
||||
✓ Watcher added successfully. Redirecting to watchers in 2 seconds.
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" v-if="error">
|
||||
<div class="toast toast-error">
|
||||
✗ Watcher not added. {{error}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" v-if="tabs.instances">
|
||||
<div v-if="instances.length">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Type</th>
|
||||
<th>Name</th>
|
||||
<th>Status</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="i in instances" v-bind:key="i.id">
|
||||
<td>{{ i.type.toUpperCase() }}</td>
|
||||
<td>{{ i.name }}</td>
|
||||
<td>
|
||||
<span v-if="i.status" class="instance-online">✓ ONLINE</span>
|
||||
<span v-else class="instance-offline">⚠ OFFLINE</span>
|
||||
</td>
|
||||
<td><button class="btn btn-sm" v-on:click="delete_instance(i)">Delete</button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="empty">
|
||||
<div v-if="loading">
|
||||
<p class="empty-title h5">
|
||||
<span class="loading loading-lg"></span>
|
||||
</p>
|
||||
<p class="empty-subtitle">Testing and loading the watchers.</p>
|
||||
</div>
|
||||
<div v-else>
|
||||
<p class="empty-title h5">No watcher found.</p>
|
||||
<p class="empty-subtitle">Do not hesitate to add a watcher.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'managewatchers',
|
||||
data() {
|
||||
return {
|
||||
error:false,
|
||||
loading:false,
|
||||
added:false,
|
||||
watcher:{ name:'', url:'', type:"iocs" },
|
||||
instances:[],
|
||||
tabs: { "addwatcher" : true, "instances" : false },
|
||||
jwt:""
|
||||
}
|
||||
},
|
||||
props: { },
|
||||
methods: {
|
||||
add_instance: function()
|
||||
{
|
||||
this.added = false;
|
||||
this.error = false;
|
||||
if (this.watcher.name && this.watcher.url && this.watcher.type)
|
||||
{
|
||||
axios.post(`/api/watchers/add`, { data: { instance: this.watcher } }, { headers: {'X-Token': this.jwt} }).then(response => {
|
||||
if(response.data.status){
|
||||
this.added = true;
|
||||
setTimeout(function (){
|
||||
this.switch_tab('instances')
|
||||
this.watcher = { name:'', url:'' }
|
||||
this.added = false
|
||||
}.bind(this), 2000);
|
||||
} else {
|
||||
this.error = response.data.message;
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
}
|
||||
},
|
||||
delete_instance(elem)
|
||||
{
|
||||
axios.get(`/api/watchers/delete/${elem.id}`, { timeout: 10000, headers: {'X-Token': this.jwt} })
|
||||
.then(response => {
|
||||
if(response.data.status){
|
||||
this.instances = this.instances.filter(function(el) { return el != elem; });
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
},
|
||||
get_watchers_instances()
|
||||
{
|
||||
this.loading = true;
|
||||
this.instances = []
|
||||
axios.get(`/api/watchers/get_all`, { timeout: 10000, headers: {'X-Token': this.jwt} })
|
||||
.then(response => {
|
||||
if(response.data.results){
|
||||
this.instances = response.data.results;
|
||||
}
|
||||
this.loading = false
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
},
|
||||
switch_tab: function(tab) {
|
||||
Object.keys(this.tabs).forEach(key => {
|
||||
if( key == tab ){
|
||||
this.tabs[key] = true
|
||||
if (key == "instances") this.get_watchers_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();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
254
app/backend/src/views/iocs-manage.vue
Executable file
254
app/backend/src/views/iocs-manage.vue
Executable file
@@ -0,0 +1,254 @@
|
||||
<template>
|
||||
<div class="backend-content" id="content">
|
||||
<div class="column col-8 col-xs-12">
|
||||
<h3 class="s-title">Manage IOCs</h3>
|
||||
<ul class="tab tab-block">
|
||||
<li class="tab-item">
|
||||
<a href="#" v-on:click="switch_tab('bulk')" v-bind:class="{ active: tabs.bulk }">Bulk import</a>
|
||||
</li>
|
||||
<li class="tab-item">
|
||||
<a href="#" v-on:click="switch_tab('file')" v-bind:class="{ active: tabs.file }">File import</a>
|
||||
</li>
|
||||
<li class="tab-item">
|
||||
<a href="#" v-on:click="switch_tab('export')" v-bind:class="{ active: tabs.export }">Export IOCs</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div v-if="tabs.export">
|
||||
<iframe :src="export_url" class="frame-export"></iframe>
|
||||
</div>
|
||||
<div v-if="tabs.file">
|
||||
<label class="form-upload empty" for="upload">
|
||||
<input type="file" class="upload-field" id="upload" @change="import_from_file">
|
||||
<p class="empty-title h5">Drop or select a file to import.</p>
|
||||
<p class="empty-subtitle">The file needs to be an export from a SpyGuard instance.</p>
|
||||
</label>
|
||||
</div>
|
||||
<div v-if="tabs.bulk">
|
||||
<div class="columns">
|
||||
<div class="column col-4 col-xs-4">
|
||||
<div class="form-group">
|
||||
<select class="form-select" v-model="tag">
|
||||
<option value="">IOC(s) Tag</option>
|
||||
<option v-for="t in tags" :value="t" :key="t">
|
||||
{{ t.toUpperCase() }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column col-4 col-xs-4">
|
||||
<div class="form-group">
|
||||
<select class="form-select width-full" v-model="type">
|
||||
<option value="">IOC(s) Type</option>
|
||||
<option value="unknown">Multiple (regex parsing)</option>
|
||||
<option v-for="t in types" :value="t.type" :key="t.type">
|
||||
{{ t.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column col-4 col-xs-4">
|
||||
<div class="form-group">
|
||||
<select class="form-select width-full" v-model="tlp">
|
||||
<option value="">IOC(s) TLP</option>
|
||||
<option value="white">TLP:WHITE</option>
|
||||
<option value="green">TLP:GREEN</option>
|
||||
<option value="amber">TLP:AMBER</option>
|
||||
<option value="red">TLP:RED</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<textarea class="form-input" id="input-example-3" placeholder="Paste your Indicators of Compromise here" rows="15" v-model="iocs"></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button class="btn-primary btn col-12" v-on:click="import_from_bulk()">Import the IOCs</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" v-if="imported.length>0">
|
||||
<div class="toast toast-success">
|
||||
✓ {{imported.length}} IOC<span v-if="errors.length>1">s</span> imported successfully.
|
||||
</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: 'manageiocs',
|
||||
data() {
|
||||
return {
|
||||
type:"",
|
||||
tag:"",
|
||||
tlp:"",
|
||||
iocs:"",
|
||||
types:[],
|
||||
tags:[],
|
||||
errors:[],
|
||||
imported:[],
|
||||
type_tag_error: false,
|
||||
wrong_ioc_file: false,
|
||||
tabs: { "bulk" : true, "file" : false, "export" : false },
|
||||
jwt:"",
|
||||
export_url:"",
|
||||
config: {},
|
||||
watcher: ""
|
||||
}
|
||||
},
|
||||
props: { },
|
||||
methods: {
|
||||
import_from_bulk: function() {
|
||||
this.errors = []
|
||||
this.imported = []
|
||||
if (this.tag != "" && this.type != "" && this.tlp != ""){
|
||||
this.iocs.match(/[^\r\n]+/g).forEach(ioc => {
|
||||
this.import_ioc(this.tag, this.type, this.tlp, ioc);
|
||||
});
|
||||
this.iocs = "";
|
||||
} else {
|
||||
this.type_tag_error = true
|
||||
}
|
||||
},
|
||||
import_ioc: function(tag, type, tlp, ioc) {
|
||||
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(")", "")
|
||||
}
|
||||
axios.get(`/api/ioc/add/${type.trim()}/${tag.trim()}/${tlp.trim()}/${ioc}`, { timeout: 10000, 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)))
|
||||
}
|
||||
},
|
||||
delete_watcher: function(watcher) {
|
||||
var i = this.config.watchers.indexOf(watcher);
|
||||
this.config.watchers.splice(i, 1);
|
||||
},
|
||||
add_watcher: function() {
|
||||
this.config.watchers.push(this.watcher);
|
||||
this.watcher = "";
|
||||
},
|
||||
enrich_selects: function() {
|
||||
axios.get(`/api/ioc/get/tags`, { timeout: 10000, headers: {'X-Token': this.jwt} })
|
||||
.then(response => {
|
||||
if(response.data.tags) this.tags = response.data.tags
|
||||
})
|
||||
.catch(err => (console.log(err)));
|
||||
axios.get(`/api/ioc/get/types`, { timeout: 10000, headers: {'X-Token': this.jwt} })
|
||||
.then(response => {
|
||||
if(response.data.types) this.types = response.data.types
|
||||
})
|
||||
.catch(err => (console.log(err)));
|
||||
},
|
||||
switch_tab: function(tab) {
|
||||
this.errors = []
|
||||
this.imported = []
|
||||
|
||||
Object.keys(this.tabs).forEach(key => {
|
||||
if( key == tab ){
|
||||
this.tabs[key] = true
|
||||
} else {
|
||||
this.tabs[key] = false
|
||||
}
|
||||
});
|
||||
},
|
||||
import_from_file: function(ev) {
|
||||
this.errors = []
|
||||
this.imported = []
|
||||
|
||||
const file = ev.target.files[0];
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = e => this.$emit("load", e.target.result);
|
||||
reader.onload = () => {
|
||||
try {
|
||||
JSON.parse(reader.result).iocs.forEach(ioc => {
|
||||
this.import_ioc(ioc["tag"], ioc["type"], ioc["tlp"], ioc["value"])
|
||||
})
|
||||
} catch (error) {
|
||||
this.wrong_ioc_file = true
|
||||
}
|
||||
|
||||
}
|
||||
reader.readAsText(file);
|
||||
},
|
||||
async get_jwt(){
|
||||
await axios.get(`/api/get-token`, { timeout: 10000 })
|
||||
.then(response => {
|
||||
if(response.data.token){
|
||||
this.jwt = response.data.token
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
},
|
||||
load_config: function() {
|
||||
axios.get(`/api/config/list`, {
|
||||
timeout: 10000,
|
||||
headers: { 'X-Token': this.jwt }
|
||||
}).then(response => {
|
||||
if (response.data) {
|
||||
this.config = response.data
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
}
|
||||
},
|
||||
created: function() {
|
||||
this.get_jwt().then(() => {
|
||||
this.enrich_selects();
|
||||
this.load_config();
|
||||
this.export_url = `/api/ioc/export?token=${this.jwt}`
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
127
app/backend/src/views/iocs-search.vue
Executable file
127
app/backend/src/views/iocs-search.vue
Executable file
@@ -0,0 +1,127 @@
|
||||
<template>
|
||||
<div class="backend-content" id="content">
|
||||
<div class="column col-12 col-xs-12">
|
||||
<h3 class="s-title">Search IOCs</h3>
|
||||
<div class="form-group">
|
||||
<textarea class="form-input" id="input-example-3" placeholder="Paste your IOCs here" rows="3" v-model="iocs"></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button class="btn btn-primary col-12" v-on:click="search_iocs()">Search</button>
|
||||
</div>
|
||||
<div class="form-group" v-if="results.length>0 ">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Indicator</th>
|
||||
<th>Tag</th>
|
||||
<th>TLP</th>
|
||||
<th>Source</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="r in results" :key="r.tlp">
|
||||
<td>{{ r.value }}</td>
|
||||
<td class="upper">{{ r.tag }}</td>
|
||||
<td><label :class="['tlp-' + r.tlp]">{{ r.tlp }}</label></td>
|
||||
<td class="capi">{{ r.source }}</td>
|
||||
<td><button class="btn btn-sm" v-on:click="remove(r)">Delete</button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div v-else-if="first_search==false">
|
||||
<div v-if="loading">
|
||||
<div class="empty">
|
||||
<p class="empty-title h5">
|
||||
<span class="loading loading-lg"></span>
|
||||
</p>
|
||||
<p class="empty-subtitle">Finding your IOC(s)...</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="empty">
|
||||
<p class="empty-title h5">IOC<span v-if="this.iocs.match(/[^\r\n]+/g).length>1">s</span> not found.</p>
|
||||
<p class="empty-subtitle">Try wildcard search to expend your search.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'iocs-search',
|
||||
data() {
|
||||
return {
|
||||
results: [],
|
||||
first_search: true,
|
||||
jwt:"",
|
||||
loading:false
|
||||
}
|
||||
},
|
||||
props: { },
|
||||
methods: {
|
||||
search_iocs: function() {
|
||||
this.results = []
|
||||
this.first_search = false
|
||||
this.loading = true;
|
||||
this.iocs.match(/[^\r\n]+/g).forEach(ioc => {
|
||||
ioc = ioc.trim()
|
||||
if("alert " != ioc.slice(0,6)) {
|
||||
ioc = ioc.replace(" ", "")
|
||||
ioc = ioc.replace("[", "")
|
||||
ioc = ioc.replace("]", "")
|
||||
ioc = ioc.replace("\\", "")
|
||||
ioc = ioc.replace("(", "")
|
||||
ioc = ioc.replace(")", "")
|
||||
}
|
||||
axios.get(`/api/ioc/search/${ioc}`, { timeout: 10000, headers: {'X-Token': this.jwt} })
|
||||
.then(response => {
|
||||
if(response.data.results.length>0){
|
||||
this.results = [].concat(this.results, response.data.results);
|
||||
}
|
||||
this.loading = false;
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
});
|
||||
return true;
|
||||
},
|
||||
remove: function(elem){
|
||||
axios.get(`/api/ioc/delete/${elem.id}`, { timeout: 10000, headers: {'X-Token': this.jwt} })
|
||||
.then(response => {
|
||||
if(response.data.status){
|
||||
this.results = this.results.filter(function(el) { return el != elem; });
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
},
|
||||
load_config: function() {
|
||||
axios.get(`/api/config/list`, {
|
||||
timeout: 10000,
|
||||
headers: { 'X-Token': this.jwt }
|
||||
}).then(response => {
|
||||
if (response.data) {
|
||||
this.config = response.data
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
},
|
||||
async get_jwt(){
|
||||
await 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()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
131
app/backend/src/views/network-manage.vue
Executable file
131
app/backend/src/views/network-manage.vue
Executable file
@@ -0,0 +1,131 @@
|
||||
<template>
|
||||
<div class="backend-content" id="content">
|
||||
<div v-bind:class="{ 'alert-toaster-visible' : toaster.show, 'alert-toaster-hidden' : !toaster.show }">{{toaster.message}}</div>
|
||||
<div class="column col-8 col-xs-12">
|
||||
<h3 class="s-title">Network configuration</h3>
|
||||
<h5 class="s-subtitle">Interfaces configuration</h5>
|
||||
<img src="@/assets/network.png" id="network-thumbnail" />
|
||||
<div class="container interfaces-container">
|
||||
<div class="columns">
|
||||
<div class="column col-6">
|
||||
<span class="interface-label">Wireless AP interface</span>
|
||||
<select class="form-select width-full" v-model="iface_in" @change="change_interface('in', iface_in)">
|
||||
<option v-for="iface in config.ifaces_in" :value="iface" :key="iface">
|
||||
{{ iface.toUpperCase() }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="column col-6">
|
||||
<span class="interface-label">Internet link interface</span>
|
||||
<select class="form-select width-full" v-model="iface_out" @change="change_interface('out', iface_out)">
|
||||
<option v-for="iface in config.ifaces_out" :value="iface" :key="iface">
|
||||
{{ iface.toUpperCase() }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h5 class="s-subtitle">Edit SSIDs names</h5>
|
||||
<div class="form-group">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Network name</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="ssid in config.network.ssids" :key="ssid">
|
||||
<td>{{ ssid }}</td>
|
||||
<td><button class="btn btn-sm" v-on:click="delete_ssid(ssid)">Delete</button></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><input class="form-input" v-model="ssid" type="text" placeholder="SSID name"></td>
|
||||
<td><button class="btn btn-sm" @click="add_ssid()">Add</button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'manageinterface',
|
||||
data() {
|
||||
return {
|
||||
config: {},
|
||||
ssid: "",
|
||||
iface_in: "",
|
||||
toaster: { show: false, message : "", type : null }
|
||||
}
|
||||
},
|
||||
props: {},
|
||||
methods: {
|
||||
async get_jwt() {
|
||||
await axios.get(`/api/get-token`, { timeout: 10000 })
|
||||
.then(response => {
|
||||
if (response.data.token) {
|
||||
this.jwt = response.data.token
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
},
|
||||
load_config: function() {
|
||||
axios.get(`/api/config/list`, {
|
||||
timeout: 10000,
|
||||
headers: { 'X-Token': this.jwt }
|
||||
}).then(response => {
|
||||
if (response.data) {
|
||||
this.config = response.data
|
||||
this.iface_in = this.config.network.in
|
||||
this.iface_out = this.config.network.out
|
||||
console.log(this.iface_in);
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
},
|
||||
delete_ssid: function(ssid) {
|
||||
var i = this.config.network.ssids.indexOf(ssid);
|
||||
this.config.network.ssids.splice(i, 1);
|
||||
this.update_ssids();
|
||||
},
|
||||
add_ssid: function() {
|
||||
this.config.network.ssids.push(this.ssid);
|
||||
this.ssid = "";
|
||||
this.update_ssids();
|
||||
},
|
||||
update_ssids: function() {
|
||||
axios.get(`/api/config/edit/network/ssids/${this.config.network.ssids.join("|")}`, {
|
||||
timeout: 10000,
|
||||
headers: { 'X-Token': this.jwt }
|
||||
}).then(response => {
|
||||
if (response.data.status) {
|
||||
this.toaster = { show : true, message : "Configuration updated", type : "success" }
|
||||
setTimeout(function () { this.toaster = { show : false } }.bind(this), 1000)
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
},
|
||||
change_interface: function(type, iface) {
|
||||
axios.get(`/api/config/edit/network/${type}/${iface}`, {
|
||||
timeout: 10000,
|
||||
headers: { 'X-Token': this.jwt }
|
||||
}).then(response => {
|
||||
this.toaster = { show : true, message : "Configuration updated", type : "success" }
|
||||
setTimeout(function () { this.toaster = { show : false } }.bind(this), 1000)
|
||||
if (response.data.status) this.config.network[type] = iface
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
},
|
||||
},
|
||||
created: function() {
|
||||
this.get_jwt().then(() => {
|
||||
this.load_config();
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
85
app/backend/src/views/update.vue
Executable file
85
app/backend/src/views/update.vue
Executable file
@@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<div class="backend-content" id="content">
|
||||
<div class="column col-8 col-xs-12">
|
||||
<br />
|
||||
<p><strong>A new SpyGuard version is available ({{next_version}}).</strong><br />
|
||||
<span v-if="!update_launched">Please click on the button below to update your instance.</span>
|
||||
<span v-if="update_launched&&!update_finished">Updating SpyGuard, please wait. You'll be redirected once updated.</span>
|
||||
<span v-if="update_launched&&update_finished" class="color-green">✓ Update finished!</span>
|
||||
</p>
|
||||
<button class="btn btn-primary" :class="[ update_launched ? 'loading' : '' ]" v-on:click="launch_update()" v-if="!update_finished">Update Spyguard</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'update',
|
||||
data() {
|
||||
return {
|
||||
translation: {},
|
||||
update_launched: null,
|
||||
check_interval: null,
|
||||
next_version: null,
|
||||
current_version: null,
|
||||
update_finished: false,
|
||||
jwt: "",
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
check_version: function() {
|
||||
axios.get('/api/update/get-version', { timeout: 60000, headers: { 'X-Token': this.jwt } })
|
||||
.then(response => {
|
||||
if(response.data.status) {
|
||||
if(response.data.current_version == this.next_version){
|
||||
window.current_version = response.data.current_version
|
||||
this.update_finished = true
|
||||
clearInterval(this.check_interval);
|
||||
setTimeout(function () { window.location.href = "/"; }, 3000)
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => { console.log(error) });
|
||||
},
|
||||
launch_update: function() {
|
||||
axios.get(`/api/update/process`, { timeout: 60000, headers: { 'X-Token': this.jwt } })
|
||||
.then(response => {
|
||||
if(response.data.status) {
|
||||
if(response.data.message == "Update successfully launched"){
|
||||
this.update_launched = true
|
||||
this.check_interval = setInterval(function(){ this.check_version(); }.bind(this), 3000);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => { console.log(error) });
|
||||
},
|
||||
async get_jwt() {
|
||||
await axios.get(`/api/get-token`, { timeout: 10000 })
|
||||
.then(response => {
|
||||
if (response.data.token) {
|
||||
this.jwt = response.data.token
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
},
|
||||
get_versions: function() {
|
||||
axios.get('/api/update/check', { timeout: 60000, headers: { 'X-Token': this.jwt } })
|
||||
.then(response => {
|
||||
if(response.data.status){
|
||||
this.current_version = response.data.current_version
|
||||
this.next_version = response.data.next_version
|
||||
if(this.current_version == this.next_version) window.location.href = "/";
|
||||
}
|
||||
})
|
||||
.catch(error => { console.log(error) });
|
||||
},
|
||||
},
|
||||
created: function() {
|
||||
this.get_jwt().then(() => {
|
||||
this.get_versions();
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
191
app/backend/src/views/whitelist-manage.vue
Executable file
191
app/backend/src/views/whitelist-manage.vue
Executable file
@@ -0,0 +1,191 @@
|
||||
<template>
|
||||
<div class="backend-content" id="content">
|
||||
<div class="column col-8 col-xs-12">
|
||||
<h3 class="s-title">Manage whitelisted elements</h3>
|
||||
<ul class="tab tab-block">
|
||||
<li class="tab-item">
|
||||
<a href="#" v-on:click="switch_tab('bulk')" v-bind:class="{ active: tabs.bulk }">Bulk elements import</a>
|
||||
</li>
|
||||
<li class="tab-item">
|
||||
<a href="#" v-on:click="switch_tab('file')" v-bind:class="{ active: tabs.file }">Import from file</a>
|
||||
</li>
|
||||
<li class="tab-item">
|
||||
<a href="#" v-on:click="switch_tab('export')" v-bind:class="{ active: tabs.export }">Export elements</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div v-if="tabs.export">
|
||||
<iframe :src="export_url" class="frame-export"></iframe>
|
||||
</div>
|
||||
<div v-if="tabs.file">
|
||||
<label class="form-upload empty" for="upload">
|
||||
<input type="file" class="upload-field" id="upload" @change="import_from_file">
|
||||
<p class="empty-title h5">Drop or select a file to import.</p>
|
||||
<p class="empty-subtitle">The file needs to be an whitelist file export from a SpyGuard instance.</p>
|
||||
</label>
|
||||
</div>
|
||||
<div v-if="tabs.bulk">
|
||||
<div class="form-group">
|
||||
<select class="form-select width-full" placeholder="test" v-model="type">
|
||||
<option value="">Elements Type</option>
|
||||
<option value="unknown">Multiple (regex parsing)</option>
|
||||
<option v-for="t in types" :value="t.type" :key="t.type">
|
||||
{{ t.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<textarea class="form-input" id="input-example-3" placeholder="Paste the elements to be whitelisted here" rows="15" v-model="elements"></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button class="btn-primary btn col-12" v-on:click="import_from_bulk()">Whitelist elements</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" v-if="imported.length>0">
|
||||
<div class="toast toast-success">
|
||||
✓ {{imported.length}} IOC<span v-if="errors.length>1">s</span> imported successfully.
|
||||
</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>Element</th>
|
||||
<th>Importation error</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="e in errors" :key="e.element">
|
||||
<td>{{ e.element }}</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: 'manageiocs',
|
||||
data() {
|
||||
return {
|
||||
type:"",
|
||||
elements:"",
|
||||
types:[],
|
||||
errors:[],
|
||||
imported:[],
|
||||
wrong_wh_file: false,
|
||||
tabs: { "bulk" : true, "file" : false, "export" : false },
|
||||
jwt:"",
|
||||
export_url:""
|
||||
}
|
||||
},
|
||||
props: { },
|
||||
methods: {
|
||||
import_from_bulk: function() {
|
||||
this.errors = []
|
||||
this.imported = []
|
||||
if (this.type != ""){
|
||||
this.elements.match(/[^\r\n]+/g).forEach(elem => {
|
||||
this.import_element(this.type, elem);
|
||||
});
|
||||
this.elements = "";
|
||||
} else {
|
||||
this.type_tag_error = true
|
||||
}
|
||||
},
|
||||
import_element: function(type, elem) {
|
||||
if (elem != "" && elem.slice(0,1) != "#"){
|
||||
axios.get(`/api/whitelist/add/${type.trim()}/${elem.trim()}`, {
|
||||
timeout: 10000,
|
||||
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)))
|
||||
}
|
||||
},
|
||||
enrich_types: function() {
|
||||
axios.get(`/api/whitelist/get/types`, { timeout: 10000, headers: {'X-Token': this.jwt} })
|
||||
.then(response => {
|
||||
if(response.data.types) this.types = response.data.types
|
||||
})
|
||||
.catch(err => (console.log(err)));
|
||||
},
|
||||
switch_tab: function(tab) {
|
||||
this.errors = []
|
||||
this.imported = []
|
||||
|
||||
Object.keys(this.tabs).forEach(key => {
|
||||
if( key == tab ){
|
||||
this.tabs[key] = true
|
||||
} else {
|
||||
this.tabs[key] = false
|
||||
}
|
||||
});
|
||||
},
|
||||
import_from_file: function(ev) {
|
||||
this.errors = []
|
||||
this.imported = []
|
||||
|
||||
const file = ev.target.files[0];
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = e => this.$emit("load", e.target.result);
|
||||
reader.onload = () => {
|
||||
try {
|
||||
JSON.parse(reader.result).elements.forEach(elem => {
|
||||
this.import_element(elem["type"], elem["element"])
|
||||
})
|
||||
} catch (error) {
|
||||
this.wrong_wh_file = true
|
||||
}
|
||||
|
||||
}
|
||||
reader.readAsText(file);
|
||||
},
|
||||
async get_jwt(){
|
||||
await 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().then(() => {
|
||||
this.enrich_types();
|
||||
this.export_url = `/api/whitelist/export?token=${this.jwt}`
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
94
app/backend/src/views/whitelist-search.vue
Executable file
94
app/backend/src/views/whitelist-search.vue
Executable file
@@ -0,0 +1,94 @@
|
||||
<template>
|
||||
<div class="backend-content" id="content">
|
||||
<div class="column col-12 col-xs-12">
|
||||
<h3 class="s-title">Search whitelisted elements</h3>
|
||||
<div class="form-group">
|
||||
<textarea class="form-input" id="input-example-3" placeholder="Paste the elements here" rows="3" v-model="elements"></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button class="btn btn-primary col-12" v-on:click="search_elements()">Search</button>
|
||||
</div>
|
||||
<div class="form-group" v-if="results.length>0 ">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Element</th>
|
||||
<th>Element type</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="r in results" :key="r.element">
|
||||
<td>{{ r.element }}</td>
|
||||
<td>{{ r.type }}</td>
|
||||
<td><button class="btn btn-sm" v-on:click="remove(r)">Delete</button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div v-else-if="first_search==false">
|
||||
<div class="empty">
|
||||
<p class="empty-title h5">Element<span v-if="this.elements.match(/[^\r\n]+/g).length>1">s</span> not found.</p>
|
||||
<p class="empty-subtitle">Try wildcard search to expend your search.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'elements-search',
|
||||
data() {
|
||||
return {
|
||||
results: [],
|
||||
first_search: true,
|
||||
jwt:""
|
||||
}
|
||||
},
|
||||
props: { },
|
||||
methods: {
|
||||
search_elements: function() {
|
||||
this.results = []
|
||||
this.first_search = false
|
||||
this.elements.match(/[^\r\n]+/g).forEach(elem => {
|
||||
axios.get(`/api/whitelist/search/${elem.trim()}`, {
|
||||
timeout: 10000,
|
||||
headers: {'X-Token': this.jwt}
|
||||
}).then(response => {
|
||||
if(response.data.results.length>0){
|
||||
this.results = [].concat(this.results, response.data.results);
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
});
|
||||
return true;
|
||||
},
|
||||
remove: function(elem){
|
||||
axios.get(`/api/whitelist/delete/${elem.id}`, {
|
||||
timeout: 10000,
|
||||
headers: {'X-Token': this.jwt}
|
||||
}).then(response => {
|
||||
if(response.data.status){
|
||||
this.results = this.results.filter(function(el) { return el != elem; });
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
},
|
||||
async get_jwt(){
|
||||
await 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()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user