38 Commits

Author SHA1 Message Date
09de9177d2 Update iocs-misp.vue 2021-06-15 14:54:44 +02:00
bd329c27cf Update iocs-misp.vue 2021-06-14 17:16:08 +02:00
793a97b530 Merge pull request #73 from KasperskyLab/misp
Adding MISP support to dev
2021-06-14 16:07:03 +02:00
920bd6785c Enchancing report.vue 2021-06-14 13:22:26 +02:00
bbfbfc2e84 Adding the choose network option 2021-06-14 10:10:24 +02:00
6f5fd11a26 Modifying iocs search to show the source 2021-06-11 17:15:12 +02:00
22315277f3 Changing last synchronization value style 2021-06-11 16:27:10 +02:00
601450d9fd Adding last sync tooltip 2021-06-11 15:45:57 +02:00
b8bed722dc Merge branch 'misp' of https://github.com/KasperskyLab/TinyCheck into misp 2021-06-11 14:46:27 +02:00
940d5954a3 Updating MISP watcher 2021-06-11 14:46:11 +02:00
cb0742d440 Hiding warnings of watchers. 2021-06-11 14:10:08 +02:00
436870960d Correcting error 2021-06-10 21:11:41 +02:00
772ff95108 Updating the update script 2021-06-10 20:17:24 +02:00
926daf3e34 Debug after testing, everything works great now 2021-06-10 19:48:35 +02:00
73946c27e2 More code modification regarding MISP integration 2021-06-10 19:00:06 +02:00
8e09d4e1c8 More code modification regarding MISP integration 2021-06-10 18:37:40 +02:00
f189f2e100 Code modifications regarding MISP integration 2021-06-09 18:24:37 +02:00
a481e88251 Deleting get iocs endpoint 2021-06-09 12:49:28 +02:00
1d1c217258 Deleting edit endpoint 2021-06-09 12:48:44 +02:00
93e164d7c2 Typo in comment 2021-06-09 11:17:30 +02:00
50baeaa9e5 Updating misp class and associated watcher code 2021-06-09 11:11:55 +02:00
691a413bfb Modifying ip4addr & ip6addr types 2021-06-09 09:27:30 +02:00
73ee7a280b Some modifications, still need tests and debug 2021-06-08 20:11:51 +02:00
e0c79fa5d6 Working on the MISP integration, still WIP (untested) 2021-06-08 18:22:52 +02:00
61de73d989 Adding the channel check to prevent interferences 2021-06-08 11:49:53 +02:00
9f75d339da Update config.yaml 2021-06-07 14:28:50 +02:00
c1b8f4a447 Merge pull request #70 from JulAkx/misp
New Feature : Import IoCs from an added MISP instance.
2021-06-06 11:08:35 +02:00
1d8c2387ca Check window location host value to show the close button.
The close button was available even if the interface is requested remotely. Now the script hides the close button if the value of window.location.host is equal to 127.0.0.1 or localhost.
2021-06-04 14:40:16 +02:00
fd66d2274e Update install.sh 2021-06-03 15:20:25 +02:00
fa8393cba5 Merge pull request #72 from KasperskyLab/dev
Adding detection improvements and capture removing.
2021-06-03 14:54:38 +02:00
8ea52b1a4f Resolving import and var error 2021-06-03 14:25:51 +02:00
2968d6fcb6 Adding unsaved captures deletion 2021-06-03 14:08:34 +02:00
cb7aeb2721 Update iocs.json
Adding IOCs from the ESET report. Thanks guys.
Report: https://www.welivesecurity.com/wp-content/uploads/2021/05/eset_android_stalkerware.pdf
2021-05-31 20:21:53 +02:00
24be446598 New Feature : It is now possible to import IoCs from an added MISP instance. 2021-05-10 16:08:58 +02:00
e04ef547c2 Update snort rule issue + http header detection 2021-05-04 14:48:09 +02:00
033d751e31 Update iocs.json 2021-05-04 10:21:29 +02:00
d41ad28c25 Update iocs.json 2021-04-30 16:51:24 +02:00
bf8edb0283 Update iocs.json 2021-04-29 20:24:09 +02:00
46 changed files with 1259 additions and 398 deletions

View File

@ -285,6 +285,54 @@ class ZeekEngine(object):
"level": "High",
"id": "IOC-07"})
def http_check(self, dir):
"""
Check on the http.log:
* Blacklisted domain/tld from the Host header field.
Can be used when no DNS query have been done during the session (already cached by the device.)
:return: nothing - all stuff appended to self.alerts
"""
if os.path.isfile(os.path.join(dir, "http.log")):
for record in ParseZeekLogs(os.path.join(dir, "http.log"), output_format="json", safe_headers=False):
if record is not None:
c = {"host": record['host']}
if c not in self.http:
self.http.append(c)
if self.iocs_analysis:
for c in self.http:
# If we already know the host form DNS, let's continue.
if c["host"] in [r["domain"] for r in self.dns]:
continue
# Check for blacklisted domain.
for h in self.bl_domains:
if h[1] != "tracker":
if c["host"].endswith(h[0]):
self.alerts.append({"title": self.template["IOC-08"]["title"].format(c["host"], h[1].upper()),
"description": self.template["IOC-08"]["description"].format(c["host"]),
"host": c["host"],
"level": "High",
"id": "IOC-08"})
# Check for freedns.
for h in self.bl_freedns:
if c["host"].endswith(h[0]):
self.alerts.append({"title": self.template["IOC-09"]["title"].format(c["host"]),
"description": self.template["IOC-09"]["description"].format(c["host"]),
"host": c["host"],
"level": "Moderate",
"id": "IOC-09"})
# Check for fancy TLD.
for h in self.bl_tlds:
if c["host"].endswith(h[0]):
self.alerts.append({"title": self.template["IOC-10"]["title"].format(c["host"]),
"description": self.template["IOC-10"]["description"].format(c["host"], h[0]),
"host": c["host"],
"level": "Low",
"id": "IOC-10"})
def ssl_check(self, dir):
"""
Check on the ssl.log:
@ -345,6 +393,10 @@ class ZeekEngine(object):
# Check if the domain in the certificate haven't been blacklisted
# This check can be good if the domain has already been cached by
# the device so it wont appear in self.dns.
if any([cert["cn"].endswith(r["domain"]) for r in self.dns]):
continue
for domain in self.bl_domains:
if domain[1] != "tracker":
if cert["cn"].endswith(domain[0]):
@ -399,6 +451,7 @@ class ZeekEngine(object):
self.fill_dns(self.working_dir + "/assets/")
self.netflow_check(self.working_dir + "/assets/")
self.ssl_check(self.working_dir + "/assets/")
self.http_check(self.working_dir + "/assets/")
self.files_check(self.working_dir + "/assets/")
self.alerts_check()

View File

@ -48,6 +48,18 @@
"title": "Un certificat associat a activitats {} ha estat identificat en una comunicació cap a {}.",
"description": "El certificat ({}) associat a {} ha estat explícitament etiquetat com a maliciós. Això indica que el seu dispositiu està probablement compromès i necessita ser analitzat en profunditat per un especialista forense."
},
"IOC-08": {
"title": "S'ha fet una petició HTTP a {} que està etiquetada com {}.",
"description": "El domini {} identificat en la captura ha estat explícitament etiquetat com a maliciós. Això indica que el dispositiu està probablement compromès i ha de ser investigat en profunditat."
},
"IOC-09": {
"title": "S'ha fet una petició HTTP a el domini {} que és un DNS gratuït.",
"description": "El domini {} està utilitzant un servei DNS gratuït. Aquest tipus de serveis és utilitzat per cibercriminals i altres actors d'amenaces de manera comú. És força sospitós que una aplicació en segon pla utilitzi aquest tipus de serveis.Per favor investigui. "
},
"IOC-10": {
"title": "S'ha fet una petició HTTP a el domini {} que conté un TLD sospitós.",
"description": "El domini {} està utilitzant un domini de primer nivell -TLD- ({}). Encara que no sigui maliciós, aquest TLP no-genèric és usat per cibercriminals i altres actors d'amenaces amb regularitat. Comproveu aquest domini mitjançant la seva cerca a Internet. Si hi ha altres alertes relacionades amb aquest host, considereu-lo com a molt sospitós. "
},
"ACT-01": {
"title": "El domini {} està utilitzant un servidor de noms sospitós ({}).",
"description": "El nom de domini {} utilitza un servidor de noms que ha estat explícitament etiquetat i associat a activitat maliciosa. Molts ciberdelinqüents i altres actors d'amenaces utilitzen aquest tipus de registradors ja que accepten criptomonedes i pagaments anònims. Es recomana investigar aquest domini i l'aplicació en execució associada mitjançant un anàlisi forense de el dispositiu. "

View File

@ -48,6 +48,18 @@
"title": "Ein Zertifikat, das mit {} Aktivitäten verknüpft ist, wurde in der Kommunikationsverbindung zu {} gefunden.",
"description": "Das Zertifikat ({}), das mit {} verknüpft ist, wurde explizit als bösartig gekennzeichnet. Dies weist darauf hin, dass Ihr Gerät wahrscheinlich kompromittiert ist und eine forensische Analyse benötigt."
},
"IOC-08": {
"title": "Es wurde eine HTTP-Abfrage zu {} ausgeführt, was als {} gekennzeichnet ist.",
"description": "Der in der Aufnahme vorkommende Domain-Name {} wurde explizit als bösartig gekennzeichnet. Dies weist darauf hin, dass Ihr Gerät wahrscheinlich kompromittiert ist und eingehend untersucht werden muss."
},
"IOC-09": {
"title": "Es wurde eine HTTP-Abfrage zur Domain {} ausgeführt, die einen Free-DNS-Dienst nutzt.",
"description": "Der Domain-Name {} nutzt einen Free-DNS-Dienst. Dienste dieser Art werden häufig von Cyberkriminellen oder staatlich unterstützten Angreifern bei ihren Operationen genutzt. Es ist sehr verdächtig, dass eine im Hintergrund laufende Anwendung einen solchen Dienst verwendet. Bitte untersuchen Sie das näher."
},
"IOC-10": {
"title": "Es wurde eine HTTP-Abfrage zur Domain {} ausgeführt, die eine verdächtige TLD enthält.",
"description": "Der Domain-Name {} nutzt eine verdächtige Top-Level-Domain ({}). Diese nicht-generische TLD ist zwar selbst nicht bösartig, wird aber häufig von Cyberkriminellen und bei staatlich unterstützten Operationen verwendet. Bitte überprüfen Sie diese Domain anhand einer Internetsuche. Wenn dieser Host in anderen Warnungen erwähnt wird, können Sie ihn als sehr verdächtig betrachten."
},
"ACT-01": {
"title": "Die Domain {} nutzt einen verdächtigen Nameserver ({}).",
"description": "Der Domain-Name {} nutzt einen Nameserver, der explizit mit bösartigen Aktivitäten in Verbindung gebracht wird. Viele Cyberkriminelle und staatlich unterstützte Angreifer nutzen Registrare dieser Art, weil sie Kryptowährungen und anonyme Zahlungen zulassen. Es wird empfohlen, diese Domain und die damit verknüpfte laufende Anwendung mithilfe einer forensischen Analyse des Telefons näher zu untersuchen."

View File

@ -48,6 +48,18 @@
"title": "A certificate associated to {} activities have been found in the communication to {}.",
"description": "The certificate ({}) associated to {} has been explicitly tagged as malicious. This indicates that your device is likely compromised and need a forensic analysis."
},
"IOC-08": {
"title": "An HTTP request have been done to {} which is tagged as {}.",
"description": "The domain name {} seen in the capture has been explicitly tagged as malicious. This indicates that your device is likely compromised and needs to be investigated deeply."
},
"IOC-09": {
"title": "An HTTP request have been done to the domain {} which is a Free DNS.",
"description": "The domain name {} is using a Free DNS service. This kind of service is commonly used by cybercriminals or state-sponsored threat actors during their operations. It is very suspicious that an application running in background use this kind of service, please investigate."
},
"IOC-10": {
"title": "An HTTP request have been done to the domain {} which contains a suspect TLD.",
"description": "The domain name {} is using a suspect Top Level Domain ({}). Even not malicious, this non-generic TLD is used regularly by cybercrime or state-sponsored operations. Please check this domain by searching it on an internet search engine. If other alerts are related to this host, please consider it as very suspicious."
},
"ACT-01": {
"title": "The domain {} is using a suspect nameserver ({}).",
"description": "The domain name {} is using a nameserver that has been explicitly tagged to be associated to malicious activities. Many cybercriminals and state-sponsored threat actors are using this kind of registrars because they allow cryptocurrencies and anonymous payments. It is adviced to investigate on this domain and the associated running application by doing a forensic analysis of the phone."
@ -113,4 +125,4 @@
"low_msg": "You have only {} low alert(s), don't hesitate to check them.",
"none_msg": "Everything looks fine, zero alerts. Don't hesitate to check the uncategorized communications, if any."
}
}
}

View File

@ -48,6 +48,18 @@
"title": "Un certificado asociado a actividades {} ha sido identificado en una comunicación hacia {}.",
"description": "El certificado ({}) asociado a {} ha sido explícitamente etiquetado como malicioso. Esto indica que su dispositivo está probablemente comprometido y necesita ser analizado en profundidad por un especialista forense."
},
"IOC-08": {
"title": "Se ha realizado una petición HTTP a {} que está etiquetada como {}.",
"description": "El dominio {} identificado en la captura ha sido explícitamente etiquetado como malicioso. Esto indica que su dispositivo está probablemente comprometido y debe ser investigado en profundidad."
},
"IOC-09": {
"title": "Se ha realizado una petición HTTP al dominio {} que es un DNS gratuito.",
"description": "El dominio {} está usando un servicio DNS gratuito. Este tipo de servicios es comúnmente utilizado por cibercriminales y otros actores de amenazas. Es altamente sospechoso que una aplicación ejecutándose en segundo plano use este tipo de servicios. Por favor investigue."
},
"IOC-10": {
"title": "Se ha realizado una petición HTTP al dominio {} que contiene un TLD sospechoso.",
"description": "El dominio {} está usando un dominio de primero nivel -TLD- ({}). Aunque no sea malicioso, este TLP no-genérico es usado por cibercriminales y otros actores de amenazas con regularidad. Verifique este dominio mediante su búsqueda en Internet. Si hay otras alertas relacionadas con este host, por favor considérelo como muy sospechoso."
},
"ACT-01": {
"title": "El dominio {} está usando un servidor de nombres sospechoso ({}).",
"description": "El nombre de dominio {} usa un servidor de nombres que ha sido explícitamente etiquetado como asociado a actividad maliciosa. Muchos ciberdelincuentes y otros actores de amenazas utilizan este tipo de registradores ya que aceptan criptomonedas y pagos anónimos. Se recomienda investigar este dominio y la aplicación en ejecución asociada mediante un análisis forense del dispositivo."

View File

@ -48,6 +48,18 @@
"title": "Un certificat associé à des activités de {} a été vu lors de communications vers {}.",
"description": "Le certificat ({}) associé au serveur {} a été explicitement catégorisé comme malveillant. Votre appareil est sûrement compromis et doit être investigué plus en détails par une équipe professionnelle."
},
"IOC-08": {
"title": "Requête HTTP vers le domaine {} qui est tagué en tant que {}.",
"description": "Le serveur {} vers lequel communique votre appareil a été explicitement catégorisé en tant que malveillant. Votre appareil est sûrement compromis et doit être investigué plus en détails par une équipe professionnelle."
},
"IOC-09": {
"title": "Requête HTTP vers le domaine {} qui est un domaine gratuit.",
"description": "Le nom de domaine {} utilise un service de noms de domaine gratuits. Ce type de service est couramment utilisé par les cybercriminels ou des acteurs associés à des États au cours de leurs opérations d'espionnage. Il est très suspect qu'une application exécutée en arrière-plan utilise ce type de service, veuillez enquêter."
},
"IOC-10": {
"title": "Requête HTTP vers le domaine {} contenant une extension suspecte.",
"description": "Le nom de domaine {} utilise une extension suspecte ({}). Même si cela n'est pas malveillant en-soi, l'utilisation d'une extension non générique est l'apanage d'acteurs cybercriminels et étatiques durant leurs opérations. Veuillez vérifier la pertinance de ce domaine en le recherchant sur un moteur de recherche Internet. Si d'autres alertes sont liées à ce dernier, veuillez le considérer comme très suspect."
},
"ACT-01": {
"title": "Le domaine {} utilise un serveur de noms suspect ({}).",
"description": "Le nom de domaine {} utilise un serveur de noms qui a été explicitement catégorisé comme associé à des activités malveillantes. Plusieurs cybercriminels et acteurs étatiques utilisent ce type de serveurs de noms car ils autorisent les paiements anonymes grâce aux cryptomonnaies. Il est conseillé d'investiguer sur ce domaine et l'application s'y connectant en réalisant une analyse post-mortem de l'appareil analysé."
@ -113,4 +125,4 @@
"low_msg": "Vous avez uniquement {} alerte(s) faibles, n'hésitez pas à les consulter.",
"none_msg": "Toute semble normal, vous avez aucune alerte. Cependant, n'hésitez pas à regarder les communications non catégorisées."
}
}
}

View File

@ -48,6 +48,18 @@
"title": "Nella comunicazione a {} è stato rilevato un certificato associato ad attività {}.",
"description": "Il certificato ({}) associato a {} è stato esplicitamente contrassegnato come dannoso. Questo indica che il dispositivo è potenzialmente compromesso e necessita di un'analisi forense."
},
"IOC-08": {
"title": "È stata effettuata una richiesta HTTP a {} con contrassegno {}.",
"description": "Il nome di dominio {} visualizzato nell'acquisizione è stato esplicitamente contrassegnato come dannoso. Questo indica che il dispositivo è potenzialmente compromesso e deve essere esaminato a fondo."
},
"IOC-09": {
"title": "È stata effettuata una richiesta HTTP al dominio {} che è un servizio Free DNS.",
"description": "Il nome di dominio {} utilizza un servizio Free DNS. Questo tipo di servizio è comunemente utilizzato durante le operazioni di criminali informatici o autori di attacchi commissionati da stati esteri. L'utilizzo di questo tipo di servizio da parte di un'applicazione in esecuzione in background è molto sospetto e richiede ulteriori indagini."
},
"IOC-10": {
"title": "È stata effettuata una richiesta HTTP al dominio {} contenente un dominio di primo livello sospetto.",
"description": "Il nome di dominio {} utilizza un dominio di primo livello sospetto ({}). Anche se non dannoso, questo dominio di primo livello non generico viene utilizzato regolarmente durante le operazioni di criminali informatici o autori di attacchi commissionati da stati esteri. Controllare questo dominio effettuando una ricerca tramite un motore di ricerca Internet. Se altri avvisi sono correlati all'host, è necessario considerare questo elemento molto sospetto."
},
"ACT-01": {
"title": "Il dominio {} utilizza un server dei nomi sospetto ({}).",
"description": "Il nome di dominio {} utilizza un server dei nomi che è stato esplicitamente contrassegnato come associato ad attività dannose. Molti criminali informatici e autori di attacchi commissionati da stati esteri utilizzano questo tipo di registrar poiché sono ammessi criptovalute e pagamenti anonimi. È consigliabile indagare su questo dominio e sull'applicazione in esecuzione associata eseguendo un'analisi forense del telefono."

View File

@ -48,6 +48,18 @@
"title": "Um certificado associado a atividades de {} foi encontrado na comunicação para {}.",
"description": "O certificado ({}) associado a {} foi explicitamente marcado como malicioso. Isso indica que o dispositivo provavelmente foi comprometido e precisa de uma análise forense."
},
"IOC-08": {
"title": "Uma solicitação de HTTP foi feita para {}, marcado como {}.",
"description": "O nome de domínio {} visto na captura foi explicitamente marcado como malicioso. Isso indica que o dispositivo provavelmente foi comprometido e precisa ser analisado com cuidado."
},
"IOC-09": {
"title": "Uma solicitação de HTTP foi feita para o domínio {}, que é um DNS gratuito.",
"description": "O nome de domínio {} está usando um serviço de DNS gratuito. Esse tipo de serviço é comumente usado por cibercriminosos ou agências de inteligência estatais no exercício de suas funções. É muito suspeito que aplicativos em execução em segundo plano usem esse tipo de serviço e isso deve ser analisado com cuidado."
},
"IOC-10": {
"title": "Uma solicitação de HTTP foi feita para o domínio {}, que contém um TLD (domínio de nível superior) suspeito.",
"description": "O nome de domínio {} está usando um TLD suspeito ({}). Mesmo não sendo malicioso, esse TLD não genérico é frequentemente usado por cibercriminosos ou agências de inteligência estatais. Analise a reputação do domínio pesquisando-o na internet. Se outros alertas forem observados, considere esse host como muito suspeito."
},
"ACT-01": {
"title": "O domínio {} está usando um servidor de nomes suspeito ({}).",
"description": "O nome de domínio {} está usando um servidor de nomes explicitamente marcado como associado a atividades maliciosas. Muitos cibercriminosos e agentes de inteligência estatais usam esse tipo de registradores porque isso permite pagamentos com criptomoedas e anônimos. É recomendável investigar esse domínio e o aplicativo em execução por meio de uma análise forense do telefone."
@ -113,4 +125,4 @@
"low_msg": "Você tem apenas {} alerta(s) leve(s), não deixe de verificá-los.",
"none_msg": "Tudo parece estar bem, zero alertas. Não deixe de verificar comunicações não categorizadas, se houver."
}
}
}

View File

@ -48,6 +48,18 @@
"title": "Сертификат, связанный с действиями {}, был обнаружен при взаимодействии с {}.",
"description": "Сертификат ({}), связанный с {}, явно отмечен как вредоносный. Это указывает на то, что ваше устройство, вероятно, взломано и требуется провести экспертный анализ."
},
"IOC-08": {
"title": "Выполнен HTTP-запрос к {}, отмеченному как {}.",
"description": "Доменное имя {}, обнаруженное при сборе данных, явно отмечено как вредоносное. Это указывает на то, что ваше устройство, вероятно, взломано и требуется тщательное расследование."
},
"IOC-09": {
"title": "Выполнен HTTP-запрос к домену {}, использующему бесплатную службу DNS.",
"description": "Доменное имя {} использует бесплатную службу DNS. Такие службы обычно используются киберпреступниками или спонсируемыми государством злоумышленниками для атак. Очень подозрительно, что приложение, работающее в фоновом режиме, использует такую службу. Требуется расследование."
},
"IOC-10": {
"title": "Выполнен HTTP-запрос к домену {}, содержащему подозрительный домен верхнего уровня.",
"description": "Доменное имя {} использует подозрительный домен верхнего уровня ({}). Даже не являясь вредоносным, этот не универсальный домен верхнего уровня регулярно используется киберпреступниками или спонсируемыми государством злоумышленниками. Проверьте этот домен, выполнив поиск в интернете. Если с этим устройством связаны другие предупреждения, это очень подозрительно."
},
"ACT-01": {
"title": "Домен {} использует подозрительный сервер имен ({}).",
"description": "Доменное имя {} использует сервер имен, который явно отмечен как связанный с вредоносными действиями. Многие киберпреступники и спонсируемые государством злоумышленники пользуются такими регистраторами, поскольку они позволяют использовать криптовалюту и анонимные платежи. Рекомендуется исследовать этот домен и связанные с ним работающие приложения, выполнив экспертный анализ телефона."

View File

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

View File

@ -650,4 +650,128 @@ h4, h5 {
.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;
}
.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'),
props: true
},
{
path: '/iocs/misp',
name: 'iocs-manage',
component: () => import('../views/iocs-misp.vue'),
props: true
},
{
path: '/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">
<i class="form-icon"></i> Use virtual keyboard (for touch screen)
</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">
<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.

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>
<tr>
<th>Indicator</th>
<th>Type</th>
<th>Tag</th>
<th>TLP</th>
<th> </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="capi">{{ r.type }}</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 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 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>
@ -49,7 +59,8 @@ export default {
return {
results: [],
first_search: true,
jwt:""
jwt:"",
loading:false
}
},
props: { },
@ -57,6 +68,7 @@ export default {
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)) {
@ -72,6 +84,7 @@ export default {
if(response.data.results.length>0){
this.results = [].concat(this.results, response.data.results);
}
this.loading = false;
})
.catch(err => (console.log(err)))
});

View File

@ -128,8 +128,8 @@
}
.report-wrapper {
width:90%;
margin:auto;
padding-bottom:1px;
}
.device-ctx {
@ -212,10 +212,11 @@
.report-wrapper {
width:60%;
margin:auto;
padding-bottom: 1px;
}
.device-ctx {
padding:15px;
padding:15px 0px 15px 0px;
margin:auto;
}
@ -303,6 +304,17 @@ footer {
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 {
text-transform: uppercase;
color : #999;

View File

@ -53,7 +53,7 @@ export default {
load_config: function() {
axios.get(`/api/misc/config`, { timeout: 60000 })
.then(response => {
this.quit_available = response.data.quit_option
this.quit_available = (response.data.quit_option && (["localhost", "127.0.0.1"].some(h => window.location.host.includes(h) )))
this.off_available = response.data.shutdown_option
})
.catch(error => { console.log(error) });
@ -73,4 +73,4 @@ export default {
this.check_update();
}
}
</script>
</script>

View File

@ -50,8 +50,11 @@
"low_msg": "Només teniu {nb} alertes baixes, <br /> Si us plau comproveu-les.",
"save_report": "Desa l'informe",
"report_of": "Informe de",
"ip_address": "Adreça IP",
"mac_address": "Adreça MAC",
"ip_address": "Adreça IP:",
"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",
"moderat": "moderat",
"low": "baix"

View File

@ -50,8 +50,11 @@
"low_msg": "Sie haben nur {nb} Warnungen der Stufe \"Niedrig\":<br> Überprüfen Sie sie gerne.",
"save_report": "Bericht speichern",
"report_of": "Bericht zu",
"ip_address": "IP-Adresse",
"mac_address": "MAC-Adresse",
"ip_address": "IP-Adresse:",
"mac_address": "MAC-Adresse:",
"pcap_sha1": "SHA1:",
"capture_started": "Capture begann mit:",
"capture_ended": "Capture endete an:",
"high": "Hoch",
"moderate": "Mittel",
"low": "Niedrig"
@ -86,4 +89,4 @@
"update_finished": "Update abgeschlossen, Sie werden weitergeleitet ...",
"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.",
"save_report": "Save the report",
"report_of": "Report of",
"ip_address": "IP address",
"mac_address": "MAC address",
"ip_address": "IP address:",
"mac_address": "MAC address:",
"pcap_sha1": "SHA1:",
"capture_started": "Capture started on:",
"capture_ended": "Capture ended on:",
"high": "high",
"moderate": "moderate",
"low": "low"

View File

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

View File

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

View File

@ -50,8 +50,11 @@
"low_msg": "Sono presenti solo {nb} avvisi con priorità bassa<br /> da controllare.",
"save_report": "Salva il rapporto",
"report_of": "Rapporto di",
"ip_address": "Indirizzo IP",
"mac_address": "Indirizzo MAC",
"ip_address": "Indirizzo IP:",
"mac_address": "Indirizzo MAC:",
"pcap_sha1": "SHA1:",
"capture_started": "Cattura è iniziata su:",
"capture_ended": "Cattura terminata su:",
"high": "elevata",
"moderate": "moderata",
"low": "bassa"
@ -86,4 +89,4 @@
"update_finished": "Aggiornamento completato, verrai reindirizzato ...",
"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.",
"save_report": "Salvar o relatório",
"report_of": "Relatório de",
"ip_address": "Endereço IP",
"mac_address": "Endereço MAC",
"ip_address": "Endereço IP:",
"mac_address": "Endereço MAC:",
"pcap_sha1": "SHA1:",
"capture_started": "Captura iniciada em:",
"capture_ended": "Captura terminou em:",
"high": "crítico",
"moderate": "moderado",
"low": "leve"
@ -86,4 +89,4 @@
"update_finished": "Atualização concluída, você será redirecionado ...",
"update_it": "Atualizar TinyCheck"
}
}
}

View File

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

View File

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

View File

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

View File

@ -46,8 +46,14 @@
</div>
<div v-else-if="show_report" class="report-wrapper">
<div class="device-ctx">
<h3 style="margin: 0;">{{ $t("report.report_of") }} {{device.name}}</h3>
{{ $t("report.ip_address") }} {{device.ip_address}}<br />{{ $t("report.mac_address") }} {{device.mac_address}}
<h3 style="margin: 0; padding-left:10px;">{{ $t("report.report_of") }} {{device.name}}</h3>
<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>
<ul class="alerts">
<li class="alert" v-for="alert in alerts.high" :key="alert.message">
@ -94,6 +100,7 @@
<script>
import router from '../router'
import axios from 'axios'
export default {
name: 'report',
@ -104,6 +111,7 @@ export default {
},
props: {
device: Object,
pcap: Object,
alerts: Array,
capture_token: String
},
@ -113,7 +121,11 @@ export default {
router.replace({ name: 'save-capture', params: { capture_token: capture_token } });
},
new_capture: function() {
router.push({ name: 'generate-ap' })
axios.get('/api/misc/delete-captures', { timeout: 30000 })
.then(() => {
router.push({ name: 'generate-ap' })
})
.catch(err => (console.log(err)))
},
grep_keyword: function(kw, level){
try {

View File

@ -39,6 +39,10 @@
})
.catch(err => (console.log(err)))
},
delete_captures: function() {
axios.get('/api/misc/delete-captures', { timeout: 30000 })
.catch(err => (console.log(err)))
},
goto_home: function() {
var list_ssids = this.list_ssids
var internet = this.internet
@ -46,6 +50,7 @@
}
},
created: function() {
this.delete_captures();
setTimeout(function () { this.internet_check(); }.bind(this), 5000);
}
}

File diff suppressed because one or more lines are too long

View File

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

View File

@ -17,3 +17,14 @@ CREATE TABLE "whitelist" (
"added_on" INTEGER NOT NULL,
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
user_lang: userlang
update: updateoption
choose_net: false
# NETWORK -
# Some elements related to the network configuration, such as
@ -59,7 +60,7 @@ frontend:
#
network:
in: iface_in
internet_check: http://example.com
internet_check: https://1.1.1.1
out: iface_out
ssids:
- wireless

View File

@ -338,7 +338,7 @@ create_desktop() {
Version=1.0
Type=Application
Terminal=false
Exec=chromium-browser http://localhost
Exec=bash /usr/share/tinycheck/kiosk.sh
Name=TinyCheck
Comment=Launcher for the TinyCheck frontend
Icon=/usr/share/tinycheck/app/frontend/src/assets/icon.png
@ -441,7 +441,7 @@ change_configs() {
feeding_iocs() {
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() {

View File

@ -1,11 +1,12 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from flask import Blueprint, jsonify, Response
from flask import Blueprint, jsonify, Response, request
from app.decorators import require_header_token, require_get_token
from app.classes.iocs import IOCs
import json
from urllib.parse import unquote
ioc_bp = Blueprint("ioc", __name__)
ioc = IOCs()
@ -19,10 +20,26 @@ def add(ioc_type, ioc_tag, ioc_tlp, ioc_value):
:return: status of the operation in JSON
"""
source = "backend"
if ioc_type == "snort":
ioc_value = unquote("/".join(request.full_path.split("/")[7:]))
res = IOCs.add(ioc_type, ioc_tag, ioc_tlp, ioc_value, source)
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'])
@require_header_token
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()
return {"status": True,
"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:
return {"status": False,
"message": "Wrong IOC format",
@ -111,7 +116,8 @@ class IOCs(object):
"type": ioc["type"],
"tag": ioc["tag"],
"tlp": ioc["tlp"],
"value": ioc["value"]}
"value": ioc["value"],
"source": ioc["source"]}
@staticmethod
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
class Ioc(db.Model):
def __init__(self, value, type, tlp, tag, source, added_on):
self.value = value
@ -9,6 +10,7 @@ class Ioc(db.Model):
self.source = source
self.added_on = added_on
class Whitelist(db.Model):
def __init__(self, element, type, source, added_on):
self.element = element
@ -16,5 +18,17 @@ class Whitelist(db.Model):
self.source = source
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(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.whitelist import whitelist_bp
from app.blueprints.config import config_bp
from app.blueprints.misp import misp_bp
import datetime
import secrets
import jwt
@ -56,6 +57,7 @@ def page_not_found(e):
app.register_blueprint(ioc_bp, url_prefix='/api/ioc')
app.register_blueprint(whitelist_bp, url_prefix='/api/whitelist')
app.register_blueprint(config_bp, url_prefix='/api/config')
app.register_blueprint(misp_bp, url_prefix='/api/misp')
if __name__ == '__main__':
ssl_cert = "{}/{}".format(path[0], 'cert.pem')

View File

@ -4,6 +4,7 @@
from app.utils import read_config
from app.classes.iocs import IOCs
from app.classes.whitelist import WhiteList
from app.classes.misp import MISP
import requests
import json
@ -16,11 +17,6 @@ from multiprocessing import Process
in the configuration file. This in order to get
automatically new iocs / elements from remote
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)
@ -29,7 +25,7 @@ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def watch_iocs():
"""
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
@ -120,8 +116,32 @@ def watch_whitelists():
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)
p2 = Process(target=watch_whitelists)
p3 = Process(target=watch_misp)
p1.start()
p2.start()
p3.start()

View File

@ -3,7 +3,7 @@
import subprocess as sp
from flask import Blueprint, jsonify
from app.utils import read_config
from app.utils import read_config, delete_captures
import re
import sys
import os
@ -11,6 +11,17 @@ import os
misc_bp = Blueprint("misc", __name__)
@misc_bp.route("/delete-captures", methods=["GET"])
def api_delete_captures():
"""
Delete the zombies capture folders (if any)
"""
if delete_captures():
return jsonify({"message": "Captures deleted", "status": True})
else:
return jsonify({"message": "Issue while removing captures", "status": False})
@misc_bp.route("/reboot", methods=["GET"])
def api_reboot():
"""
@ -61,5 +72,6 @@ def get_config():
"shutdown_option": read_config(("frontend", "shutdown_option")),
"reboot_option": read_config(("frontend", "reboot_option")),
"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.
"""
device, alerts = {}, {}
device, alerts, pcap = {}, {}, {}
# Getting device configuration.
if os.path.isfile("/tmp/{}/assets/device.json".format(self.token)):
with open("/tmp/{}/assets/device.json".format(self.token), "r") as 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.
if os.path.isfile("/tmp/{}/assets/alerts.json".format(self.token)):
with open("/tmp/{}/assets/alerts.json".format(self.token), "r") as f:
@ -55,6 +60,7 @@ class Analysis(object):
if device != {} and alerts != {}:
return {"alerts": alerts,
"device": device}
"device": device,
"pcap": pcap}
else:
return {"message": "No report yet"}

View File

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

@ -7,6 +7,8 @@ import yaml
import sys
import os
from functools import reduce
import shutil
import re
def terminate_process(process):
@ -33,3 +35,16 @@ def read_config(path):
config = yaml.load(open(os.path.join(dir, "config.yaml"), "r"),
Loader=yaml.SafeLoader)
return reduce(dict.get, path, config)
def delete_captures():
"""
Delete potential zombies capture directories
"""
try:
for d in os.listdir("/tmp/"):
if re.match("[A-F0-9]{8}", d):
shutil.rmtree(os.path.join("/tmp/", d))
return True
except:
return False

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/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."
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
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
if ! grep -q choose_net /usr/share/tinycheck/config.yaml; then
sed -i 's/frontend:/frontend:\n choose_net: false/g' /usr/share/tinycheck/config.yaml
fi
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
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
sed -i "s/free_issuers:/free_issuers:\n - CN=R3,O=Let's Encrypt,C=US/g" /usr/share/tinycheck/config.yaml
fi