Merge pull request #45 from KasperskyLab/dev

Adding multi-language to TinyCheck frontend interface and reports.
This commit is contained in:
Félix Aimé 2021-02-08 20:07:46 +01:00 committed by GitHub
commit 3b45608edb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 789 additions and 321 deletions

View File

@ -2,10 +2,13 @@ import weasyprint
import os import os
import json import json
import hashlib import hashlib
import re
import sys
from weasyprint import HTML from weasyprint import HTML
from pathlib import Path from pathlib import Path
from datetime import datetime from datetime import datetime
from utils import get_config
class Report(object): class Report(object):
@ -28,6 +31,14 @@ class Report(object):
except: except:
self.capture_sha1 = "N/A" self.capture_sha1 = "N/A"
self.userlang = get_config(("frontend", "user_lang"))
# Load template language
if not re.match("^[a-z]{2,3}$", self.userlang):
self.userlang = "en"
with open(os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])), "locales/{}.json".format(self.userlang))) as f:
self.template = json.load(f)["report"]
def read_json(self, json_path): def read_json(self, json_path):
""" """
Read and convert a JSON file. Read and convert a JSON file.
@ -59,21 +70,35 @@ class Report(object):
:return: str :return: str
""" """
if len(self.alerts["high"]): if len(self.alerts["high"]):
return "<div class=\"warning high\">Your device seems to be compromised as you have {} high alert(s).</div>".format(self.nb_translate(len(self.alerts["high"]))) msg = "<div class=\"warning high\">"
msg += self.template["high_msg"].format(
self.nb_translate(len(self.alerts["high"])))
msg += "</div>"
return msg
elif len(self.alerts["moderate"]): elif len(self.alerts["moderate"]):
return "<div class=\"warning moderate\">You have {} moderate alert(s), your device might be compromised. Please look at them carefully.</div>".format(self.nb_translate(len(self.alerts["moderate"]))) msg = "<div class=\"warning moderate\">"
msg += self.template["moderate_msg"].format(
self.nb_translate(len(self.alerts["moderate"])))
msg += "</div>"
return msg
elif len(self.alerts["low"]): elif len(self.alerts["low"]):
return "<div class=\"warning low\">You have only {} low alert(s), don't hesitate to check them.</div>".format(self.nb_translate(len(self.alerts["low"]))) msg = "<div class=\"warning low\">"
msg += self.template["low_msg"].format(
self.nb_translate(len(self.alerts["low"])))
msg += "</div>"
return msg
else: else:
return "<div class=\"warning low\">Everything looks fine, zero alerts. Don't hesitate to check the uncategorized communications, if any.</div>" msg = "<div class=\"warning low\">"
msg += self.template["none_msg"]
msg += "</div>"
return msg
def nb_translate(self, nb): def nb_translate(self, nb):
""" """
Translate a number in a string. Translate a number in a string.
:return: str :return: str
""" """
a = ["one", "two", "three", "four", "five", a = self.template["numbers"]
"six", "seven", "height", "nine"]
return a[nb-1] if nb <= 9 else str(nb) return a[nb-1] if nb <= 9 else str(nb)
def generate_suspect_conns_block(self): def generate_suspect_conns_block(self):
@ -85,17 +110,17 @@ class Report(object):
if not len([c for c in self.conns if c["alert_tiggered"] == True]): if not len([c for c in self.conns if c["alert_tiggered"] == True]):
return "" return ""
title = "<h2>Suspect communications</h2>" title = "<h2>{}</h2>".format(self.template["suspect_title"])
table = """<table> table = "<table>"
<thead> table += " <thead>"
<tr> table += " <tr>"
<th>Protocol</th> table += " <th>{}</th>".format(self.template["protocol"])
<th>Domain</th> table += " <th>{}</th>".format(self.template["domain"])
<th>Dst IP Address</th> table += " <th>{}</th>".format(self.template["dst_ip"])
<th>Dst port</th> table += " <th>{}</th>".format(self.template["dst_port"])
</tr> table += " </tr>"
</thead> table += " </thead>"
<tbody>""" table += "<tbody>"
for rec in self.conns: for rec in self.conns:
if rec["alert_tiggered"] == True: if rec["alert_tiggered"] == True:
@ -117,17 +142,17 @@ class Report(object):
if not len([c for c in self.conns if c["alert_tiggered"] == False]): if not len([c for c in self.conns if c["alert_tiggered"] == False]):
return "" return ""
title = "<h2>Uncategorized communications</h2>" title = "<h2>{}</h2>".format(self.template["uncat_title"])
table = """<table> table = "<table>"
<thead> table += " <thead>"
<tr> table += " <tr>"
<th>Protocol</th> table += " <th>{}</th>".format(self.template["protocol"])
<th>Domain</th> table += " <th>{}</th>".format(self.template["domain"])
<th>Dst IP Address</th> table += " <th>{}</th>".format(self.template["dst_ip"])
<th>Dst port</th> table += " <th>{}</th>".format(self.template["dst_port"])
</tr> table += " </tr>"
</thead> table += " </thead>"
<tbody>""" table += "<tbody>"
for rec in self.conns: for rec in self.conns:
if rec["alert_tiggered"] == False: if rec["alert_tiggered"] == False:
@ -149,17 +174,17 @@ class Report(object):
if not len(self.whitelist): if not len(self.whitelist):
return "" return ""
title = "<h2>Whitelisted communications</h2>" title = "<h2>{}</h2>".format(self.template["whitelist_title"])
table = """<table> table = "<table>"
<thead> table += " <thead>"
<tr> table += " <tr>"
<th>Protocol</th> table += " <th>{}</th>".format(self.template["protocol"])
<th>Domain</th> table += " <th>{}</th>".format(self.template["domain"])
<th>Dst IP Address</th> table += " <th>{}</th>".format(self.template["dst_ip"])
<th>Dst port</th> table += " <th>{}</th>".format(self.template["dst_port"])
</tr> table += " </tr>"
</thead> table += " </thead>"
<tbody>""" table += "<tbody>"
for rec in sorted(self.whitelist, key=lambda k: k['resolution']): for rec in sorted(self.whitelist, key=lambda k: k['resolution']):
table += "<tr>" table += "<tr>"
@ -179,17 +204,18 @@ class Report(object):
""" """
header = "<div class=\"header\">" header = "<div class=\"header\">"
header += "<div class=\"logo\"></div>" header += "<div class=\"logo\"></div>"
header += "<p><br /><strong>Device name: {}</strong><br />".format( header += "<p><br /><strong>{}: {}</strong><br />".format(self.template["device_name"],
self.device["name"]) self.device["name"])
header += "Device MAC address: {}<br />".format( header += "{}: {}<br />".format(self.template["device_mac"],
self.device["mac_address"]) self.device["mac_address"])
header += "Report generated on {}<br />".format( header += "{} {}<br />".format(self.template["report_generated_on"],
datetime.now().strftime("%d/%m/%Y at %H:%M:%S")) datetime.now().strftime("%d/%m/%Y - %H:%M:%S"))
header += "Capture duration: {}s<br />".format( header += "{}: {}s<br />".format(self.template["capture_duration"],
self.capinfos["Capture duration"]) self.capinfos["Capture duration"])
header += "Number of packets: {}<br />".format( header += "{}: {}<br />".format(self.template["packets_number"],
self.capinfos["Number of packets"]) self.capinfos["Number of packets"])
header += "Capture SHA1: {}<br />".format(self.capture_sha1) header += "{}: {}<br />".format(
self.template["capture_sha1"], self.capture_sha1)
header += "</p>" header += "</p>"
header += "</div>" header += "</div>"
return header return header
@ -420,16 +446,16 @@ class Report(object):
} }
@page { @page {
@top-center { @top-center {
content: "REPORT_HEADER - Page " counter(page) " of " counter(pages) "."; content: "REPORT_HEADER - Page " counter(page) " / " counter(pages) ".";
font-size:12px; font-size:12px;
color:#CCC; color:#CCC;
} }
@bottom-center { @bottom-center {
content: "This report has been autogenerated by a Tinycheck device. For any question, bug report or feedback, please contact tinycheck@kaspersky.com."; content: "REPORT_FOOTER";
font-size:12px; font-size:12px;
color:#CCC; color:#CCC;
} }
} }
</style> </style>
</head> </head>
<body>""".replace("REPORT_HEADER", "Report for the capture {}".format(self.capture_sha1)) <body>""".replace("REPORT_HEADER", "{} {}".format(self.template["report_for_the_capture"], self.capture_sha1)).replace("REPORT_FOOTER", self.template["report_footer"])

View File

@ -1,12 +1,13 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from utils import get_iocs, get_apname, get_device from utils import get_iocs, get_apname, get_device, get_config
import time import time
import os import os
import subprocess as sp import subprocess as sp
import re import re
import json import json
import sys
class SuricataEngine(): class SuricataEngine():
@ -20,6 +21,14 @@ class SuricataEngine():
self.rules = [r[0] for r in get_iocs( self.rules = [r[0] for r in get_iocs(
"snort")] + self.generate_contextual_alerts() "snort")] + self.generate_contextual_alerts()
self.userlang = get_config(("frontend", "user_lang"))
# Load template language
if not re.match("^[a-z]{2,3}$", self.userlang):
self.userlang = "en"
with open(os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])), "locales/{}.json".format(self.userlang))) as f:
self.template = json.load(f)["alerts"]
def start_suricata(self): def start_suricata(self):
""" """
Launch suricata against the capture.pcap file. Launch suricata against the capture.pcap file.
@ -37,9 +46,8 @@ class SuricataEngine():
s = line.split("[**]")[1].strip() s = line.split("[**]")[1].strip()
m = re.search( m = re.search(
r"\[\d+\:(?P<sid>\d+)\:(?P<rev>\d+)\] (?P<title>[ -~]+)", s) r"\[\d+\:(?P<sid>\d+)\:(?P<rev>\d+)\] (?P<title>[ -~]+)", s)
self.alerts.append({"title": "Suricata rule tiggered: {}".format(m.group('title')), self.alerts.append({"title": self.template["SNORT-01"]["title"].format(m.group('title')),
"description": """A network detection rule has been tiggered. It's likely that your device has been compromised "description": self.template["SNORT-01"]["description"],
or contains a malicious application.""",
"level": "High", "level": "High",
"id": "SNORT-01"}) "id": "SNORT-01"})
# Remove fast.log # Remove fast.log

View File

@ -10,6 +10,8 @@ import subprocess as sp
import json import json
import pydig import pydig
import os import os
import re
import sys
class ZeekEngine(object): class ZeekEngine(object):
@ -24,10 +26,17 @@ class ZeekEngine(object):
self.files = [] self.files = []
self.whitelist = [] self.whitelist = []
# Get analysis configuration # Get analysis and userlang configuration
self.heuristics_analysis = get_config(("analysis", "heuristics")) self.heuristics_analysis = get_config(("analysis", "heuristics"))
self.iocs_analysis = get_config(("analysis", "iocs")) self.iocs_analysis = get_config(("analysis", "iocs"))
self.whitelist_analysis = get_config(("analysis", "whitelist")) self.whitelist_analysis = get_config(("analysis", "whitelist"))
self.userlang = get_config(("frontend", "user_lang"))
# Load template language
if not re.match("^[a-z]{2,3}$", self.userlang):
self.userlang = "en"
with open(os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])), "locales/{}.json".format(self.userlang))) as f:
self.template = json.load(f)["alerts"]
def fill_dns(self, dir): def fill_dns(self, dir):
""" """
@ -99,47 +108,41 @@ class ZeekEngine(object):
# Check for UDP / ICMP (strange from a smartphone.) # Check for UDP / ICMP (strange from a smartphone.)
if c["proto"] in ["UDP", "ICMP"]: if c["proto"] in ["UDP", "ICMP"]:
c["alert_tiggered"] = True c["alert_tiggered"] = True
self.alerts.append({"title": "{} communication going outside the local network to {}.".format(c["proto"].upper(), c["resolution"]), self.alerts.append({"title": self.template["PROTO-01"]["title"].format(c["proto"].upper(), c["resolution"]),
"description": "The {} protocol is commonly used in internal networks. Please, verify if the host {} leveraged other alerts which may ".format(c["proto"].upper(), c["resolution"]) "description": self.template["PROTO-01"]["description"].format(c["proto"].upper(), c["resolution"]),
+ "indicates a possible malicious behavior.", "host": c["resolution"],
"host": c["resolution"],
"level": "Moderate", "level": "Moderate",
"id": "PROTO-01"}) "id": "PROTO-01"})
# Check for use of ports over 1024. # Check for use of ports over 1024.
if c["port_dst"] >= max_ports: if c["port_dst"] >= max_ports:
c["alert_tiggered"] = True c["alert_tiggered"] = True
self.alerts.append({"title": "{} connection to {} to a port over or equal to {}.".format(c["proto"].upper(), c["resolution"], max_ports), self.alerts.append({"title": self.template["PROTO-02"]["title"].format(c["proto"].upper(), c["resolution"], max_ports),
"description": "{} connections have been seen to {} by using the port {}. The use of non-standard port can be sometimes associated to malicious activities. ".format(c["proto"].upper(), c["resolution"], c["port_dst"]) "description": self.template["PROTO-02"]["description"].format(c["proto"].upper(), c["resolution"], c["port_dst"]),
+ "We recommend to check if this host has a good reputation by looking on other alerts and search it on the internet.", "host": c["resolution"],
"host": c["resolution"],
"level": "Low", "level": "Low",
"id": "PROTO-02"}) "id": "PROTO-02"})
# Check for use of HTTP. # Check for use of HTTP.
if c["service"] == "http" and c["port_dst"] == http_default_port: if c["service"] == "http" and c["port_dst"] == http_default_port:
c["alert_tiggered"] = True c["alert_tiggered"] = True
self.alerts.append({"title": "HTTP communications have been done to the host {}".format(c["resolution"]), self.alerts.append({"title": self.template["PROTO-03"]["title"].format(c["resolution"]),
"description": "Your device exchanged with the host {} by using HTTP, an unencrypted protocol. ".format(c["resolution"]) "description": self.template["PROTO-03"]["description"].format(c["resolution"]),
+ "Even if this behavior is not malicious by itself, it is unusual to see HTTP communications issued from smartphone applications " "host": c["resolution"],
+ "running in the background. Please check the host reputation by searching it on the internet.",
"host": c["resolution"],
"level": "Low", "level": "Low",
"id": "PROTO-03"}) "id": "PROTO-03"})
# Check for use of HTTP on a non standard port. # Check for use of HTTP on a non standard port.
if c["service"] == "http" and c["port_dst"] != http_default_port: if c["service"] == "http" and c["port_dst"] != http_default_port:
c["alert_tiggered"] = True c["alert_tiggered"] = True
self.alerts.append({"title": "HTTP communications have been seen to the host {} on a non standard port ({}).".format(c["resolution"], c["port_dst"]), self.alerts.append({"title": self.template["PROTO-04"]["title"].format(c["resolution"], c["port_dst"]),
"description": "Your device exchanged with the host {} by using HTTP, an unencrypted protocol on the port {}. ".format(c["resolution"], c["port_dst"]) "description": self.template["PROTO-04"]["description"].format(c["resolution"], c["port_dst"]),
+ "This behavior is quite unusual. Please check the host reputation by searching it on the internet.", "host": c["resolution"],
"host": c["resolution"],
"level": "Moderate", "level": "Moderate",
"id": "PROTO-04"}) "id": "PROTO-04"})
# Check for non-resolved IP address. # Check for non-resolved IP address.
if c["ip_dst"] == c["resolution"]: if c["ip_dst"] == c["resolution"]:
c["alert_tiggered"] = True c["alert_tiggered"] = True
self.alerts.append({"title": "The server {} hasn't been resolved by any DNS query during the session".format(c["ip_dst"]), self.alerts.append({"title": self.template["PROTO-05"]["title"].format(c["ip_dst"]),
"description": "It means that the server {} is likely not resolved by any domain name or the resolution has already been cached by ".format(c["ip_dst"]) "description": self.template["PROTO-05"]["description"].format(c["ip_dst"]),
+ "the device. If the host appears in other alerts, please check it.",
"host": c["ip_dst"], "host": c["ip_dst"],
"level": "Low", "level": "Low",
"id": "PROTO-05"}) "id": "PROTO-05"})
@ -159,10 +162,8 @@ class ZeekEngine(object):
for host in bl_hosts: for host in bl_hosts:
if c["ip_dst"] == host[0]: if c["ip_dst"] == host[0]:
c["alert_tiggered"] = True c["alert_tiggered"] = True
self.alerts.append({"title": "A connection has been made to {} ({}) which is tagged as {}.".format(c["resolution"], c["ip_dst"], host[1].upper()), self.alerts.append({"title": self.template["IOC-01"]["title"].format(c["resolution"], c["ip_dst"], host[1].upper()),
"description": "The host {} has been explicitly blacklisted for malicious activities. Your device is likely compromised ".format(c["ip_dst"]) "description": self.template["IOC-01"]["description"].format(c["ip_dst"]),
+ "and needs to be investigated more deeply by IT security professionals.",
"host": c["resolution"], "host": c["resolution"],
"level": "High", "level": "High",
"id": "IOC-01"}) "id": "IOC-01"})
@ -171,10 +172,8 @@ class ZeekEngine(object):
for cidr in bl_cidrs: for cidr in bl_cidrs:
if IPAddress(c["ip_dst"]) in cidr[0]: if IPAddress(c["ip_dst"]) in cidr[0]:
c["alert_tiggered"] = True c["alert_tiggered"] = True
self.alerts.append({"title": "Communication to {} under the CIDR {} which is tagged as {}.".format(c["resolution"], cidr[0], cidr[1].upper()), self.alerts.append({"title": self.template["IOC-02"]["title"].format(c["resolution"], cidr[0], cidr[1].upper()),
"description": "The server {} is hosted under a network which is known to host malicious activities. Even if this behavior is not malicious by itself, ".format(c["resolution"]) "description": self.template["IOC-02"]["description"].format(c["resolution"]),
+ "you need to check if other alerts are also mentioning this host. If you have some doubts, please "
+ "search this host on the internet to see if its legit or not.",
"host": c["resolution"], "host": c["resolution"],
"level": "Moderate", "level": "Moderate",
"id": "IOC-02"}) "id": "IOC-02"})
@ -183,42 +182,37 @@ class ZeekEngine(object):
if c["resolution"].endswith(domain[0]): if c["resolution"].endswith(domain[0]):
if domain[1] != "tracker": if domain[1] != "tracker":
c["alert_tiggered"] = True c["alert_tiggered"] = True
self.alerts.append({"title": "A DNS request have been done to {} which is tagged as {}.".format(c["resolution"], domain[1].upper()), self.alerts.append({"title": self.template["IOC-03"]["title"].format(c["resolution"], domain[1].upper()),
"description": "The domain name {} seen in the capture has been explicitly tagged as malicious. This indicates that ".format(c["resolution"]) "description": self.template["IOC-03"]["description"].format(c["resolution"]),
+ "your device is likely compromised and needs to be investigated deeply.",
"host": c["resolution"], "host": c["resolution"],
"level": "High", "level": "High",
"id": "IOC-03"}) "id": "IOC-03"})
else: else:
c["alert_tiggered"] = True c["alert_tiggered"] = True
self.alerts.append({"title": "A DNS request have been done to {} which is tagged as {}.".format(c["resolution"], domain[1].upper()), self.alerts.append({"title": self.template["IOC-04"]["title"].format(c["resolution"], domain[1].upper()),
"description": "The domain name {} seen in the capture has been explicitly tagged as a Tracker. This ".format(c["resolution"]) "description": self.template["IOC-04"]["description"].format(c["resolution"]),
+ "indicates that one of the active apps is geo-tracking your moves.",
"host": c["resolution"], "host": c["resolution"],
"level": "Moderate", "level": "Moderate",
"id": "IOC-03"}) "id": "IOC-04"})
# Check for blacklisted FreeDNS. # Check for blacklisted FreeDNS.
for domain in bl_freedns: for domain in bl_freedns:
if c["resolution"].endswith("." + domain[0]): if c["resolution"].endswith("." + domain[0]):
c["alert_tiggered"] = True c["alert_tiggered"] = True
self.alerts.append({"title": "A DNS request have been done to the domain {} which is a Free DNS.".format(c["resolution"]), self.alerts.append({"title": self.template["IOC-05"]["title"].format(c["resolution"]),
"description": "The domain name {} is using a Free DNS service. This kind of service is commonly used by cybercriminals ".format(c["resolution"]) "description": self.template["IOC-05"]["description"].format(c["resolution"]),
+ "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.",
"host": c["resolution"], "host": c["resolution"],
"level": "Moderate", "level": "Moderate",
"id": "IOC-04"}) "id": "IOC-05"})
# Check for suspect tlds. # Check for suspect tlds.
for tld in bl_tlds: for tld in bl_tlds:
if c["resolution"].endswith(tld[0]): if c["resolution"].endswith(tld[0]):
c["alert_tiggered"] = True c["alert_tiggered"] = True
self.alerts.append({"title": "A DNS request have been done to the domain {} which contains a suspect TLD.".format(c["resolution"]), self.alerts.append({"title": self.template["IOC-06"]["title"].format(c["resolution"]),
"description": "The domain name {} is using a suspect Top Level Domain ({}). Even not malicious, this non-generic TLD is used regularly by cybercrime ".format(c["resolution"], tld[0]) "description": self.template["IOC-06"]["description"].format(c["resolution"], tld[0]),
+ "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.",
"host": c["resolution"], "host": c["resolution"],
"level": "Low", "level": "Low",
"id": "IOC-05"}) "id": "IOC-06"})
# Check for use of suspect nameservers. # Check for use of suspect nameservers.
try: try:
@ -230,13 +224,11 @@ class ZeekEngine(object):
for ns in bl_nameservers: for ns in bl_nameservers:
if name_servers[0].endswith(".{}.".format(ns[0])): if name_servers[0].endswith(".{}.".format(ns[0])):
c["alert_tiggered"] = True c["alert_tiggered"] = True
self.alerts.append({"title": "The domain {} is using a suspect nameserver ({}).".format(c["resolution"], name_servers[0]), self.alerts.append({"title": self.template["IOC-07"]["title"].format(c["resolution"], name_servers[0]),
"description": "The domain name {} is using a nameserver that has been explicitly tagged to be associated to malicious activities. ".format(c["resolution"]) "description": self.template["IOC-07"]["description"].format(c["resolution"]),
+ "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.",
"host": c["resolution"], "host": c["resolution"],
"level": "Moderate", "level": "Moderate",
"id": "IOC-06"}) "id": "IOC-07"})
def files_check(self, dir): def files_check(self, dir):
""" """
@ -268,12 +260,11 @@ class ZeekEngine(object):
if f["sha1"] == cert[0]: if f["sha1"] == cert[0]:
host = self.resolve(f["ip_dst"]) host = self.resolve(f["ip_dst"])
c["alert_tiggered"] = True c["alert_tiggered"] = True
self.alerts.append({"title": "A certificate associated to {} activities have been found in the communication to {}.".format(cert[1].upper(), host), self.alerts.append({"title": self.template["IOC-08"]["title"].format(cert[1].upper(), host),
"description": "The certificate ({}) associated to {} has been explicitly tagged as malicious. This indicates that ".format(f["sha1"], host) "description": self.template["IOC-08"]["description"].format(f["sha1"], host),
+ "your device is likely compromised and need a forensic analysis.",
"host": host, "host": host,
"level": "High", "level": "High",
"id": "IOC-07"}) "id": "IOC-08"})
def ssl_check(self, dir): def ssl_check(self, dir):
""" """
@ -306,30 +297,24 @@ class ZeekEngine(object):
# Check for non generic SSL port. # Check for non generic SSL port.
if cert["port"] not in ssl_default_ports: if cert["port"] not in ssl_default_ports:
c["alert_tiggered"] = True c["alert_tiggered"] = True
self.alerts.append({"title": "SSL connection done on a non standard port ({}) to {}".format(cert["port"], host), self.alerts.append({"title": self.template["SSL-01"]["title"].format(cert["port"], host),
"description": "It is not common to see SSL connections issued from smartphones using non-standard ports. Even this can be totally legit," "description": self.template["SSL-01"]["description"].format(host),
+ " we recommend to check the reputation of {}, by looking at its WHOIS record, the associated autonomus system, its creation date, and ".format(host)
+ " by searching it the internet.",
"host": host, "host": host,
"level": "Moderate", "level": "Moderate",
"id": "SSL-01"}) "id": "SSL-01"})
# Check Free SSL certificates. # Check Free SSL certificates.
if cert["issuer"] in free_issuers: if cert["issuer"] in free_issuers:
c["alert_tiggered"] = True c["alert_tiggered"] = True
self.alerts.append({"title": "An SSL connection to {} is using a free certificate.".format(host), self.alerts.append({"title": self.template["SSL-02"]["title"].format(host),
"description": "Free certificates — such as Let's Encrypt — are wildly used by command and control servers associated to " "description": self.template["SSL-02"]["description"],
+ "malicious implants or phishing web pages. We recommend to check the host associated to this certificate, "
+ "by looking at the domain name, its creation date, or by checking its reputation on the internet.",
"host": host, "host": host,
"level": "Moderate", "level": "Moderate",
"id": "SSL-02"}) "id": "SSL-02"})
# Check for self-signed certificates. # Check for self-signed certificates.
if cert["validation_status"] == "self signed certificate in certificate chain": if cert["validation_status"] == "self signed certificate in certificate chain":
c["alert_tiggered"] = True c["alert_tiggered"] = True
self.alerts.append({"title": "The certificate associated to {} is self-signed.".format(host), self.alerts.append({"title": self.template["SSL-03"]["title"].format(host),
"description": "The use of self-signed certificates is a common thing for attacker infrastructure. We recommend to check the host {} ".format(host) "description": self.template["SSL-03"]["description"].format(host),
+ "which is associated to this certificate, by looking at the domain name (if any), its WHOIS record, its creation date, and "
+ " by checking its reputation on the internet.",
"host": host, "host": host,
"level": "Moderate", "level": "Moderate",
"id": "SSL-03"}) "id": "SSL-03"})
@ -349,8 +334,8 @@ class ZeekEngine(object):
for host, nb in hosts.items(): for host, nb in hosts.items():
if nb >= get_config(("analysis", "max_alerts")): if nb >= get_config(("analysis", "max_alerts")):
self.alerts.append({"title": "Check alerts for {}".format(host), self.alerts.append({"title": self.template["ADV-01"]["title"].format(host),
"description": "Please, check the reputation of the host {}, this one seems to be malicious as it leveraged {} alerts during the session.".format(host, nb), "description": self.template["ADV-01"]["description"].format(host, nb),
"host": host, "host": host,
"level": "High", "level": "High",
"id": "ADV-01"}) "id": "ADV-01"})

108
analysis/locales/en.json Normal file
View File

@ -0,0 +1,108 @@
{
"alerts": {
"PROTO-01": {
"title": "{} communication going outside the local network to {}.",
"description": "The {} protocol is commonly used in internal networks. Please, verify if the host {} leveraged other alerts which may indicates a possible malicious behavior."
},
"PROTO-02": {
"title": "{} connection to {} to a port over or equal to {}.",
"description": "{} connections have been seen to {} by using the port {}. The use of non-standard port can be sometimes associated to malicious activities. We recommend to check if this host has a good reputation by looking on other alerts and search it on the internet."
},
"PROTO-03": {
"title": "HTTP communications have been done to the host {}",
"description": "Your device exchanged with the host {} by using HTTP, an unencrypted protocol. Even if this behavior is not malicious by itself, it is unusual to see HTTP communications issued from smartphone applications running in the background. Please check the host reputation by searching it on the internet."
},
"PROTO-04": {
"title": "HTTP communications have been seen to the host {} on a non standard port ({}).",
"description": "Your device exchanged with the host {} by using HTTP, an unencrypted protocol on the port {}. This behavior is quite unusual. Please check the host reputation by searching it on the internet."
},
"PROTO-05": {
"title": "The server {} hasn't been resolved by any DNS query during the session",
"description": "It means that the server {} is likely not resolved by any domain name or the resolution has already been cached by the device. If the host appears in other alerts, please check it."
},
"IOC-01": {
"title": "A connection has been made to {} ({}) which is tagged as {}.",
"description": "The host {} has been explicitly blacklisted for malicious activities. Your device is likely compromised and needs to be investigated more deeply by IT security professionals."
},
"IOC-02": {
"title": "Communication to {} under the CIDR {} which is tagged as {}.",
"description": "The server {} is hosted under a network which is known to host malicious activities. Even if this behavior is not malicious by itself, you need to check if other alerts are also mentioning this host. If you have some doubts, please search this host on the internet to see if its legit or not."
},
"IOC-03": {
"title": "A DNS 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-04": {
"title": "A DNS request have been done to {} which is tagged as {}.",
"description": "The domain name {} seen in the capture has been explicitly tagged as a Tracker. This indicates that one of the active apps is geo-tracking your moves."
},
"IOC-05": {
"title": "A DNS 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-06": {
"title": "A DNS 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."
},
"IOC-07": {
"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."
},
"IOC-08": {
"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."
},
"SSL-01": {
"title": "SSL connection done on a non standard port ({}) to {}",
"description": "It is not common to see SSL connections issued from smartphones using non-standard ports. Even this can be totally legit, we recommend to check the reputation of {}, by looking at its WHOIS record, the associated autonomus system, its creation date, and by searching it the internet."
},
"SSL-02": {
"title": "An SSL connection to {} is using a free certificate.",
"description": "Free certificates — such as Let's Encrypt — are wildly used by command and control servers associated to malicious implants or phishing web pages. We recommend to check the host associated to this certificate, by looking at the domain name, its creation date, or by checking its reputation on the internet."
},
"SSL-03": {
"title": "The certificate associated to {} is self-signed.",
"description": "The use of self-signed certificates is a common thing for attacker infrastructure. We recommend to check the host {} which is associated to this certificate, by looking at the domain name (if any), its WHOIS record, its creation date, and by checking its reputation on the internet."
},
"ADV-01": {
"title": "Check the alerts for {}",
"description": "Please, check the reputation of the host {}, this one seems to be malicious as it leveraged {} alerts during the session."
},
"SNORT-01": {
"title": "Suricata rule tiggered: {}",
"description": "A network detection rule has been triggered. It's likely that your device has been compromised or have some suspect behaviour."
}
},
"report": {
"numbers": [
"one",
"two",
"three",
"four",
"five",
"six",
"seven",
"eight",
"nine"
],
"suspect_title": "Suspect communications",
"uncat_title": "Uncategorized communications",
"whitelist_title": "Whitelisted communications",
"protocol": "Protocol",
"domain": "Domain",
"dst_ip": "Dst IP address",
"dst_port": "Dst port number",
"device_name": "Device name",
"device_mac": "Device MAC address",
"report_generated_on": "Report generated on",
"capture_duration": "Capture duration",
"packets_number": "Number of packets",
"capture_sha1": "Capture SHA1",
"report_for_the_capture": "Report for the capture",
"report_footer": "This report has been autogenerated by a Tinycheck device. For any question, bug report or feedback, please contact tinycheck@kaspersky.com.",
"high_msg": "Your device seems to be compromised as you have {} high alert(s).",
"moderate_msg": "You have {} moderate alert(s), your device might be compromised. Please look at them carefully.",
"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."
}
}

108
analysis/locales/fr.json Normal file
View File

@ -0,0 +1,108 @@
{
"alerts": {
"PROTO-01": {
"title": "Une communication {} va à l'extérieur du réseau jusqu'à {}.",
"description": "Le protocole {} est généralement utilisé dans des réseaux internes. Vérifiez si le serveur {} a soulevé d'autres alertes. Ceci pourrait indiquer la présence d'une possible application malveillante."
},
"PROTO-02": {
"title": "Connexion {} vers {} sur un port au dessus ou égal à {}.",
"description": "Des connexions {} vers {} ont été vues utilisant le port {}. L'utilisation d'un numéro de port non standard peut être associé à des activités malveillantes. Nous recommandons de vérifier si ce serveur possède une bonne réputation en regardant les autres alertes et en le cherchant sur Internet."
},
"PROTO-03": {
"title": "Des communications HTTP ont été réalisées vers {}",
"description": "Votre appareil a communiqué avec le serveur {} en utilisant HTTP, un protocole non chiffré. Même si ce n'est pas malveillant en tant que tel, il est rare de voir des communications HTTP issues d'applications installées sur des smartphones exécutées en arrière plan. Il est conseillé de vérifier la réputation du serveur en le recherchant sur Internet."
},
"PROTO-04": {
"title": "Des communications HTTP ont été réalisées vers {} sur un port non standard ({}).",
"description": "Votre appareil a communiqué vers le serveur {} en utilisant HTTP, un protocole non chiffré sur le port {}. Ce type de communication peut être signe d'une activité malveillante sur votre appareil car il est très rare qu'HTTP utilise ce port. Il est conseillé de vérifier la réputation du serveur en le recherchant sur Internet."
},
"PROTO-05": {
"title": "Le serveur {} n'a pas été résolu par le protocole DNS durant la session",
"description": "Cela signifie que le serveur {} ne possède pas de nom de domaine associé ou que sa résolution a déjà été mise en cache par votre appareil. Si le serveur apparait dans d'autres alertes, merci de vérifier sa réputation."
},
"IOC-01": {
"title": "Connexion vers {} ({}) qui est tagué en tant que {}.",
"description": "Le serveur {} est connu pour être associé à des activités malveillantes. Votre appareil est surement compromis et doit être investigué plus en détails par une équipe professionnelle."
},
"IOC-02": {
"title": "Connexion vers {} appartenant au bloc réseau {} qui est tagué en tant que {}.",
"description": "Le serveur {} est hébergé dans un réseau qui est connu pour abriter des activités malveillantes. Même si ce n'est pas malveillant en tant que tel, vous devez regarder si d'autres alertes mentionnent ce serveur. Si vous avez certains doutes, recherchez sur Internet ce serveur pour savoir s'il semble être légitime ou non."
},
"IOC-03": {
"title": "Requête DNS 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-04": {
"title": "Requête DNS vers le domaine {} qui est tagué en tant que {}.",
"description": "Le nom de domaine {} vers lequel communique votre appareil a été explicitement catégorisé comme un tracker. Ceci indique d'une application active sur votre appareil est entrain de vous géo-localiser."
},
"IOC-05": {
"title": "Requête DNS 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-06": {
"title": "Requête DNS 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."
},
"IOC-07": {
"title": "Le domaine {} utilise un serveur de noms suspect ({}).",
"description": "Le nom de domaine {} utilise un server de nom 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é."
},
"IOC-08": {
"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."
},
"SSL-01": {
"title": "Connexion SSL utilisant un port non standard ({}) vers {}",
"description": "Il n'est pas commun de voir des connexions SSL issues de smartphones utiliser des ports non standards. Même si cela peut être totalement légitime, il est recommandé d'évaluer la réputation du serveur {}, en regardant son enregistrement WHOIS, son système autonome, sa date de création et en le recherchant sur Internet."
},
"SSL-02": {
"title": "Une connexion SSL vers {} utilise un certificat gratuit.",
"description": "Les certificats gratuits — tels que Let's Encrypt - sont largement utilisés par des serveurs de commande et de contrôle associés à des implants malveillants ou à des pages Web de phishing. Nous vous recommandons de vérifier le serveur associé à ce certificat, en regardant le nom de domaine, sa date de création, ou en vérifiant sa réputation sur Internet."
},
"SSL-03": {
"title": "Le certificat associé à {} est auto-signé.",
"description": "L'utilisation de certificats auto-signés est une chose courante pour des infrastructures d'attaque associées à des activités malveillantes. Nous vous recommandons de vérifier le serveur {} qui est associé à ce certificat, en regardant le nom de domaine (le cas échéant), son enregistrement WHOIS, sa date de création, et en vérifiant sa réputation sur Internet."
},
"ADV-01": {
"title": "Vérifiez les alertes liées au serveur {}",
"description": "Merci de vérifier la réputation et les alertes liées au serveur {}, ce dernier semble malveillant, ayant engendré {} alertes durant la session de capture."
},
"SNORT-01": {
"title": "Règle suricata déclanchée : {}",
"description": "Une règle de détection de réseau a été déclenchée. Il est probable que votre appareil ait été compromis ou présente un comportement suspect."
}
},
"report": {
"numbers": [
"une",
"deux",
"trois",
"quatre",
"cinq",
"six",
"sept",
"huit",
"neuf"
],
"suspect_title": "Communications suspectes",
"uncat_title": "Communications non catégorisées",
"whitelist_title": "Communications légitimes",
"protocol": "Protocole",
"domain": "Domaine",
"dst_ip": "Adresse IP",
"dst_port": "Port",
"device_name": "Nom de l'appareil",
"device_mac": "Adresse MAC de l'appareil",
"report_generated_on": "Rapport généré le ",
"capture_duration": "Durée de la capture",
"packets_number": "Nombre de paquets",
"capture_sha1": "SHA1 de la capture",
"report_for_the_capture": "Rapport pour la capture",
"report_footer": "Ce rapport a été automatiquement généré par une instance de TinyCheck. Pour toute question et retours, n'hésitez pas à contacter tinycheck@kaspersky.com.",
"high_msg": "Votre appareil semble être compromis car vous avez {} alerte(s) élevée(s).",
"moderate_msg": "Vous avez {} alerte(s) modérée(s), votre appareil peut être compromis. Regardez ces alertes en détail.",
"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

@ -4638,6 +4638,24 @@
"domelementtype": "1" "domelementtype": "1"
} }
}, },
"dot-object": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/dot-object/-/dot-object-2.1.4.tgz",
"integrity": "sha512-7FXnyyCLFawNYJ+NhkqyP9Wd2yzuo+7n9pGiYpkmXCTYa8Ci2U0eUNDVg5OuO5Pm6aFXI2SWN8/N/w7SJWu1WA==",
"dev": true,
"requires": {
"commander": "^4.0.0",
"glob": "^7.1.5"
},
"dependencies": {
"commander": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
"dev": true
}
}
},
"dot-prop": { "dot-prop": {
"version": "5.3.0", "version": "5.3.0",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz",
@ -6877,6 +6895,12 @@
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
"dev": true "dev": true
}, },
"is-valid-glob": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz",
"integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=",
"dev": true
},
"is-windows": { "is-windows": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
@ -11315,6 +11339,32 @@
"integrity": "sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==", "integrity": "sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==",
"dev": true "dev": true
}, },
"vue-i18n": {
"version": "8.22.4",
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-8.22.4.tgz",
"integrity": "sha512-XLI5s0AdqMP2Lf4I4CmdmOq8kjb5DDFGR77wAuxCfpEuYSfhTRyyx6MetgZMiL6Lxa0DasjBOiOcciU3NkL3/Q=="
},
"vue-i18n-extract": {
"version": "1.1.10",
"resolved": "https://registry.npmjs.org/vue-i18n-extract/-/vue-i18n-extract-1.1.10.tgz",
"integrity": "sha512-DYqcjrAm4L95Ftz8AVIzUyrHCBQV326rbOru6B3W2vXHIzUUuJwwFgyVPE+d7YTeHD189eoOkRu0aL6EzeMWZQ==",
"dev": true,
"requires": {
"commander": "^6.1.0",
"dot-object": "^2.1.4",
"glob": "^7.1.6",
"is-valid-glob": "^1.0.0",
"js-yaml": "^3.14.0"
},
"dependencies": {
"commander": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz",
"integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==",
"dev": true
}
}
},
"vue-loader": { "vue-loader": {
"version": "15.9.3", "version": "15.9.3",
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.9.3.tgz", "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.9.3.tgz",

View File

@ -5,7 +5,8 @@
"scripts": { "scripts": {
"serve": "vue-cli-service serve --copy --port=4202", "serve": "vue-cli-service serve --copy --port=4202",
"build": "vue-cli-service build", "build": "vue-cli-service build",
"lint": "vue-cli-service lint" "lint": "vue-cli-service lint",
"vue-i18n-extract": "vue-i18n-extract report -a -v './src/**/*.?(js|vue)' -l './src/locales/*.?(json|yaml|yml)'"
}, },
"dependencies": { "dependencies": {
"@fnando/sparkline": "^0.3.10", "@fnando/sparkline": "^0.3.10",
@ -15,6 +16,7 @@
"sass-loader": "^10.0.4", "sass-loader": "^10.0.4",
"simple-keyboard": "^2.30.25", "simple-keyboard": "^2.30.25",
"vue": "^2.6.12", "vue": "^2.6.12",
"vue-i18n": "^8.22.4",
"vue-router": "^3.4.3" "vue-router": "^3.4.3"
}, },
"devDependencies": { "devDependencies": {
@ -24,6 +26,7 @@
"babel-eslint": "^10.1.0", "babel-eslint": "^10.1.0",
"eslint": "^7.9.0", "eslint": "^7.9.0",
"eslint-plugin-vue": "^6.2.2", "eslint-plugin-vue": "^6.2.2",
"vue-i18n-extract": "1.1.10",
"vue-template-compiler": "^2.6.12" "vue-template-compiler": "^2.6.12"
}, },
"eslintConfig": { "eslintConfig": {

View File

@ -28,13 +28,38 @@
</style> </style>
<script> <script>
document.title = 'TinyCheck Frontend' import axios from 'axios'
import Controls from "@/components/Controls.vue" import Controls from "@/components/Controls.vue"
document.title = 'TinyCheck Frontend'
export default { export default {
name: 'app', name: 'app',
components: { components: {
Controls Controls
},
methods: {
set_lang: function() {
if (window.config.user_lang) {
var lang = window.config.user_lang
if (Object.keys(this.$i18n.messages).includes(lang)) {
this.$i18n.locale = lang
document.querySelector('html').setAttribute('lang', lang)
}
}
},
get_config: function() {
axios.get('/api/misc/config', { timeout: 60000 })
.then(response => {
window.config = response.data
this.set_lang();
})
.catch(error => { console.log(error) });
}
},
created: function() {
window.config = {}
this.get_config();
} }
} }
</script> </script>

View File

@ -0,0 +1,81 @@
{
"home": {
"welcome_msg": "Welcome to TinyCheck.",
"help_msg": "We are going to help you to check your device.",
"start_btn": "Let's start!"
},
"analysis": {
"question": "Do you want to analyze the captured communications?",
"no_btn": "No, just save them",
"yes_btn": "Yes, let's do it",
"please_wait_msg": "Please wait during the analysis...",
"some_time_msg": "Yes, it can take some time..."
},
"capture": {
"intercept_coms_msg": "Intercepting the communications of ",
"stop_btn": "Stop the capture"
},
"generate-ap": {
"network_name": "Network name",
"network_password": "Network password",
"tap_msg": "Tap the white frame to generate a new network.",
"generate_ap_msg": "We generate an ephemeral network for you.",
"error_msg1": "Unfortunately, we got some issues <br />during the AP creation.",
"error_msg2": "Please verify that you've two WiFi interfaces on your device<br /> and try again by restarting it.",
"restart_btn": "Restart the device"
},
"report": {
"show_full_report": "Show the full report",
"start_new_capture": "Start a new capture",
"save_capture": "Save the capture",
"numbers": [
"zero",
"one",
"two",
"three",
"four",
"five",
"six",
"seven",
"eight",
"nine",
"ten",
"eleven"
],
"stalkerware_msg": "Your device is compromised by<br />a Stalkerware, please check the report.",
"location_msg": "An application is sharing your<br /> current geolocation with a third party.",
"fine_msg": "Everything looks fine, zero alerts.",
"high_msg": "You have {nb} high alert,<br />your device seems to be compromised.",
"moderate_msg": "You have {nb} moderate alerts,<br />your device might be compromised.",
"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",
"high": "high",
"moderate": "moderate",
"low": "low"
},
"wifi-select": {
"already_connected_question": "You seem to be already connected to a network.<br />Do you want to use the current connection?",
"no_btn": "No, use another",
"yes_btn": "Yes, use it.",
"wifi_name": "Wi-Fi name",
"refresh_btn": "Refresh networks list",
"not_connected": "You seem to not be connected to Internet.",
"please_config": "Please configure the Wi-Fi connection.",
"lets_do_btn": "Ok, let's do that.",
"wifi_connected": "Wi-Fi connected",
"wifi_not_connected": "Wi-Fi not connected. Please retry.",
"tap_keyboard": "Tap on the virtual keyboard to start",
"wifi_password": "Wi-Fi password",
"connect_to_it": "Connect to it"
},
"save-capture": {
"please_connect": "Please connect a USB key to save your capture.",
"we_are_saving": "We are saving your capture.",
"tap_msg": "You can tap the USB key to start a new capture.",
"capture_download": "The capture download is going to start...",
"start_capture_btn": "Start another capture"
}
}

View File

@ -0,0 +1,81 @@
{
"home": {
"welcome_msg": "Bienvenue sur TinyCheck.",
"help_msg": "Nous allons vous accompagner pour analyser votre appareil.",
"start_btn": "Allons-y !"
},
"analysis": {
"question": "Voulez-vous analyser les communications capturées ?",
"no_btn": "Non, enregistrez-les.",
"yes_btn": "Oui, allons-y !",
"please_wait_msg": "Merci d'attendre pendant l'analyse...",
"some_time_msg": "Oui, cela peut prendre du temps..."
},
"capture": {
"intercept_coms_msg": "Interception des communications de ",
"stop_btn": "Arrêter la capture"
},
"generate-ap": {
"network_name": "Nom du réseau Wi-Fi",
"network_password": "Mot de passe",
"tap_msg": "Appuyez sur le cadre blanc pour générer un nouveau réseau.",
"generate_ap_msg": "Nous générons un réseau Wi-Fi éphémère.",
"error_msg1": "Malheureusement, nous rencontrons des problèmes <br />lors de la création du point d'accès.",
"error_msg2": "Veuillez vérifier que vous disposez de <br /> deux interfaces WiFi sur votre appareil.",
"restart_btn": "Restart the device"
},
"report": {
"show_full_report": "Lire le rapport complet",
"start_new_capture": "Nouvelle capture",
"save_capture": "Sauvegarder la capture",
"numbers": [
"zéro",
"une",
"deux",
"trois",
"quatre",
"cinq",
"six",
"sept",
"huit",
"neuf",
"dix",
"onze"
],
"stalkerware_msg": "Votre smartphone est compromis<br /> par un Stalkerware, lisez le rapport.",
"location_msg": "Une application dévoile votre<br /> géo-localisation actelle.",
"fine_msg": "Aucun flux suspect ou malveillant détecté,<br />vous avez aucune alerte.",
"high_msg": "Vous avez {nb} alerte.s élevée.s,<br />votre appareil semble être compromis.",
"moderate_msg": "Vous avez {nb} alerte.s moyenne.s,<br />votre appareil est peut-être compromis.",
"low_msg": "Vous avez uniquement {nb} alerte.s basse.s<br /> n'hésitez pas à les consulter.",
"save_report": "Archiver le rapport",
"report_of": "Rapport de",
"ip_address": "Adresse IP :",
"mac_address": "Adresse MAC :",
"high": "elevee",
"moderate": "moyenne",
"low": "base"
},
"wifi-select": {
"already_connected_question": "Vous semblez être connecté à un réseau.<br />Voulez-vous utiliser cette connexion ?",
"no_btn": "Non, une autre",
"yes_btn": "Oui, utilisez celle-là",
"wifi_name": "Nom du réseau",
"refresh_btn": "Rafraichir la liste des réseaux",
"not_connected": "Vous semblez ne pas être connecté à Internet.",
"please_config": "Configurez la connexion Wi-Fi.",
"lets_do_btn": "Oui, allons-y",
"wifi_connected": "Wi-Fi connecté",
"wifi_not_connected": "Wi-Fi non connecté. Ressayez.",
"tap_keyboard": "Appuez sur cet espace pour écrire",
"wifi_password": "Mot de passe",
"connect_to_it": "Se connecter"
},
"save-capture": {
"please_connect": "Connectez une clé USB pour enregistrer votre capture.",
"we_are_saving": "Nous enregistrons votre capture.",
"tap_msg": "Vous pouvez appuyer sur l'animation pour lancer une nouvelle capture.",
"capture_download": "Le téléchargement de la capture va se lancer...",
"start_capture_btn": "Lancer une nouvelle capture"
}
}

View File

@ -1,10 +1,13 @@
import Vue from 'vue' import Vue from 'vue'
import App from './App.vue' import App from './App.vue'
import router from './router' import router from './router'
import { i18n } from '@/plugins/i18n'
Vue.config.productionTip = true Vue.config.productionTip = true
Vue.config.devtools = true Vue.config.devtools = true
new Vue({ new Vue({
router, router,
i18n,
render: h => h(App) render: h => h(App)
}).$mount('#app') }).$mount('#app')

View File

@ -0,0 +1,13 @@
import Vue from 'vue'
import VueI18n from 'vue-i18n'
Vue.use(VueI18n)
export const i18n = new VueI18n({
locale: 'en',
fallbackLocale: 'en',
messages: {
'en': require('@/locales/en.json'),
'fr': require('@/locales/fr.json')
}
})

View File

@ -41,12 +41,14 @@ const routes = [
component: () => import('../views/save-capture.vue'), component: () => import('../views/save-capture.vue'),
props: true props: true
}, },
{ path: '/analysis', {
path: '/analysis',
name: 'analysis', name: 'analysis',
component: () => import('../views/analysis.vue'), component: () => import('../views/analysis.vue'),
props: true props: true
}, },
{ path: '/report', {
path: '/report',
name: 'report', name: 'report',
component: () => import('../views/report.vue'), component: () => import('../views/report.vue'),
props: true props: true

View File

@ -1,15 +1,15 @@
<template> <template>
<div class="center"> <div class="center">
<div v-if="question"> <div v-if="question">
<p>Do you want to analyze the captured communications?</p> <p>{{ $t("analysis.question") }}</p>
<div class="empty-action"> <div class="empty-action">
<button class="btn" v-on:click="save_capture()">No, just save them</button> <button class="btn btn-primary" v-on:click="start_analysis()">Yes, let's do it</button> <button class="btn" v-on:click="save_capture()">{{ $t("analysis.no_btn") }}</button> <button class="btn btn-primary" v-on:click="start_analysis()">{{ $t("analysis.yes_btn") }}</button>
</div> </div>
</div> </div>
<div v-else-if="running"> <div v-else-if="running">
<img src="@/assets/loading.svg"/> <img src="@/assets/loading.svg"/>
<p class="legend" v-if="!long_waiting">Please wait during the analysis...</p> <p class="legend" v-if="!long_waiting">{{ $t("analysis.please_wait_msg") }}</p>
<p class="legend fade-in" v-if="long_waiting">Yes, it can take some time...</p> <p class="legend fade-in" v-if="long_waiting">{{ $t("analysis.some_time_msg") }}</p>
</div> </div>
</div> </div>
</template> </template>
@ -25,7 +25,8 @@ export default {
question: true, question: true,
running: false, running: false,
check_alerts: false, check_alerts: false,
long_waiting: false long_waiting: false,
translation: {}
} }
}, },
props: { props: {
@ -38,7 +39,7 @@ export default {
setTimeout(function () { this.long_waiting = true }.bind(this), 15000); setTimeout(function () { this.long_waiting = true }.bind(this), 15000);
axios.get(`/api/analysis/start/${this.capture_token}`, { timeout: 60000 }) axios.get(`/api/analysis/start/${this.capture_token}`, { timeout: 60000 })
.then(response => { .then(response => {
if(response.data.message == "Analysis started") if(response.data.message == 'Analysis started')
this.check_alerts = setInterval(() => { this.get_alerts(); }, 500); this.check_alerts = setInterval(() => { this.get_alerts(); }, 500);
}) })
.catch(error => { .catch(error => {
@ -48,13 +49,14 @@ export default {
get_alerts: function() { get_alerts: function() {
axios.get(`/api/analysis/report/${this.capture_token}`, { timeout: 60000 }) axios.get(`/api/analysis/report/${this.capture_token}`, { timeout: 60000 })
.then(response => { .then(response => {
if(response.data.message != "No report yet"){ if(response.data.message != 'No report yet'){
clearInterval(this.check_alerts); clearInterval(this.check_alerts);
this.long_waiting = false; this.long_waiting = false;
this.running = false; this.running = false;
router.replace({ name: 'report', params: { alerts : response.data.alerts, router.replace({ name: 'report',
device : response.data.device, params: { alerts : response.data.alerts,
capture_token : this.capture_token } }); device : response.data.device,
capture_token : this.capture_token } });
} }
}) })
.catch(error => { .catch(error => {
@ -63,7 +65,8 @@ export default {
}, },
save_capture: function() { save_capture: function() {
var capture_token = this.capture_token var capture_token = this.capture_token
router.replace({ name: 'save-capture', params: { capture_token: capture_token } }); router.replace({ name: 'save-capture',
params: { capture_token: capture_token } });
} }
} }
} }

View File

@ -4,9 +4,9 @@
<div class="center"> <div class="center">
<div class="footer"> <div class="footer">
<h3 class="timer">{{timer_hours}}:{{timer_minutes}}:{{timer_seconds}}</h3> <h3 class="timer">{{timer_hours}}:{{timer_minutes}}:{{timer_seconds}}</h3>
<p>Intercepting the communications of {{device_name}}.</p> <p>{{$t("capture.intercept_coms_msg")}} {{device_name}}.</p>
<div class="empty-action"> <div class="empty-action">
<button class="btn" :class="[ loading ? 'loading' : 'btn-primary', ]" v-on:click="stop_capture()">Stop the capture</button> <button class="btn" :class="[ loading ? 'loading' : 'btn-primary', ]" v-on:click="stop_capture()">{{$t("capture.stop_btn")}}</button>
</div> </div>
</div> </div>
</div> </div>
@ -29,7 +29,8 @@ export default {
loading: false, loading: false,
stats_interval: false, stats_interval: false,
chrono_interval: false, chrono_interval: false,
sparklines: false sparklines: false,
translation: {}
} }
}, },
props: { props: {
@ -42,16 +43,16 @@ export default {
}, },
stop_capture: function() { stop_capture: function() {
this.loading = true this.loading = true
axios.get(`/api/network/ap/stop`, { timeout: 30000 }) axios.get('/api/network/ap/stop', { timeout: 30000 })
axios.get(`/api/capture/stop`, { timeout: 30000 }) axios.get('/api/capture/stop', { timeout: 30000 })
.then(response => (this.handle_finish(response.data))) .then(response => (this.handle_finish(response.data)))
}, },
get_stats: function() { get_stats: function() {
axios.get(`/api/capture/stats`, { timeout: 30000 }) axios.get('/api/capture/stats', { timeout: 30000 })
.then(response => (this.handle_stats(response.data))) .then(response => (this.handle_stats(response.data)))
}, },
handle_stats: function(data) { handle_stats: function(data) {
if (data.packets.length) sparkline(document.querySelector("#sparkline"), data.packets); if (data.packets.length) sparkline(document.querySelector('#sparkline'), data.packets);
}, },
handle_finish: function(data) { handle_finish: function(data) {
clearInterval(this.chrono_interval); clearInterval(this.chrono_interval);
@ -65,28 +66,28 @@ export default {
chrono: function() { chrono: function() {
var time = Date.now() - this.capture_start var time = Date.now() - this.capture_start
this.timer_hours = Math.floor(time / (60 * 60 * 1000)); this.timer_hours = Math.floor(time / (60 * 60 * 1000));
this.timer_hours = (this.timer_hours < 10) ? "0" + this.timer_hours : this.timer_hours this.timer_hours = (this.timer_hours < 10) ? '0' + this.timer_hours : this.timer_hours
time = time % (60 * 60 * 1000); time = time % (60 * 60 * 1000);
this.timer_minutes = Math.floor(time / (60 * 1000)); this.timer_minutes = Math.floor(time / (60 * 1000));
this.timer_minutes = (this.timer_minutes < 10) ? "0" + this.timer_minutes : this.timer_minutes this.timer_minutes = (this.timer_minutes < 10) ? '0' + this.timer_minutes : this.timer_minutes
time = time % (60 * 1000); time = time % (60 * 1000);
this.timer_seconds = Math.floor(time / 1000); this.timer_seconds = Math.floor(time / 1000);
this.timer_seconds = (this.timer_seconds < 10) ? "0" + this.timer_seconds : this.timer_seconds this.timer_seconds = (this.timer_seconds < 10) ? '0' + this.timer_seconds : this.timer_seconds
}, },
setup_sparklines: function() { setup_sparklines: function() {
axios.get(`/api/misc/config`, { timeout: 60000 }) axios.get('/api/misc/config', { timeout: 60000 })
.then(response => { .then(response => {
if(response.data.sparklines){ if(response.data.sparklines){
this.sparklines = true this.sparklines = true
this.sparkwidth = window.screen.width + "px"; this.sparkwidth = window.screen.width + 'px';
this.sparkheight = Math.trunc(window.screen.height / 5) + "px"; this.sparkheight = Math.trunc(window.screen.height / 5) + 'px';
this.stats_interval = setInterval(() => { this.get_stats(); }, 500); this.stats_interval = setInterval(() => { this.get_stats(); }, 500);
} }
}) })
.catch(error => { .catch(error => {
console.log(error) console.log(error)
}); });
} },
}, },
created: function() { created: function() {
// Get the config for the sparklines. // Get the config for the sparklines.

View File

@ -9,29 +9,30 @@
</div> </div>
<div class="divider-vert white-bg" data-content="OR"></div> <div class="divider-vert white-bg" data-content="OR"></div>
<div class="column col-5"><br /> <div class="column col-5"><br />
<span class="light-grey">Network name: </span><br /> <span class="light-grey">{{ $t("generate-ap.network_name") }} </span><br />
<h4>{{ ssid_name }}</h4> <h4>{{ ssid_name }}</h4>
<span class="light-grey">Network password: </span><br /> <span class="light-grey">{{ $t("generate-ap.network_password") }} </span><br />
<h4>{{ ssid_password }}</h4> <h4>{{ ssid_password }}</h4>
</div> </div>
</div> </div>
</div> </div>
<br /><br /><br /><br /> <br /><br /><br /><br /><br /><br /> <br /><br /><br /><br /> <br /><br /><br /><br /><br /><br />
<!-- Requite a CSS MEME for that shit :) --> <!-- Requite a CSS MEME for that shit :) -->
<span class="legend">Tap the white frame to generate a new network.</span> <span class="legend">{{ $t("generate-ap.tap_msg") }}</span>
</div> </div>
<div v-else> <div v-else>
<img src="@/assets/loading.svg"/> <img src="@/assets/loading.svg"/>
<p class="legend">We generate an ephemeral network for you.</p> <p class="legend">{{ $t("generate-ap.generate_ap_msg") }}</p>
</div> </div>
</div> </div>
<div v-else> <div v-else>
<p> <p>
<strong>Unfortunately, we got some issues <br />during the AP creation.</strong> <strong v-html="$t('generate-ap.error_msg1')"></strong>
<br /><br />
<span v-html="$t('generate-ap.error_msg2')"></span>
<br /><br /> <br /><br />
Please verify that you've two WiFi interfaces on your device<br /> and try again by restarting it.<br /><br />
</p> </p>
<button v-if="reboot_option" class="btn" v-on:click="reboot()">Restart the device</button> <button v-if="window.config.reboot_option" class="btn" v-on:click="reboot()">{{ $t("generate-ap.restart_btn") }}</button>
</div> </div>
</div> </div>
@ -54,14 +55,15 @@ export default {
interval: false, interval: false,
error: false, error: false,
reboot_option: false, reboot_option: false,
attempts: 3 attempts: 3,
translation: {}
} }
}, },
methods: { methods: {
generate_ap: function() { generate_ap: function() {
clearInterval(this.interval); clearInterval(this.interval);
this.ssid_name = false this.ssid_name = false
axios.get(`/api/network/ap/start`, { timeout: 30000 }) axios.get('/api/network/ap/start', { timeout: 30000 })
.then(response => (this.show_ap(response.data))) .then(response => (this.show_ap(response.data)))
}, },
show_ap: function(data) { show_ap: function(data) {
@ -80,11 +82,11 @@ export default {
} }
}, },
start_capture: function() { start_capture: function() {
axios.get(`/api/capture/start`, { timeout: 30000 }) axios.get('/api/capture/start', { timeout: 30000 })
.then(response => (this.get_capture_token(response.data))) .then(response => (this.get_capture_token(response.data)))
}, },
reboot: function() { reboot: function() {
axios.get(`/api/misc/reboot`, { timeout: 30000 }) axios.get('/api/misc/reboot', { timeout: 30000 })
.then(response => { console.log(response)}) .then(response => { console.log(response)})
}, },
get_capture_token: function(data) { get_capture_token: function(data) {
@ -115,19 +117,9 @@ export default {
} }
}); });
} }
}, }
load_config: function() {
axios.get(`/api/misc/config`, { timeout: 60000 })
.then(response => {
this.reboot_option = response.data.reboot_option
})
.catch(error => {
console.log(error)
});
},
}, },
created: function() { created: function() {
this.load_config()
this.generate_ap(); this.generate_ap();
} }
} }

View File

@ -1,8 +1,8 @@
<template> <template>
<div class="center"> <div class="center">
<h3 class="lobster">Welcome to TinyCheck.</h3> <h3 class="lobster">{{ $t("home.welcome_msg") }}</h3>
<p>We are going to help you to check your device.</p> <p>{{ $t("home.help_msg") }}</p>
<button class="btn btn-primary" v-on:click="next()">Let's start!</button> <button class="btn btn-primary" v-on:click="next()">{{ $t("home.start_btn") }}</button>
</div> </div>
</template> </template>
@ -11,22 +11,26 @@ import router from '../router'
export default { export default {
name: 'home', name: 'home',
props: { saved_ssid: String, iface_out: String, list_ssids: Array, internet: Boolean }, props: { saved_ssid: String, list_ssids: Array, internet: Boolean },
data() {
return {
translation: {},
}
},
methods: { methods: {
next: function() { next: function() {
var saved_ssid = this.saved_ssid var saved_ssid = this.saved_ssid
var list_ssids = this.list_ssids var list_ssids = this.list_ssids
var internet = this.internet var internet = this.internet
console.log(this.iface_out) if (window.config.iface_out.charAt(0) == "e"){
if (this.iface_out.charAt(0) == "e"){
router.push({ name: 'generate-ap' }); 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,
list_ssids: list_ssids, list_ssids: list_ssids,
internet:internet } }); internet: internet } });
} }
} }
} }
} }
</script> </script>

View File

@ -3,69 +3,69 @@
<div v-if="results"> <div v-if="results">
<div v-if="grep_keyword('STALKERWARE', 'high')" class="high-wrapper"> <div v-if="grep_keyword('STALKERWARE', 'high')" class="high-wrapper">
<div class="center"> <div class="center">
<h1 class="warning-title">Your device is compromised by<br />a Stalkerware, please check the report.</h1> <h1 class="warning-title" v-html="$t('report.stalkerware_msg')"></h1>
<button class="btn btn-report-low-light" v-on:click="new_capture()">Start a new capture</button> <button class="btn btn-report-low-light" v-on:click="new_capture()">{{ $t("report.start_new_capture") }}</button>
<button class="btn btn-report-high" @click="show_report=true;results=false;">Show the full report</button> <button class="btn btn-report-high" @click="show_report=true;results=false;">{{ $t("report.show_full_report") }}</button>
</div> </div>
</div> </div>
<div v-else-if="alerts.high.length >= 1" class="high-wrapper"> <div v-else-if="alerts.high.length >= 1" class="high-wrapper">
<div class="center"> <div class="center">
<h1 class="warning-title">You have {{ nb_translate(alerts.high.length) }} high alert,<br />your device seems to be compromised.</h1> <h1 class="warning-title" v-html="$t('report.high_msg', { nb: $i18n.messages[$i18n.locale].report.numbers[alerts.high.length] })"></h1>
<button class="btn btn-report-low-light" v-on:click="new_capture()">Start a new capture</button> <button class="btn btn-report-low-light" v-on:click="new_capture()">{{ $t("report.start_new_capture") }}</button>
<button class="btn btn-report-high" @click="show_report=true;results=false;">Show the full report</button> <button class="btn btn-report-high" @click="show_report=true;results=false;">{{ $t("report.show_full_report") }}</button>
</div> </div>
</div> </div>
<div v-else-if="grep_keyword('TRACKER', 'moderate')" class="med-wrapper"> <div v-else-if="grep_keyword('TRACKER', 'moderate')" class="med-wrapper">
<div class="center"> <div class="center">
<h1 class="warning-title">An application is sharing your<br /> current geolocation with a third party.</h1> <h1 class="warning-title" v-html="$t('report.location_msg')"></h1>
<button class="btn btn-report-low-light" v-on:click="new_capture()">Start a new capture</button> <button class="btn btn-report-low-light" v-on:click="new_capture()">{{ $t("report.start_new_capture") }}</button>
<button class="btn btn-report-moderate" @click="show_report=true;results=false;">Show the full report</button> <button class="btn btn-report-moderate" @click="show_report=true;results=false;">{{ $t("report.show_full_report") }}</button>
</div> </div>
</div> </div>
<div v-else-if="alerts.moderate.length >= 1" class="med-wrapper"> <div v-else-if="alerts.moderate.length >= 1" class="med-wrapper">
<div class="center"> <div class="center">
<h1 class="warning-title">You have {{ nb_translate(alerts.moderate.length) }} moderate alerts,<br />your device might be compromised.</h1> <h1 class="warning-title" v-html="$t('report.moderate_msg', { nb: $i18n.messages[$i18n.locale].report.numbers[alerts.moderate.length] })"></h1>
<button class="btn btn-report-low-light" v-on:click="new_capture()">Start a new capture</button> <button class="btn btn-report-low-light" v-on:click="new_capture()">{{ $t("report.start_new_capture") }}</button>
<button class="btn btn-report-moderate" @click="show_report=true;results=false;">Show the full report</button> <button class="btn btn-report-moderate" @click="show_report=true;results=false;">{{ $t("report.show_full_report") }}</button>
</div> </div>
</div> </div>
<div v-else-if="alerts.low.length >= 1" class="low-wrapper"> <div v-else-if="alerts.low.length >= 1" class="low-wrapper">
<div class="center"> <div class="center">
<h1 class="warning-title">You have only {{ nb_translate(alerts.moderate.low) }} low alerts,<br /> don't hesitate to check them.</h1> <h1 class="warning-title" v-html="$t('report.low_msg', { nb: $i18n.messages[$i18n.locale].report.numbers[alerts.low.length] })"></h1>
<button class="btn btn-report-low-light" v-on:click="new_capture()">Start a new capture</button> <button class="btn btn-report-low-light" v-on:click="new_capture()">{{ $t("report.start_new_capture") }}</button>
<button class="btn btn-report-low" @click="show_report=true;results=false;">Show the full report</button> <button class="btn btn-report-low" @click="show_report=true;results=false;">{{ $t("report.show_full_report") }}</button>
</div> </div>
</div> </div>
<div v-else class="none-wrapper"> <div v-else class="none-wrapper">
<div class="center"> <div class="center">
<h1 class="warning-title">Everything looks fine, zero alerts.</h1> <h1 class="warning-title">{{ $t("report.fine_msg") }}</h1>
<button class="btn btn-report-low-light" v-on:click="save_capture()">Save the capture</button> <button class="btn btn-report-low-light" v-on:click="save_capture()">{{ $t("report.save_capture") }}</button>
<button class="btn btn-report-low" v-on:click="new_capture()">Start a new capture</button> <button class="btn btn-report-low" v-on:click="new_capture()">{{ $t("report.start_new_capture") }}</button>
</div> </div>
</div> </div>
</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;">Report for {{device.name}}</h3> <h3 style="margin: 0;">{{ $t("report.report_of") }} {{device.name}}</h3>
IP Address: {{device.ip_address}}<br />Mac Address: {{device.mac_address}} {{ $t("report.ip_address") }} {{device.ip_address}}<br />{{ $t("report.mac_address") }} {{device.mac_address}}
</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">
<span class="high-label">High</span><span class="alert-id">{{ alert.id }}</span> <span class="high-label">{{ $t("report.high") }}</span><span class="alert-id">{{ alert.id }}</span>
<div class="alert-body"> <div class="alert-body">
<span class="title">{{ alert.title }}</span> <span class="title">{{ alert.title }}</span>
<p class="description">{{ alert.description }}</p> <p class="description">{{ alert.description }}</p>
</div> </div>
</li> </li>
<li class="alert" v-for="alert in alerts.moderate" :key="alert.message"> <li class="alert" v-for="alert in alerts.moderate" :key="alert.message">
<span class="moderate-label">Moderate</span><span class="alert-id">{{ alert.id }}</span> <span class="moderate-label">{{ $t("report.moderate") }}</span><span class="alert-id">{{ alert.id }}</span>
<div class="alert-body"> <div class="alert-body">
<span class="title">{{ alert.title }}</span> <span class="title">{{ alert.title }}</span>
<p class="description">{{ alert.description }}</p> <p class="description">{{ alert.description }}</p>
</div> </div>
</li> </li>
<li class="alert" v-for="alert in alerts.low" :key="alert.message"> <li class="alert" v-for="alert in alerts.low" :key="alert.message">
<span class="low-label">Low</span><span class="alert-id">{{ alert.id }}</span> <span class="low-label">{{ $t("report.low") }}</span><span class="alert-id">{{ alert.id }}</span>
<div class="alert-body"> <div class="alert-body">
<span class="title">{{ alert.title }}</span> <span class="title">{{ alert.title }}</span>
<p class="description">{{ alert.description }}</p> <p class="description">{{ alert.description }}</p>
@ -74,11 +74,11 @@
</ul> </ul>
<div class="columns" id="controls-analysis"> <div class="columns" id="controls-analysis">
<div class="column col-5"> <div class="column col-5">
<button class="btn width-100" @click="$router.push('generate-ap')">Start a capture</button> <button class="btn width-100" @click="$router.push('generate-ap')">{{ $t("report.start_new_capture") }}</button>
</div> </div>
<div class="divider-vert column col-2" data-content="OR"></div> <div class="divider-vert column col-2" data-content="OR"></div>
<div class="column col-5"> <div class="column col-5">
<button class="btn btn btn-primary width-100" v-on:click="save_capture()">Save the report</button> <button class="btn btn btn-primary width-100" v-on:click="save_capture()">{{ $t("report.save_report") }}</button>
</div> </div>
</div> </div>
</div> </div>
@ -100,6 +100,7 @@ export default {
data() { data() {
return { return {
results: true, results: true,
translation: {}
} }
}, },
props: { props: {
@ -115,15 +116,6 @@ export default {
new_capture: function() { new_capture: function() {
router.push({ name: 'generate-ap' }) router.push({ name: 'generate-ap' })
}, },
nb_translate: function(x) {
var nbs = ['zero','one','two','three','four', 'five','six','seven','eight','nine', 'ten', 'eleven']
try {
return nbs[x];
} catch (error)
{
return x;
}
},
grep_keyword: function(kw, level){ grep_keyword: function(kw, level){
try { try {
if(this.alerts[level].length){ if(this.alerts[level].length){

View File

@ -6,14 +6,14 @@
<div class="icon-usb"></div> <div class="icon-usb"></div>
<div class="icon-usb-plug"></div> <div class="icon-usb-plug"></div>
</div> </div>
<p class="legend" v-if="!saved && !usb"><br />Please connect a USB key to save your capture.</p> <p class="legend" v-if="!saved && !usb"><br />{{ $t("save-capture.please_connect") }}</p>
<p class="legend" v-if="!saved && usb"><br />We are saving your capture.</p> <p class="legend" v-if="!saved && usb"><br />{{ $t("save-capture.we_are_saving") }}</p>
<p class="legend" v-if="saved"><br />You can tap the USB key to start a new capture.</p> <p class="legend" v-if="saved"><br />{{ $t("save-capture.tap_msg") }}</p>
</div> </div>
<div class="center" v-else-if="!save_usb && init"> <div class="center" v-else-if="!save_usb && init">
<div> <div>
<p class="legend">The capture download is going to start...<br /><br /><br /></p> <p class="legend">{{ $t("save-capture.capture_download") }}<br /><br /><br /></p>
<button class="btn btn-primary" v-on:click="new_capture()">Start another capture</button> <button class="btn btn-primary" v-on:click="new_capture()">{{ $t("save-capture.start_capture_btn") }}</button>
<iframe :src="download_url" class="frame-download"></iframe> <iframe :src="download_url" class="frame-download"></iframe>
</div> </div>
</div> </div>
@ -144,7 +144,8 @@ export default {
usb: false, usb: false,
saved: false, saved: false,
save_usb: false, save_usb: false,
init: false init: false,
translation: {}
} }
}, },
props: { props: {
@ -174,27 +175,18 @@ export default {
new_capture: function() { new_capture: function() {
clearTimeout(this.timeout); clearTimeout(this.timeout);
router.push({ name: 'generate-ap' }) router.push({ name: 'generate-ap' })
},
load_config: function() {
axios.get(`/api/misc/config`, { timeout: 60000 })
.then(response => {
if(response.data.download_links){
this.init = true
this.save_usb = false
this.download_url = `/api/save/save-capture/${this.capture_token}/url`
} else {
this.init = true
this.save_usb = true
this.interval = setInterval(() => { this.check_usb() }, 500);
}
})
.catch(error => {
console.log(error)
});
} }
}, },
created: function() { created: function() {
this.load_config() if(window.config.download_links){
this.init = true
this.save_usb = false
this.download_url = `/api/save/save-capture/${this.capture_token}/url`
} else {
this.init = true
this.save_usb = true
this.interval = setInterval(() => { this.check_usb() }, 500);
}
} }
} }
</script> </script>

View File

@ -15,58 +15,34 @@
data() { data() {
return { return {
list_ssids: [], list_ssids: [],
internet: false, internet: false
iface_out:""
} }
}, },
methods: { methods: {
// Check if the device is connected to internet.
internet_check: function() { internet_check: function() {
axios.get(`/api/network/status`, { timeout: 10000 }) axios.get('/api/network/status', { timeout: 10000 })
.then(response => { .then(response => {
if (response.data.internet){ if (response.data.internet) this.internet = true
this.internet = true if (window.config.iface_out.charAt(0) == 'e') {
setTimeout(function () { this.goto_home(); }.bind(this), 1000);
} else {
this.get_wifi_networks();
} }
this.load_config()
}) })
.catch(err => (console.log(err))) .catch(err => (console.log(err)))
}, },
// Get the WiFi networks around the box.
get_wifi_networks: function() { get_wifi_networks: function() {
axios.get(`/api/network/wifi/list`, { timeout: 10000 }) axios.get('/api/network/wifi/list', { timeout: 10000 })
.then(response => { .then(response => {
this.list_ssids = response.data.networks this.list_ssids = response.data.networks
this.goto_home(); this.goto_home();
}) })
.catch(err => (console.log(err))) .catch(err => (console.log(err)))
}, },
// Forward the view to home, with some props
// such as (SSIDs, internet & interface)
goto_home: function() { goto_home: function() {
var list_ssids = this.list_ssids var list_ssids = this.list_ssids
var internet = this.internet var internet = this.internet
var iface_out = this.iface_out router.replace({ name: 'home', params: { list_ssids: list_ssids, internet: internet } });
router.replace({ name: 'home', params: { list_ssids: list_ssids, internet: internet, iface_out : iface_out } });
},
// Get the network_out from the config
// to determine the next steps.
load_config: function() {
axios.get(`/api/misc/config`, { timeout: 60000 })
.then(response => {
if(response.data.iface_out){
this.iface_out = response.data.iface_out
// If ethernet, just goto the homepage.
// Else, get wifi networks and then go to home.
if(this.iface_out.charAt(0) == "e"){
setTimeout(function () { this.goto_home(); }.bind(this), 1000);
} else {
this.get_wifi_networks();
}
}
})
.catch(error => {
console.log(error)
});
} }
}, },
created: function() { created: function() {

View File

@ -2,41 +2,42 @@
<div :class="[ keyboard == false ? 'center' : '' ]"> <div :class="[ keyboard == false ? 'center' : '' ]">
<div v-if="keyboard == false"> <div v-if="keyboard == false">
<div v-if="have_internet"> <div v-if="have_internet">
<p>You seem to be already connected to a network.<br />Do you want to use the current connection?</p> <p v-html="$t('wifi-select.already_connected_question')"></p>
<div class="empty-action"> <div class="empty-action">
<button class="btn" @click="have_internet = false">No, use another</button> <button class="btn" :class="[ connecting ? 'loading' : '', success ? 'btn-success' : 'btn-primary', ]" @click="$router.push({ name: 'generate-ap' })">Yes, use it.</button> <button class="btn" @click="have_internet = false">{{ $t("wifi-select.no_btn") }}</button> &nbsp;
<button class="btn" :class="[ connecting ? 'loading' : '', success ? 'btn-success' : 'btn-primary', ]" @click="$router.push({ name: 'generate-ap' })">{{ $t("wifi-select.yes_btn") }}</button>
</div> </div>
</div> </div>
<div v-else> <div v-else>
<div v-if="enter_creds" class="wifi-login"> <div v-if="enter_creds" class="wifi-login">
<div class="form-group"> <div class="form-group">
<select class="form-select" id="ssid-select" v-model="ssid"> <select class="form-select" id="ssid-select" v-model="ssid">
<option value="" selected>Wifi name</option> <option value="" selected>{{ $t("wifi-select.wifi_name") }}</option>
<option v-for="ssid in ssids" v-bind:key="ssid.ssid"> <option v-for="ssid in ssids" v-bind:key="ssid.ssid">
{{ ssid.ssid }} {{ ssid.ssid }}
</option> </option>
</select> </select>
</div> </div>
<div class="form-group"> <div class="form-group">
<input class="form-input" type="password" id="password" v-model="password" placeholder="Wifi password" v-on:click="keyboard = (virtual_keyboard)? true : false"> <input class="form-input" type="password" id="password" v-model="password" :placeholder="$t('wifi-select.wifi_password')" v-on:click="keyboard = (virtual_keyboard)? true : false">
</div> </div>
<div class="form-group"> <div class="form-group">
<button class="btn width-100" :class="[ connecting ? 'loading' : '', success ? 'btn-success' : 'btn-primary', ]" v-on:click="wifi_setup()">{{ btnval }}</button> <button class="btn width-100" :class="[ connecting ? 'loading' : '', success ? 'btn-success' : 'btn-primary', ]" v-on:click="wifi_setup()">{{ btnval }}</button>
</div> </div>
<div class="form-group"> <div class="form-group">
<button class="btn width-100" :class="[ refreshing ? 'loading' : '' ]" v-on:click="refresh_wifi_list()">Refresh networks list</button> <button class="btn width-100" :class="[ refreshing ? 'loading' : '' ]" v-on:click="refresh_wifi_list()">{{ $t("wifi-select.refresh_btn") }}</button>
</div> </div>
</div> </div>
<div v-else> <div v-else>
<p><strong>You seem to not be connected to Internet.</strong><br />Please configure the Wi-Fi connection.</p> <p><strong>{{ $t("wifi-select.not_connected") }}</strong><br />{{ $t("wifi-select.please_config") }}</p>
<div class="empty-action"> <div class="empty-action">
<button class="btn btn-primary" @click="enter_creds = true">Ok, let's do that.</button> <button class="btn btn-primary" @click="enter_creds = true">{{ $t("wifi-select.lets_do_btn") }}</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div v-else> <div v-else>
<input :value="input" class="keyboardinput" @input="onInputChange" placeholder="Tap on the virtual keyboard to start"> <input :value="input" class="keyboardinput" @input="onInputChange" :placeholder="$t('wifi-select.tap_keyboard')">
<SimpleKeyboard @onChange="onChange" @onKeyPress="onKeyPress" :input="input" /> <SimpleKeyboard @onChange="onChange" @onKeyPress="onKeyPress" :input="input" />
</div> </div>
</div> </div>
@ -63,17 +64,18 @@ export default {
connecting: false, connecting: false,
error: false, error: false,
success: false, success: false,
btnval: "Connect to it.", btnval: this.$t("wifi-select.connect_to_it"),
ssid: "", ssid: "",
selected_ssid: false, selected_ssid: false,
password: "", password: "",
keyboard: false, keyboard: false,
input: "", input: "",
ssids: [], ssids: [],
virtual_keyboard: window.config.virtual_keyboard,
have_internet: false, have_internet: false,
enter_creds: false, enter_creds: false,
virtual_keyboard: false, refreshing: false,
refreshing: false translation: {}
} }
}, },
props: { props: {
@ -83,15 +85,15 @@ export default {
}, },
methods: { methods: {
wifi_connect: function() { wifi_connect: function() {
axios.get(`/api/network/wifi/connect`, { timeout: 60000 }) axios.get('/api/network/wifi/connect', { timeout: 60000 })
.then(response => { .then(response => {
if (response.data.status) { if (response.data.status) {
this.success = true this.success = true
this.connecting = false this.connecting = false
this.btnval = "Wifi connected!" this.btnval = this.$t('wifi-select.wifi_connected')
setTimeout(() => router.push('generate-ap'), 1000); setTimeout(() => router.push('generate-ap'), 1000);
} else { } else {
this.btnval = "Wifi not connected. Please retry." this.btnval = this.$t('wifi-select.wifi_not_connected')
this.connecting = false this.connecting = false
} }
}) })
@ -101,7 +103,7 @@ export default {
}, },
wifi_setup: function() { wifi_setup: function() {
if (this.ssid.length && this.password.length >= 8 ){ if (this.ssid.length && this.password.length >= 8 ){
axios.post(`/api/network/wifi/setup`, { ssid: this.ssid, password: this.password }, { timeout: 60000 }) axios.post('/api/network/wifi/setup', { ssid: this.ssid, password: this.password }, { timeout: 60000 })
.then(response => { .then(response => {
if(response.data.status) { if(response.data.status) {
this.connecting = true this.connecting = true
@ -115,15 +117,6 @@ export default {
}); });
} }
}, },
load_config: function() {
axios.get(`/api/misc/config`, { timeout: 60000 })
.then(response => {
this.virtual_keyboard = response.data.virtual_keyboard
})
.catch(error => {
console.log(error)
});
},
onChange(input) { onChange(input) {
this.input = input this.input = input
this.password = this.input; this.password = this.input;
@ -140,23 +133,22 @@ export default {
}, },
refresh_wifi_list: function(){ refresh_wifi_list: function(){
this.refreshing = true this.refreshing = true
axios.get(`/api/network/wifi/list`, { timeout: 10000 }) axios.get('/api/network/wifi/list', { timeout: 10000 })
.then(response => { .then(response => {
this.refreshing = false this.refreshing = false
this.append_ssids(response.data.networks) this.append_ssids(response.data.networks)
}).catch(error => { }).catch(error => {
this.refreshing = false this.refreshing = false
console.log(error) console.log(error)
}); });
} }
}, },
created: function() { created: function() {
this.load_config()
this.have_internet = (this.internet) ? true : false this.have_internet = (this.internet) ? true : false
this.keyboard = false this.keyboard = false
if (typeof this.list_ssids == "object" && this.list_ssids.length != 0){ if (typeof this.list_ssids == 'object' && this.list_ssids.length != 0){
this.ssids = this.list_ssids this.ssids = this.list_ssids
} else { } else {
this.refresh_wifi_list() this.refresh_wifi_list()

View File

@ -48,6 +48,7 @@ frontend:
remote_access: true remote_access: true
sparklines: true sparklines: true
virtual_keyboard: true virtual_keyboard: true
user_lang: userlang
# NETWORK - # NETWORK -
# Some elements related to the network configuration, such as # Some elements related to the network configuration, such as

View File

@ -6,6 +6,7 @@ HOST="$( hostname )"
IFACES="$( ifconfig -a | grep -Eo '[a-z0-9]{4,14}\: ' | grep -oE [a-z0-9]+ )" IFACES="$( ifconfig -a | grep -Eo '[a-z0-9]{4,14}\: ' | grep -oE [a-z0-9]+ )"
IFACE_OUT="" IFACE_OUT=""
IFACE_IN="" IFACE_IN=""
LOCALES=(en fr)
welcome_screen() { welcome_screen() {
cat << "EOF" cat << "EOF"
@ -40,6 +41,22 @@ check_operating_system() {
fi fi
} }
set_userlang() {
# Set the user language.
echo -e "\e[39m[+] Setting the user language...\e[39m"
printf -v joined '%s/' "${LOCALES[@]}"
echo -n " Please choose a language for the reports and the interface (${joined%/}): "
read lang
if [[ " ${LOCALES[@]} " =~ " ${lang} " ]]; then
sed -i "s/userlang/${lang}/g" /usr/share/tinycheck/config.yaml
echo -e "\e[92m [✔] User language setted!\e[39m"
else
echo -e "\e[91m [✘] You must choose between the languages proposed, let's retry.\e[39m"
set_userlang
fi
}
set_credentials() { set_credentials() {
# Set the credentials to access to the backend. # Set the credentials to access to the backend.
echo -e "\e[39m[+] Setting the backend credentials...\e[39m" echo -e "\e[39m[+] Setting the backend credentials...\e[39m"
@ -410,6 +427,7 @@ else
check_operating_system check_operating_system
check_interfaces check_interfaces
create_directory create_directory
set_userlang
set_credentials set_credentials
check_dependencies check_dependencies
configure_dnsmask configure_dnsmask

View File

@ -4,6 +4,9 @@
import subprocess as sp import subprocess as sp
from flask import Blueprint, jsonify from flask import Blueprint, jsonify
from app.utils import read_config from app.utils import read_config
import re
import sys
import os
misc_bp = Blueprint("misc", __name__) misc_bp = Blueprint("misc", __name__)
@ -57,5 +60,6 @@ def get_config():
"quit_option": read_config(("frontend", "quit_option")), "quit_option": read_config(("frontend", "quit_option")),
"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"))
}) })