Merge pull request #74 from KasperskyLab/dev

PR of the version v0.7-test
This commit is contained in:
Félix Aimé
2021-06-15 15:37:12 +02:00
committed by GitHub
33 changed files with 1060 additions and 387 deletions

View File

@@ -25,9 +25,6 @@
<li class="menu-item"> <li class="menu-item">
<span @click="$router.push('/device/db')">Manage database</span> <span @click="$router.push('/device/db')">Manage database</span>
</li> </li>
<!-- <li class="menu-item">
<span @click="$router.push('/device/user')">User configuration</a>
</li> -->
</ul> </ul>
</div> </div>
</div> </div>
@@ -42,6 +39,9 @@
<li class="menu-item"> <li class="menu-item">
<span @click="$router.push('/iocs/search')">Search IOCs</span> <span @click="$router.push('/iocs/search')">Search IOCs</span>
</li> </li>
<li class="menu-item">
<span @click="$router.push('/iocs/misp')">MISP Instances</span>
</li>
</ul> </ul>
</div> </div>
</div> </div>
@@ -95,4 +95,4 @@
.fade-leave-active { .fade-leave-active {
opacity: 0 opacity: 0
} }
</style> </style>

View File

@@ -650,4 +650,128 @@ h4, h5 {
.upper { .upper {
text-transform: uppercase; text-transform: uppercase;
}
/*** MISP CSS ***/
.misp-form {
/* Using CSS Grid to lay out the elements in two-dimensions: */
display: grid;
/* specifying a 0.2em gutter/gap between adjacent elements: */
gap: 0.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;
}
.misp-offline {
background-color: #e85600;
color: #FFF;
font-size: 11px;
border-radius: 3px;
padding:3px 6px 3px 6px;
cursor: help;
}
.misp-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
} }

View File

@@ -34,6 +34,12 @@ const routes = [
component: () => import('../views/iocs-manage.vue'), component: () => import('../views/iocs-manage.vue'),
props: true props: true
}, },
{
path: '/iocs/misp',
name: 'iocs-manage',
component: () => import('../views/iocs-misp.vue'),
props: true
},
{ {
path: '/iocs/search', path: '/iocs/search',
name: 'iocs-search', name: 'iocs-search',

View File

@@ -35,6 +35,10 @@
<input type="checkbox" @change="switch_config('frontend', 'virtual_keyboard')" v-model="config.frontend.virtual_keyboard"> <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) <i class="form-icon"></i> Use virtual keyboard (for touch screen)
</label> </label>
<label class="form-switch">
<input type="checkbox" @change="switch_config('frontend', 'choose_net')" v-model="config.frontend.choose_net">
<i class="form-icon"></i> Allow the end-user to choose the network even if connected.
</label>
<label class="form-switch"> <label class="form-switch">
<input type="checkbox" @change="switch_config('frontend', 'reboot_option')" v-model="config.frontend.reboot_option"> <input type="checkbox" @change="switch_config('frontend', 'reboot_option')" v-model="config.frontend.reboot_option">
<i class="form-icon"></i> Allow the end-user to reboot the device from the interface. <i class="form-icon"></i> Allow the end-user to reboot the device from the interface.

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="misp-online tooltip" :data-tooltip="i.lastsync"> ONLINE</span>
<span v-else class="misp-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

@@ -13,27 +13,37 @@
<thead> <thead>
<tr> <tr>
<th>Indicator</th> <th>Indicator</th>
<th>Type</th>
<th>Tag</th> <th>Tag</th>
<th>TLP</th> <th>TLP</th>
<th> </th> <th>Source</th>
<th>Action</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="r in results" :key="r.tlp"> <tr v-for="r in results" :key="r.tlp">
<td>{{ r.value }}</td> <td>{{ r.value }}</td>
<td class="capi">{{ r.type }}</td>
<td class="upper">{{ r.tag }}</td> <td class="upper">{{ r.tag }}</td>
<td><label :class="['tlp-' + r.tlp]">{{ r.tlp }}</label></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> <td><button class="btn btn-sm" v-on:click="remove(r)">Delete</button></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> </div>
<div v-else-if="first_search==false"> <div v-else-if="first_search==false">
<div class="empty"> <div v-if="loading">
<p class="empty-title h5">IOC<span v-if="this.iocs.match(/[^\r\n]+/g).length>1">s</span> not found.</p> <div class="empty">
<p class="empty-subtitle">Try wildcard search to expend your search.</p> <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>
</div> </div>
@@ -49,7 +59,8 @@ export default {
return { return {
results: [], results: [],
first_search: true, first_search: true,
jwt:"" jwt:"",
loading:false
} }
}, },
props: { }, props: { },
@@ -57,6 +68,7 @@ export default {
search_iocs: function() { search_iocs: function() {
this.results = [] this.results = []
this.first_search = false this.first_search = false
this.loading = true;
this.iocs.match(/[^\r\n]+/g).forEach(ioc => { this.iocs.match(/[^\r\n]+/g).forEach(ioc => {
ioc = ioc.trim() ioc = ioc.trim()
if("alert " != ioc.slice(0,6)) { if("alert " != ioc.slice(0,6)) {
@@ -72,6 +84,7 @@ export default {
if(response.data.results.length>0){ if(response.data.results.length>0){
this.results = [].concat(this.results, response.data.results); this.results = [].concat(this.results, response.data.results);
} }
this.loading = false;
}) })
.catch(err => (console.log(err))) .catch(err => (console.log(err)))
}); });

View File

@@ -128,8 +128,8 @@
} }
.report-wrapper { .report-wrapper {
width:90%;
margin:auto; margin:auto;
padding-bottom:1px;
} }
.device-ctx { .device-ctx {
@@ -212,10 +212,11 @@
.report-wrapper { .report-wrapper {
width:60%; width:60%;
margin:auto; margin:auto;
padding-bottom: 1px;
} }
.device-ctx { .device-ctx {
padding:15px; padding:15px 0px 15px 0px;
margin:auto; margin:auto;
} }
@@ -303,6 +304,17 @@ footer {
padding-left:40px; padding-left:40px;
} }
.device-ctx-legend {
font-size: 14px;
color: #a6a6a6;
margin-top: 10px;
padding-top: 10px;
background-color: #fbfbfb;
padding: 10px;
border-radius: 5px;
}
.group-title { .group-title {
text-transform: uppercase; text-transform: uppercase;
color : #999; color : #999;

View File

@@ -50,8 +50,11 @@
"low_msg": "Només teniu {nb} alertes baixes, <br /> Si us plau comproveu-les.", "low_msg": "Només teniu {nb} alertes baixes, <br /> Si us plau comproveu-les.",
"save_report": "Desa l'informe", "save_report": "Desa l'informe",
"report_of": "Informe de", "report_of": "Informe de",
"ip_address": "Adreça IP", "ip_address": "Adreça IP:",
"mac_address": "Adreça MAC", "mac_address": "Adreça MAC:",
"pcap_sha1": "SHA1:",
"capture_started": "La captura va començar a:",
"capture_ended": "La captura va acabar el:",
"high": "alt", "high": "alt",
"moderat": "moderat", "moderat": "moderat",
"low": "baix" "low": "baix"

View File

@@ -50,8 +50,11 @@
"low_msg": "Sie haben nur {nb} Warnungen der Stufe \"Niedrig\":<br> Überprüfen Sie sie gerne.", "low_msg": "Sie haben nur {nb} Warnungen der Stufe \"Niedrig\":<br> Überprüfen Sie sie gerne.",
"save_report": "Bericht speichern", "save_report": "Bericht speichern",
"report_of": "Bericht zu", "report_of": "Bericht zu",
"ip_address": "IP-Adresse", "ip_address": "IP-Adresse:",
"mac_address": "MAC-Adresse", "mac_address": "MAC-Adresse:",
"pcap_sha1": "SHA1:",
"capture_started": "Capture begann mit:",
"capture_ended": "Capture endete an:",
"high": "Hoch", "high": "Hoch",
"moderate": "Mittel", "moderate": "Mittel",
"low": "Niedrig" "low": "Niedrig"
@@ -86,4 +89,4 @@
"update_finished": "Update abgeschlossen, Sie werden weitergeleitet ...", "update_finished": "Update abgeschlossen, Sie werden weitergeleitet ...",
"update_it": "Aktualisieren Sie TinyCheck" "update_it": "Aktualisieren Sie TinyCheck"
} }
} }

View File

@@ -50,8 +50,11 @@
"low_msg": "You have only {nb} low alerts,<br /> don't hesitate to check them.", "low_msg": "You have only {nb} low alerts,<br /> don't hesitate to check them.",
"save_report": "Save the report", "save_report": "Save the report",
"report_of": "Report of", "report_of": "Report of",
"ip_address": "IP address", "ip_address": "IP address:",
"mac_address": "MAC address", "mac_address": "MAC address:",
"pcap_sha1": "SHA1:",
"capture_started": "Capture started on:",
"capture_ended": "Capture ended on:",
"high": "high", "high": "high",
"moderate": "moderate", "moderate": "moderate",
"low": "low" "low": "low"

View File

@@ -50,8 +50,11 @@
"low_msg": "Solo tiene {nb} alertas bajas, <br /> por favor revíselas", "low_msg": "Solo tiene {nb} alertas bajas, <br /> por favor revíselas",
"save_report": "Guardar el informe", "save_report": "Guardar el informe",
"report_of": "Informe de", "report_of": "Informe de",
"ip_address": "dirección IP", "ip_address": "Dirección IP:",
"mac_address": "dirección MAC", "mac_address": "Dirección MAC:",
"pcap_sha1": "SHA1:",
"capture_started": "Captura comenzó a:",
"capture_ended": "Captura terminó a:",
"high": "alta", "high": "alta",
"moderate": "moderada", "moderate": "moderada",
"low": "bajo" "low": "bajo"
@@ -86,4 +89,4 @@
"update_finished": "Actualización finalizada, actualizando la interfaz...", "update_finished": "Actualización finalizada, actualizando la interfaz...",
"update_it": "Actualizar ahora" "update_it": "Actualizar ahora"
} }
} }

View File

@@ -52,6 +52,9 @@
"report_of": "Rapport de", "report_of": "Rapport de",
"ip_address": "Adresse IP :", "ip_address": "Adresse IP :",
"mac_address": "Adresse MAC :", "mac_address": "Adresse MAC :",
"pcap_sha1": "SHA1 :",
"capture_started": "Capture débutée le :",
"capture_ended": "Capture finie le :",
"high": "elevee", "high": "elevee",
"moderate": "moyenne", "moderate": "moyenne",
"low": "basse" "low": "basse"

View File

@@ -50,8 +50,11 @@
"low_msg": "Sono presenti solo {nb} avvisi con priorità bassa<br /> da controllare.", "low_msg": "Sono presenti solo {nb} avvisi con priorità bassa<br /> da controllare.",
"save_report": "Salva il rapporto", "save_report": "Salva il rapporto",
"report_of": "Rapporto di", "report_of": "Rapporto di",
"ip_address": "Indirizzo IP", "ip_address": "Indirizzo IP:",
"mac_address": "Indirizzo MAC", "mac_address": "Indirizzo MAC:",
"pcap_sha1": "SHA1:",
"capture_started": "Cattura è iniziata su:",
"capture_ended": "Cattura terminata su:",
"high": "elevata", "high": "elevata",
"moderate": "moderata", "moderate": "moderata",
"low": "bassa" "low": "bassa"
@@ -86,4 +89,4 @@
"update_finished": "Aggiornamento completato, verrai reindirizzato ...", "update_finished": "Aggiornamento completato, verrai reindirizzato ...",
"update_it": "Aggiorna TinyCheck" "update_it": "Aggiorna TinyCheck"
} }
} }

View File

@@ -50,8 +50,11 @@
"low_msg": "Você tem apenas {nb} alertas leves,<br /> não deixe de verificá-los.", "low_msg": "Você tem apenas {nb} alertas leves,<br /> não deixe de verificá-los.",
"save_report": "Salvar o relatório", "save_report": "Salvar o relatório",
"report_of": "Relatório de", "report_of": "Relatório de",
"ip_address": "Endereço IP", "ip_address": "Endereço IP:",
"mac_address": "Endereço MAC", "mac_address": "Endereço MAC:",
"pcap_sha1": "SHA1:",
"capture_started": "Captura iniciada em:",
"capture_ended": "Captura terminou em:",
"high": "crítico", "high": "crítico",
"moderate": "moderado", "moderate": "moderado",
"low": "leve" "low": "leve"
@@ -86,4 +89,4 @@
"update_finished": "Atualização concluída, você será redirecionado ...", "update_finished": "Atualização concluída, você será redirecionado ...",
"update_it": "Atualizar TinyCheck" "update_it": "Atualizar TinyCheck"
} }
} }

View File

@@ -50,8 +50,11 @@
"low_msg": "У вас {nb} предупреждение низкого уровня<br />, проверьте их.", "low_msg": "У вас {nb} предупреждение низкого уровня<br />, проверьте их.",
"save_report": "Сохранить отчет", "save_report": "Сохранить отчет",
"report_of": "Отчет", "report_of": "Отчет",
"ip_address": "IP-адрес", "ip_address": "IP-адрес:",
"mac_address": "MAC-адрес", "mac_address": "MAC-адрес:",
"pcap_sha1": " SHA1:",
"capture_started": "Захват начался:",
"capture_ended": "захват закончился:",
"high": "высокий", "high": "высокий",
"moderate": "средний", "moderate": "средний",
"low": "низкий" "low": "низкий"
@@ -86,4 +89,4 @@
"update_finished": "Обновление завершено, вы будете перенаправлены...", "update_finished": "Обновление завершено, вы будете перенаправлены...",
"update_it": "Обновить TinyCheck" "update_it": "Обновить TinyCheck"
} }
} }

View File

@@ -54,7 +54,8 @@ export default {
this.running = false; this.running = false;
router.replace({ name: 'report', router.replace({ name: 'report',
params: { alerts : response.data.alerts, params: { alerts : response.data.alerts,
device : response.data.device, device : response.data.device,
pcap : response.data.pcap,
capture_token : this.capture_token } }); capture_token : this.capture_token } });
} }
}) })

View File

@@ -24,6 +24,8 @@ export default {
var internet = this.internet var internet = this.internet
if (window.config.iface_out.charAt(0) == 'e'){ if (window.config.iface_out.charAt(0) == 'e'){
router.push({ name: 'generate-ap' }); router.push({ name: 'generate-ap' });
} else if (!window.config.choose_net && this.internet){
router.push({ name: 'generate-ap' });
} else { } else {
router.push({ name: 'wifi-select', router.push({ name: 'wifi-select',
params: { saved_ssid: saved_ssid, params: { saved_ssid: saved_ssid,

View File

@@ -46,8 +46,14 @@
</div> </div>
<div v-else-if="show_report" class="report-wrapper"> <div v-else-if="show_report" class="report-wrapper">
<div class="device-ctx"> <div class="device-ctx">
<h3 style="margin: 0;">{{ $t("report.report_of") }} {{device.name}}</h3> <h3 style="margin: 0; padding-left:10px;">{{ $t("report.report_of") }} {{device.name}}</h3>
{{ $t("report.ip_address") }} {{device.ip_address}}<br />{{ $t("report.mac_address") }} {{device.mac_address}} <div class="device-ctx-legend">
{{ $t("report.pcap_sha1") }} {{ pcap.SHA1 }}<br />
{{ $t("report.capture_started") }} {{ pcap["First packet time"].split(",")[0] }}<br />
{{ $t("report.capture_ended") }} {{ pcap["Last packet time"].split(",")[0] }}<br />
<!-- {{ $t("report.ip_address") }} {{device.ip_address}}<br /> -->
{{ $t("report.mac_address") }} {{device.mac_address}}
</div>
</div> </div>
<ul class="alerts"> <ul class="alerts">
<li class="alert" v-for="alert in alerts.high" :key="alert.message"> <li class="alert" v-for="alert in alerts.high" :key="alert.message">
@@ -105,6 +111,7 @@ export default {
}, },
props: { props: {
device: Object, device: Object,
pcap: Object,
alerts: Array, alerts: Array,
capture_token: String capture_token: String
}, },

View File

@@ -2,6 +2,7 @@ ipwhois
M2Crypto M2Crypto
pyOpenSSL pyOpenSSL
pydig pydig
pymisp
netaddr netaddr
pyyaml pyyaml
flask flask
@@ -14,4 +15,4 @@ wifi
qrcode qrcode
netifaces netifaces
weasyprint weasyprint
python-whois python-whois

View File

@@ -17,3 +17,14 @@ CREATE TABLE "whitelist" (
"added_on" INTEGER NOT NULL, "added_on" INTEGER NOT NULL,
PRIMARY KEY("id" AUTOINCREMENT) PRIMARY KEY("id" AUTOINCREMENT)
); );
CREATE TABLE "misp" (
"id" INTEGER UNIQUE,
"name" TEXT,
"url" TEXT NOT NULL,
"apikey" TEXT NOT NULL,
"verifycert" INTEGER NOT NULL DEFAULT 0,
"added_on" NUMERIC NOT NULL,
"last_sync" NUMERIC NOT NULL DEFAULT 0,
PRIMARY KEY("id" AUTOINCREMENT)
);

View File

@@ -51,6 +51,7 @@ frontend:
virtual_keyboard: true virtual_keyboard: true
user_lang: userlang user_lang: userlang
update: updateoption update: updateoption
choose_net: false
# NETWORK - # NETWORK -
# Some elements related to the network configuration, such as # Some elements related to the network configuration, such as

View File

@@ -441,7 +441,7 @@ change_configs() {
feeding_iocs() { feeding_iocs() {
echo -e "\e[39m[+] Feeding your TinyCheck instance with fresh IOCs and whitelist, please wait." echo -e "\e[39m[+] Feeding your TinyCheck instance with fresh IOCs and whitelist, please wait."
python3 /usr/share/tinycheck/server/backend/watchers.py python3 /usr/share/tinycheck/server/backend/watchers.py 2>/dev/null
} }
reboot_box() { reboot_box() {

View File

@@ -26,6 +26,20 @@ def add(ioc_type, ioc_tag, ioc_tlp, ioc_value):
return jsonify(res) return jsonify(res)
@ioc_bp.route('/add_post', methods=['POST'])
@require_header_token
def add_post():
"""
Parse and add an IOC to the database using the post method.
:return: status of the operation in JSON
"""
data = json.loads(request.data)
ioc = data["data"]["ioc"]
res = IOCs.add(ioc["ioc_type"], ioc["ioc_tag"], ioc["ioc_tlp"], ioc["ioc_value"], ioc["ioc_source"])
return jsonify(res)
@ioc_bp.route('/delete/<ioc_id>', methods=['GET']) @ioc_bp.route('/delete/<ioc_id>', methods=['GET'])
@require_header_token @require_header_token
def delete(ioc_id): def delete(ioc_id):

View File

@@ -0,0 +1,44 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from flask import Blueprint, jsonify, Response, request
from app.decorators import require_header_token, require_get_token
from app.classes.misp import MISP
import json
misp_bp = Blueprint("misp", __name__)
misp = MISP()
@misp_bp.route('/add', methods=['POST'])
@require_header_token
def add_instance():
"""
Parse and add a MISP instance to the database.
:return: status of the operation in JSON
"""
data = json.loads(request.data)
res = misp.add_instance(data["data"]["instance"])
return jsonify(res)
@misp_bp.route('/delete/<misp_id>', methods=['GET'])
@require_header_token
def delete_instance(misp_id):
"""
Delete a MISP instance by its id to the database.
:return: status of the operation in JSON
"""
res = misp.delete_instance(misp_id)
return jsonify(res)
@misp_bp.route('/get_all', methods=['GET'])
@require_header_token
def get_all():
"""
Retreive a list of all MISP instances.
:return: list of MISP instances in JSON.
"""
res = misp.get_instances()
return jsonify({"results": [i for i in res]})

View File

@@ -56,7 +56,12 @@ class IOCs(object):
db.session.commit() db.session.commit()
return {"status": True, return {"status": True,
"message": "IOC added", "message": "IOC added",
"ioc": escape(ioc_value)} "ioc": escape(ioc_value),
"type": escape(ioc_type),
"tlp": escape(ioc_tlp),
"tag": escape(ioc_tag),
"source": escape(source),
"added_on": escape(added_on)}
else: else:
return {"status": False, return {"status": False,
"message": "Wrong IOC format", "message": "Wrong IOC format",
@@ -111,7 +116,8 @@ class IOCs(object):
"type": ioc["type"], "type": ioc["type"],
"tag": ioc["tag"], "tag": ioc["tag"],
"tlp": ioc["tlp"], "tlp": ioc["tlp"],
"value": ioc["value"]} "value": ioc["value"],
"source": ioc["source"]}
@staticmethod @staticmethod
def get_types(): def get_types():

View File

@@ -0,0 +1,159 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from app import db
from app.db.models import MISPInst
from app.definitions import definitions as defs
from sqlalchemy.sql import exists
from urllib.parse import unquote
from flask import escape
from pymisp import PyMISP
import re
import time
import sys
class MISP(object):
def __init__(self):
return None
def add_instance(self, instance):
"""
Parse and add a MISP instance to the database.
:return: status of the operation in JSON
"""
url = instance["url"]
name = instance["name"]
apikey = instance["key"]
verify = instance["ssl"]
last_sync = int(time.time()-31536000) # One year
sameinstances = db.session.query(MISPInst).filter(
MISPInst.url == url, MISPInst.apikey == apikey)
if sameinstances.count():
return {"status": False,
"message": "This MISP instance already exists"}
if name:
if self.test_instance(url, apikey, verify):
added_on = int(time.time())
db.session.add(MISPInst(name, escape(
url), apikey, verify, added_on, last_sync))
db.session.commit()
return {"status": True,
"message": "MISP instance added"}
else:
return {"status": False,
"message": "Please verify the connection to the MISP instance"}
else:
return {"status": False,
"message": "Please provide a name for your instance"}
@staticmethod
def delete_instance(misp_id):
"""
Delete a MISP instance by its id in the database.
:return: status of the operation in JSON
"""
if db.session.query(exists().where(MISPInst.id == misp_id)).scalar():
db.session.query(MISPInst).filter_by(id=misp_id).delete()
db.session.commit()
return {"status": True,
"message": "MISP instance deleted"}
else:
return {"status": False,
"message": "MISP instance not found"}
def get_instances(self):
"""
Get MISP instances from the database
:return: generator of the records.
"""
for misp in db.session.query(MISPInst).all():
misp = misp.__dict__
yield {"id": misp["id"],
"name": misp["name"],
"url": misp["url"],
"apikey": misp["apikey"],
"verifycert": True if misp["verifycert"] else False,
"connected": self.test_instance(misp["url"], misp["apikey"], misp["verifycert"]),
"lastsync": misp["last_sync"]}
@staticmethod
def test_instance(url, apikey, verify):
"""
Test the connection of the MISP instance.
:return: generator of the records.
"""
try:
PyMISP(url, apikey, verify)
return True
except:
return False
@staticmethod
def update_sync(misp_id):
"""
Update the last synchronization date by the actual date.
:return: bool, True if updated.
"""
try:
misp = MISPInst.query.get(int(misp_id))
misp.last_sync = int(time.time())
db.session.commit()
return True
except:
return False
@staticmethod
def get_iocs(misp_id):
"""
Get all IOCs from specific MISP instance
:return: generator containing the IOCs.
"""
misp = MISPInst.query.get(int(misp_id))
if misp is not None:
if misp.url and misp.apikey:
try:
# Connect to MISP instance and get network activity attributes.
m = PyMISP(misp.url, misp.apikey, misp.verifycert)
r = m.search("attributes", category="Network activity", date_from=int(misp.last_sync))
except:
print("Unable to connect to the MISP instance ({}/{}).".format(misp.url, misp.apikey))
return []
for attr in r["Attribute"]:
if attr["type"] in ["ip-dst", "domain", "snort", "x509-fingerprint-sha1"]:
ioc = {"value": attr["value"],
"type": None,
"tag": "suspect",
"tlp": "white"}
# Deduce the IOC type.
if re.match(defs["iocs_types"][0]["regex"], attr["value"]):
ioc["type"] = "ip4addr"
elif re.match(defs["iocs_types"][1]["regex"], attr["value"]):
ioc["type"] = "ip6addr"
elif re.match(defs["iocs_types"][2]["regex"], attr["value"]):
ioc["type"] = "cidr"
elif re.match(defs["iocs_types"][3]["regex"], attr["value"]):
ioc["type"] = "domain"
elif re.match(defs["iocs_types"][4]["regex"], attr["value"]):
ioc["type"] = "sha1cert"
elif "alert " in attr["value"][0:6]:
ioc["type"] = "snort"
else:
continue
if "Tag" in attr:
for tag in attr["Tag"]:
# Add a TLP to the IOC if defined in tags.
tlp = re.search(r"^(?:tlp:)(red|green|amber|white)", tag['name'].lower())
if tlp: ioc["tlp"] = tlp.group(1)
# Add possible tag (need to match TinyCheck tags)
if tag["name"].lower() in [t["tag"] for t in defs["iocs_tags"]]:
ioc["tag"] = tag["name"].lower()
yield ioc

View File

@@ -1,5 +1,6 @@
from app import db from app import db
class Ioc(db.Model): class Ioc(db.Model):
def __init__(self, value, type, tlp, tag, source, added_on): def __init__(self, value, type, tlp, tag, source, added_on):
self.value = value self.value = value
@@ -9,6 +10,7 @@ class Ioc(db.Model):
self.source = source self.source = source
self.added_on = added_on self.added_on = added_on
class Whitelist(db.Model): class Whitelist(db.Model):
def __init__(self, element, type, source, added_on): def __init__(self, element, type, source, added_on):
self.element = element self.element = element
@@ -16,5 +18,17 @@ class Whitelist(db.Model):
self.source = source self.source = source
self.added_on = added_on self.added_on = added_on
class MISPInst(db.Model):
def __init__(self, name, url, key, ssl, added_on, last_sync):
self.name = name
self.url = url
self.apikey = key
self.verifycert = ssl
self.added_on = added_on
self.last_sync = last_sync
db.mapper(Whitelist, db.Table('whitelist', db.metadata, autoload=True)) db.mapper(Whitelist, db.Table('whitelist', db.metadata, autoload=True))
db.mapper(Ioc, db.Table('iocs', db.metadata, autoload=True)) db.mapper(Ioc, db.Table('iocs', db.metadata, autoload=True))
db.mapper(MISPInst, db.Table('misp', db.metadata, autoload=True))

View File

@@ -6,6 +6,7 @@ from app.decorators import auth
from app.blueprints.ioc import ioc_bp from app.blueprints.ioc import ioc_bp
from app.blueprints.whitelist import whitelist_bp from app.blueprints.whitelist import whitelist_bp
from app.blueprints.config import config_bp from app.blueprints.config import config_bp
from app.blueprints.misp import misp_bp
import datetime import datetime
import secrets import secrets
import jwt import jwt
@@ -56,6 +57,7 @@ def page_not_found(e):
app.register_blueprint(ioc_bp, url_prefix='/api/ioc') app.register_blueprint(ioc_bp, url_prefix='/api/ioc')
app.register_blueprint(whitelist_bp, url_prefix='/api/whitelist') app.register_blueprint(whitelist_bp, url_prefix='/api/whitelist')
app.register_blueprint(config_bp, url_prefix='/api/config') app.register_blueprint(config_bp, url_prefix='/api/config')
app.register_blueprint(misp_bp, url_prefix='/api/misp')
if __name__ == '__main__': if __name__ == '__main__':
ssl_cert = "{}/{}".format(path[0], 'cert.pem') ssl_cert = "{}/{}".format(path[0], 'cert.pem')

View File

@@ -4,6 +4,7 @@
from app.utils import read_config from app.utils import read_config
from app.classes.iocs import IOCs from app.classes.iocs import IOCs
from app.classes.whitelist import WhiteList from app.classes.whitelist import WhiteList
from app.classes.misp import MISP
import requests import requests
import json import json
@@ -16,11 +17,6 @@ from multiprocessing import Process
in the configuration file. This in order to get in the configuration file. This in order to get
automatically new iocs / elements from remote automatically new iocs / elements from remote
sources without user interaction. sources without user interaction.
As of today the default export JSON format from
the backend and unauthenticated HTTP requests
are accepted. The code is little awkward, it'll
be better in a next version ;)
""" """
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
@@ -29,7 +25,7 @@ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def watch_iocs(): def watch_iocs():
""" """
Retrieve IOCs from the remote URLs defined in config/watchers. Retrieve IOCs from the remote URLs defined in config/watchers.
For each (new ?) IOC, add it to the DB. For each IOC, add it to the DB.
""" """
# Retrieve the URLs from the configuration # Retrieve the URLs from the configuration
@@ -120,8 +116,32 @@ def watch_whitelists():
break break
def watch_misp():
"""
Retrieve IOCs from misp instances. Each new element is
tested and then added to the database.
"""
iocs, misp = IOCs(), MISP()
instances = [i for i in misp.get_instances()]
while instances:
for i, ist in enumerate(instances):
status = misp.test_instance(ist["url"],
ist["apikey"],
ist["verifycert"])
if status:
for ioc in misp.get_iocs(ist["id"]):
iocs.add(ioc["type"], ioc["tag"], ioc["tlp"],
ioc["value"], "misp-{}".format(ist["id"]))
misp.update_sync(ist["id"])
instances.pop(i)
if instances: time.sleep(60)
p1 = Process(target=watch_iocs) p1 = Process(target=watch_iocs)
p2 = Process(target=watch_whitelists) p2 = Process(target=watch_whitelists)
p3 = Process(target=watch_misp)
p1.start() p1.start()
p2.start() p2.start()
p3.start()

View File

@@ -72,5 +72,6 @@ def get_config():
"shutdown_option": read_config(("frontend", "shutdown_option")), "shutdown_option": read_config(("frontend", "shutdown_option")),
"reboot_option": read_config(("frontend", "reboot_option")), "reboot_option": read_config(("frontend", "reboot_option")),
"iface_out": read_config(("network", "out")), "iface_out": read_config(("network", "out")),
"user_lang": read_config(("frontend", "user_lang")) "user_lang": read_config(("frontend", "user_lang")),
"choose_net": read_config(("frontend", "choose_net"))
}) })

View File

@@ -41,13 +41,18 @@ class Analysis(object):
:return: dict containing the report or error message. :return: dict containing the report or error message.
""" """
device, alerts = {}, {} device, alerts, pcap = {}, {}, {}
# Getting device configuration. # Getting device configuration.
if os.path.isfile("/tmp/{}/assets/device.json".format(self.token)): if os.path.isfile("/tmp/{}/assets/device.json".format(self.token)):
with open("/tmp/{}/assets/device.json".format(self.token), "r") as f: with open("/tmp/{}/assets/device.json".format(self.token), "r") as f:
device = json.load(f) device = json.load(f)
# Getting pcap infos.
if os.path.isfile("/tmp/{}/assets/capinfos.json".format(self.token)):
with open("/tmp/{}/assets/capinfos.json".format(self.token), "r") as f:
pcap = json.load(f)
# Getting alerts configuration. # Getting alerts configuration.
if os.path.isfile("/tmp/{}/assets/alerts.json".format(self.token)): if os.path.isfile("/tmp/{}/assets/alerts.json".format(self.token)):
with open("/tmp/{}/assets/alerts.json".format(self.token), "r") as f: with open("/tmp/{}/assets/alerts.json".format(self.token), "r") as f:
@@ -55,6 +60,7 @@ class Analysis(object):
if device != {} and alerts != {}: if device != {} and alerts != {}:
return {"alerts": alerts, return {"alerts": alerts,
"device": device} "device": device,
"pcap": pcap}
else: else:
return {"message": "No report yet"} return {"message": "No report yet"}

View File

@@ -1,337 +1,355 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import subprocess as sp import subprocess as sp
import netifaces as ni import netifaces as ni
import requests as rq import requests as rq
import sys import re
import time import sys
import qrcode import time
import base64 import qrcode
import random import base64
import requests import random
import requests
from wifi import Cell
from os import path, remove from wifi import Cell
from io import BytesIO from os import path, remove
from app.utils import terminate_process, read_config from io import BytesIO
from app.utils import terminate_process, read_config
class Network(object):
class Network(object):
def __init__(self):
self.AP_SSID = False def __init__(self):
self.AP_PASS = False self.AP_SSID = False
self.iface_in = read_config(("network", "in")) self.AP_PASS = False
self.iface_out = read_config(("network", "out")) self.iface_in = read_config(("network", "in"))
self.enable_interface(self.iface_in) self.iface_out = read_config(("network", "out"))
self.enable_interface(self.iface_out) self.enable_interface(self.iface_in)
self.enable_forwarding() self.enable_interface(self.iface_out)
self.reset_dnsmasq_leases() self.enable_forwarding()
self.random_choice_alphabet = "abcdef1234567890" self.reset_dnsmasq_leases()
self.random_choice_alphabet = "abcdef1234567890"
def check_status(self):
""" def check_status(self):
The method check_status check the IP addressing of each interface """
and return their associated IP. The method check_status check the IP addressing of each interface
and return their associated IP.
:return: dict containing each interface status.
""" :return: dict containing each interface status.
"""
ctx = {"interfaces": {
self.iface_in: False, ctx = {"interfaces": {
self.iface_out: False, self.iface_in: False,
"eth0": False}, self.iface_out: False,
"internet": self.check_internet()} "eth0": False},
"internet": self.check_internet()}
for iface in ctx["interfaces"].keys():
try: for iface in ctx["interfaces"].keys():
ip = ni.ifaddresses(iface)[ni.AF_INET][0]["addr"] try:
if not ip.startswith("127") or not ip.startswith("169.254"): ip = ni.ifaddresses(iface)[ni.AF_INET][0]["addr"]
ctx["interfaces"][iface] = ip if not ip.startswith("127") or not ip.startswith("169.254"):
except: ctx["interfaces"][iface] = ip
ctx["interfaces"][iface] = "Interface not connected or present." except:
return ctx ctx["interfaces"][iface] = "Interface not connected or present."
return ctx
def wifi_list_networks(self):
""" def wifi_list_networks(self):
The method wifi_list_networks list the available WiFi networks """
by using wifi python package. The method wifi_list_networks list the available WiFi networks
:return: dict - containing the list of Wi-Fi networks. by using wifi python package.
""" :return: dict - containing the list of Wi-Fi networks.
networks = [] """
try: networks = []
for n in Cell.all(self.iface_out): try:
if n.ssid not in [n["ssid"] for n in networks] and n.ssid and n.encrypted: for n in Cell.all(self.iface_out):
networks.append( if n.ssid not in [n["ssid"] for n in networks] and n.ssid and n.encrypted:
{"ssid": n.ssid, "type": n.encryption_type}) networks.append(
return {"networks": networks} {"ssid": n.ssid, "type": n.encryption_type})
except: return {"networks": networks}
return {"networks": []} except:
return {"networks": []}
@staticmethod
def wifi_setup(ssid, password): @staticmethod
""" def wifi_setup(ssid, password):
Edit the wpa_supplicant file with provided credentials. """
If the ssid already exists, just update the password. Otherwise Edit the wpa_supplicant file with provided credentials.
create a new entry in the file. If the ssid already exists, just update the password. Otherwise
create a new entry in the file.
:return: dict containing the status of the operation
""" :return: dict containing the status of the operation
if len(password) >= 8 and len(ssid): """
found = False if len(password) >= 8 and len(ssid):
networks = [] found = False
header, content = "", "" networks = []
header, content = "", ""
with open("/etc/wpa_supplicant/wpa_supplicant.conf") as f:
content = f.read() with open("/etc/wpa_supplicant/wpa_supplicant.conf") as f:
blocks = content.split("network={") content = f.read()
header = blocks[0] blocks = content.split("network={")
header = blocks[0]
for block in blocks[1:]:
net = {} for block in blocks[1:]:
for line in block.splitlines(): net = {}
if line and line != "}": for line in block.splitlines():
if "priority=10" not in line.strip(): if line and line != "}":
key, val = line.strip().split("=") if "priority=10" not in line.strip():
if key != "disabled": key, val = line.strip().split("=")
net[key] = val.replace("\"", "") if key != "disabled":
networks.append(net) net[key] = val.replace("\"", "")
networks.append(net)
for net in networks:
if net["ssid"] == ssid: for net in networks:
net["psk"] = password.replace('"', '\\"') if net["ssid"] == ssid:
net["priority"] = "10" net["psk"] = password.replace('"', '\\"')
found = True net["priority"] = "10"
found = True
if not found:
networks.append({ if not found:
"ssid": ssid, networks.append({
"psk": password.replace('"', '\\"'), "ssid": ssid,
"key_mgmt": "WPA-PSK", "psk": password.replace('"', '\\"'),
"priority": "10" "key_mgmt": "WPA-PSK",
}) "priority": "10"
})
with open("/etc/wpa_supplicant/wpa_supplicant.conf", "w+") as f:
content = header with open("/etc/wpa_supplicant/wpa_supplicant.conf", "w+") as f:
for network in networks: content = header
net = "network={\n" for network in networks:
for k, v in network.items(): net = "network={\n"
if k in ["ssid", "psk"]: for k, v in network.items():
net += " {}=\"{}\"\n".format(k, v) if k in ["ssid", "psk"]:
else: net += " {}=\"{}\"\n".format(k, v)
net += " {}={}\n".format(k, v) else:
net += "}\n\n" net += " {}={}\n".format(k, v)
content += net net += "}\n\n"
if f.write(content): content += net
return {"status": True, if f.write(content):
"message": "Configuration saved"} return {"status": True,
else: "message": "Configuration saved"}
return {"status": False, else:
"message": "Error while writing wpa_supplicant configuration file."} return {"status": False,
else: "message": "Error while writing wpa_supplicant configuration file."}
return {"status": False, else:
"message": "Empty SSID or/and password length less than 8 chars."} return {"status": False,
"message": "Empty SSID or/and password length less than 8 chars."}
def wifi_connect(self):
""" def wifi_connect(self):
Connect to one of the WiFi networks present in the wpa_supplicant.conf. """
:return: dict containing the TinyCheck <-> AP status. Connect to one of the WiFi networks present in the wpa_supplicant.conf.
""" :return: dict containing the TinyCheck <-> AP status.
"""
# Kill wpa_supplicant instances, if any.
terminate_process("wpa_supplicant") # Kill wpa_supplicant instances, if any.
# Launch a new instance of wpa_supplicant. terminate_process("wpa_supplicant")
sp.Popen(["wpa_supplicant", "-B", "-i", self.iface_out, "-c", # Launch a new instance of wpa_supplicant.
"/etc/wpa_supplicant/wpa_supplicant.conf"]).wait() sp.Popen(["wpa_supplicant", "-B", "-i", self.iface_out, "-c",
# Check internet status "/etc/wpa_supplicant/wpa_supplicant.conf"]).wait()
for _ in range(1, 40): # Check internet status
if self.check_internet(): for _ in range(1, 40):
return {"status": True, if self.check_internet():
"message": "Wifi connected"} return {"status": True,
time.sleep(1) "message": "Wifi connected"}
time.sleep(1)
return {"status": False,
"message": "Wifi not connected"} return {"status": False,
"message": "Wifi not connected"}
def start_ap(self):
""" def start_ap(self):
The start_ap method generates an Access Point by using HostApd """
and provide to the GUI the associated ssid, password and qrcode. The start_ap method generates an Access Point by using HostApd
and provide to the GUI the associated ssid, password and qrcode.
:return: dict containing the status of the AP
""" :return: dict containing the status of the AP
"""
# Re-ask to enable interface, sometimes it just go away.
if not self.enable_interface(self.iface_out): # Re-ask to enable interface, sometimes it just go away.
return {"status": False, if not self.enable_interface(self.iface_out):
"message": "Interface not present."} return {"status": False,
"message": "Interface not present."}
# Generate the hostapd configuration
if read_config(("network", "tokenized_ssids")): # Generate the hostapd configuration
token = "".join([random.choice(self.random_choice_alphabet) if read_config(("network", "tokenized_ssids")):
for i in range(4)]) token = "".join([random.choice(self.random_choice_alphabet)
self.AP_SSID = random.choice(read_config( for i in range(4)])
("network", "ssids"))) + "-" + token self.AP_SSID = random.choice(read_config(
else: ("network", "ssids"))) + "-" + token
self.AP_SSID = random.choice(read_config(("network", "ssids"))) else:
self.AP_PASS = "".join( self.AP_SSID = random.choice(read_config(("network", "ssids")))
[random.choice(self.random_choice_alphabet) for i in range(8)]) self.AP_PASS = "".join(
[random.choice(self.random_choice_alphabet) for i in range(8)])
# Launch hostapd
if self.write_hostapd_config(): # Launch hostapd
if self.lauch_hostapd() and self.reset_dnsmasq_leases(): if self.write_hostapd_config():
return {"status": True, if self.lauch_hostapd() and self.reset_dnsmasq_leases():
"message": "AP started", return {"status": True,
"ssid": self.AP_SSID, "message": "AP started",
"password": self.AP_PASS, "ssid": self.AP_SSID,
"qrcode": self.generate_qr_code()} "password": self.AP_PASS,
else: "qrcode": self.generate_qr_code()}
return {"status": False, else:
"message": "Error while creating AP."} return {"status": False,
else: "message": "Error while creating AP."}
return {"status": False, else:
"message": "Error while writing hostapd configuration file."} return {"status": False,
"message": "Error while writing hostapd configuration file."}
def generate_qr_code(self):
""" def generate_qr_code(self):
The method generate_qr_code returns a QRCode based on """
the SSID and the password. The method generate_qr_code returns a QRCode based on
the SSID and the password.
:return: - string containing the PNG of the QRCode.
""" :return: - string containing the PNG of the QRCode.
qrc = qrcode.make("WIFI:S:{};T:WPA;P:{};;".format( """
self.AP_SSID, self.AP_PASS)) qrc = qrcode.make("WIFI:S:{};T:WPA;P:{};;".format(
buffered = BytesIO() self.AP_SSID, self.AP_PASS))
qrc.save(buffered, format="PNG") buffered = BytesIO()
return "data:image/png;base64,{}".format(base64.b64encode(buffered.getvalue()).decode("utf8")) qrc.save(buffered, format="PNG")
return "data:image/png;base64,{}".format(base64.b64encode(buffered.getvalue()).decode("utf8"))
def write_hostapd_config(self):
""" def write_hostapd_config(self):
The method write_hostapd_config write the hostapd configuration """
under a temporary location defined in the config file. The method write_hostapd_config write the hostapd configuration
under a temporary location defined in the config file.
:return: bool - if hostapd configuration file created
""" :return: bool - if hostapd configuration file created
try: """
with open("{}/app/assets/hostapd.conf".format(sys.path[0]), "r") as f: try:
conf = f.read() chan = self.set_ap_channel()
conf = conf.replace("{IFACE}", self.iface_in) with open("{}/app/assets/hostapd.conf".format(sys.path[0]), "r") as f:
conf = conf.replace("{SSID}", self.AP_SSID) conf = f.read()
conf = conf.replace("{PASS}", self.AP_PASS) conf = conf.replace("{IFACE}", self.iface_in)
with open("/tmp/hostapd.conf", "w") as c: conf = conf.replace("{SSID}", self.AP_SSID)
c.write(conf) conf = conf.replace("{PASS}", self.AP_PASS)
return True conf = conf.replace("{CHAN}", chan)
except: with open("/tmp/hostapd.conf", "w") as c:
return False c.write(conf)
return True
def lauch_hostapd(self): except:
""" return False
The method lauch_hostapd kill old instance of hostapd and launch a
new one as a background process. def lauch_hostapd(self):
"""
:return: bool - if hostapd sucessfully launched. The method lauch_hostapd kill old instance of hostapd and launch a
""" new one as a background process.
# Kill potential zombies of hostapd :return: bool - if hostapd sucessfully launched.
terminate_process("hostapd") """
sp.Popen(["ifconfig", self.iface_in, "up"]).wait() # Kill potential zombies of hostapd
sp.Popen( terminate_process("hostapd")
"/usr/sbin/hostapd /tmp/hostapd.conf > /tmp/hostapd.log", shell=True)
sp.Popen(["ifconfig", self.iface_in, "up"]).wait()
while True: sp.Popen(
if path.isfile("/tmp/hostapd.log"): "/usr/sbin/hostapd /tmp/hostapd.conf > /tmp/hostapd.log", shell=True)
with open("/tmp/hostapd.log", "r") as f:
log = f.read() while True:
err = ["Could not configure driver mode", if path.isfile("/tmp/hostapd.log"):
"driver initialization failed"] with open("/tmp/hostapd.log", "r") as f:
if not any(e in log for e in err): log = f.read()
if "AP-ENABLED" in log: err = ["Could not configure driver mode",
return True "driver initialization failed"]
else: if not any(e in log for e in err):
return False if "AP-ENABLED" in log:
time.sleep(1) return True
else:
def stop_hostapd(self): return False
""" time.sleep(1)
Stop hostapd instance.
def stop_hostapd(self):
:return: dict - a little message for debug. """
""" Stop hostapd instance.
if terminate_process("hostapd"):
return {"status": True, :return: dict - a little message for debug.
"message": "AP stopped"} """
else: if terminate_process("hostapd"):
return {"status": False, return {"status": True,
"message": "No AP running"} "message": "AP stopped"}
else:
def reset_dnsmasq_leases(self): return {"status": False,
""" "message": "No AP running"}
This method reset the DNSMasq leases and logs to get the new
connected device name & new DNS entries. def reset_dnsmasq_leases(self):
"""
:return: bool if everything goes well This method reset the DNSMasq leases and logs to get the new
""" connected device name & new DNS entries.
try:
sp.Popen("service dnsmasq stop", shell=True).wait() :return: bool if everything goes well
sp.Popen("cp /dev/null /var/lib/misc/dnsmasq.leases", """
shell=True).wait() try:
sp.Popen("cp /dev/null /var/log/messages.log", shell=True).wait() sp.Popen("service dnsmasq stop", shell=True).wait()
sp.Popen("service dnsmasq start", shell=True).wait() sp.Popen("cp /dev/null /var/lib/misc/dnsmasq.leases",
return True shell=True).wait()
except: sp.Popen("cp /dev/null /var/log/messages.log", shell=True).wait()
return False sp.Popen("service dnsmasq start", shell=True).wait()
return True
def enable_forwarding(self): except:
""" return False
This enable forwarding to get internet working on the connected device.
Method tiggered during the Network class intialization. def enable_forwarding(self):
"""
:return: bool if everything goes well This enable forwarding to get internet working on the connected device.
""" Method tiggered during the Network class intialization.
try:
sp.Popen("echo 1 > /proc/sys/net/ipv4/ip_forward", :return: bool if everything goes well
shell=True).wait() """
try:
# Enable forwarding. sp.Popen("echo 1 > /proc/sys/net/ipv4/ip_forward",
sp.Popen(["iptables", "-A", "POSTROUTING", "-t", "nat", "-o", shell=True).wait()
self.iface_out, "-j", "MASQUERADE"]).wait()
# Enable forwarding.
# Prevent the device to reach the 80 and 443 of TinyCheck. sp.Popen(["iptables", "-A", "POSTROUTING", "-t", "nat", "-o",
sp.Popen(["iptables", "-A", "INPUT", "-i", self.iface_in, "-d", self.iface_out, "-j", "MASQUERADE"]).wait()
"192.168.100.1", "-p", "tcp", "--match", "multiport", "--dports", "80,443", "-j" "DROP"]).wait()
# Prevent the device to reach the 80 and 443 of TinyCheck.
return True sp.Popen(["iptables", "-A", "INPUT", "-i", self.iface_in, "-d",
except: "192.168.100.1", "-p", "tcp", "--match", "multiport", "--dports", "80,443", "-j" "DROP"]).wait()
return False
return True
def enable_interface(self, iface): except:
""" return False
This enable interfaces, with a simple check.
:return: bool if everything goes well def enable_interface(self, iface):
""" """
sh = sp.Popen(["ifconfig", iface], This enable interfaces, with a simple check.
stdout=sp.PIPE, stderr=sp.PIPE) :return: bool if everything goes well
sh = sh.communicate() """
if b"<UP," in sh[0]: sh = sp.Popen(["ifconfig", iface],
return True # The interface is up. stdout=sp.PIPE, stderr=sp.PIPE)
elif sh[1]: sh = sh.communicate()
return False # The interface doesn't exists (most of the cases). if b"<UP," in sh[0]:
else: return True # The interface is up.
sp.Popen(["ifconfig", iface, "up"]).wait() elif sh[1]:
return True return False # The interface doesn't exists (most of the cases).
else:
def check_internet(self): sp.Popen(["ifconfig", iface, "up"]).wait()
""" return True
Check the internet link just with a small http request
to an URL present in the configuration def check_internet(self):
"""
:return: bool - if the request succeed or not. Check the internet link just with a small http request
""" to an URL present in the configuration
try:
url = read_config(("network", "internet_check")) :return: bool - if the request succeed or not.
requests.get(url, timeout=10) """
return True try:
except: url = read_config(("network", "internet_check"))
return False requests.get(url, timeout=10)
return True
except:
return False
def set_ap_channel(self):
"""
Deduce the channel to have for the AP in order to prevent
kind of jamming between the two wifi interfaces.
"""
# Get the channel of the connected interface
sh = sp.Popen(["iw", self.iface_out, "info"],
stdout=sp.PIPE, stderr=sp.PIPE).communicate()
res = re.search("channel ([0-9]{1,2})", sh[0].decode('utf8'))
chn = res.group(1)
# Return a good candidate.
return "11" if int(chn) < 7 else "1"

View File

@@ -40,13 +40,17 @@ elif [ $PWD = "/tmp/tinycheck" ]; then
cd /usr/share/tinycheck/app/frontend/ && npm install && npm audit fix && npm run build cd /usr/share/tinycheck/app/frontend/ && npm install && npm audit fix && npm run build
cd /usr/share/tinycheck/app/backend/ && npm install && npm audit fix && npm run build cd /usr/share/tinycheck/app/backend/ && npm install && npm audit fix && npm run build
echo "[+] Updating the database scheme..."
cd /usr/share/tinycheck/
sqlite3 tinycheck.sqlite3 < /tmp/tinycheck/assets/scheme.sql 2>/dev/null
echo "[+] Updating current configuration with new values." echo "[+] Updating current configuration with new values."
if ! grep -q reboot_option /usr/share/tinycheck/config.yaml; then if ! grep -q reboot_option /usr/share/tinycheck/config.yaml; then
sed -i 's/frontend:/frontend:\n reboot_option: true/g' /usr/share/tinycheck/config.yaml sed -i 's/frontend:/frontend:\n reboot_option: true/g' /usr/share/tinycheck/config.yaml
fi fi
if ! grep -q user_lang /usr/share/tinycheck/config.yaml; then if ! grep -q choose_net /usr/share/tinycheck/config.yaml; then
sed -i 's/frontend:/frontend:\n user_lang: en/g' /usr/share/tinycheck/config.yaml sed -i 's/frontend:/frontend:\n choose_net: false/g' /usr/share/tinycheck/config.yaml
fi fi
if ! grep -q shutdown_option /usr/share/tinycheck/config.yaml; then if ! grep -q shutdown_option /usr/share/tinycheck/config.yaml; then
@@ -65,6 +69,10 @@ elif [ $PWD = "/tmp/tinycheck" ]; then
sed -i 's/frontend:/frontend:\n update: true/g' /usr/share/tinycheck/config.yaml sed -i 's/frontend:/frontend:\n update: true/g' /usr/share/tinycheck/config.yaml
fi fi
if ! grep -q user_lang /usr/share/tinycheck/config.yaml; then
sed -i 's/frontend:/frontend:\n user_lang: en/g' /usr/share/tinycheck/config.yaml
fi
if ! grep -q "CN=R3,O=Let's Encrypt,C=US" /usr/share/tinycheck/config.yaml; then if ! grep -q "CN=R3,O=Let's Encrypt,C=US" /usr/share/tinycheck/config.yaml; then
sed -i "s/free_issuers:/free_issuers:\n - CN=R3,O=Let's Encrypt,C=US/g" /usr/share/tinycheck/config.yaml sed -i "s/free_issuers:/free_issuers:\n - CN=R3,O=Let's Encrypt,C=US/g" /usr/share/tinycheck/config.yaml
fi fi