First commit!

This commit is contained in:
sda
2022-11-06 15:51:33 +01:00
parent 283cf9630f
commit 64daa44e9f
225 changed files with 94329 additions and 1 deletions

177
app/backend/src/App.vue Executable file
View 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
View 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;
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 805 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 784 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

1
app/backend/src/assets/spectre-exp.min.css vendored Executable file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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
View 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
View 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

View 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>

View 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>

View 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
View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>