First commit

This commit is contained in:
Félix Aime
2020-11-24 19:45:03 +01:00
parent c042b71634
commit 513f6b1b02
130 changed files with 76873 additions and 0 deletions

View File

@ -0,0 +1,15 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from sqlalchemy import create_engine, MetaData, Table
from sqlalchemy.orm import scoped_session, mapper
from sqlalchemy.orm.session import sessionmaker
import sys
parent = "/".join(sys.path[0].split("/")[:-2])
engine = create_engine('sqlite:////{}/tinycheck.sqlite3'.format(parent), convert_unicode=True)
metadata = MetaData(bind=engine)
session = scoped_session(sessionmaker(autocommit=False, autoflush=False, bind=engine))
class Model(object):
query = session.query_property()

View File

@ -0,0 +1,88 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from flask import Blueprint, request, jsonify, send_file
from app.decorators import *
from app.classes.config import Config
import sys
config_bp = Blueprint("config", __name__)
config = Config()
@config_bp.route('/switch/<cat>/<key>', methods=['GET'])
@require_header_token
def switch(cat, key):
"""
Switch the Boolean value of a configuration key.
:return: status in JSON
"""
try:
value = config.read_config((cat, key))
if value:
config.write_config(cat, key, False)
res = { "status" : True,
"message" : "Key switched to false" }
else:
config.write_config(cat, key, True)
res = { "status" : True,
"message" : "Key switched to true" }
except:
res = { "status" : True,
"message" : "Issue while changing value" }
return jsonify(res)
@config_bp.route('/edit/<cat>/<key>/<path:value>', methods=['GET'])
@require_header_token
def edit(cat, key, value):
"""
Edit the string (or array) value of a configuration key.
:return: status in JSON
"""
value = value.split("|") if "|" in value else value
if config.write_config(cat, key, value):
res = { "status" : True,
"message" : "Configuration updated" }
else:
res = { "status" : False,
"message" : "Can't edit this configuration key" }
return jsonify(res)
@config_bp.route('/db/export', methods=['GET'])
@require_get_token
def export_db():
"""
Export the database.
:return: current database as attachment
"""
return config.export_db()
@config_bp.route('/db/import', methods=['POST'])
@require_header_token
def import_db():
"""
Import a database and replace the existant.
:return: status in JSON
"""
try:
f = request.files["file"]
assert f.read(15) == b"SQLite format 3"
d = "/".join(sys.path[0].split("/")[:-2])
f.save("/{}/tinycheck.sqlite3".format(d))
res = { "status" : True,
"message" : "Database updated" }
except:
res = { "status" : False,
"message" : "Error while database upload" }
return jsonify(res)
@config_bp.route('/list', methods=['GET'])
def list():
"""
List key, values of the configuration
:return: configuration in JSON
"""
res = config.export_config()
res["backend"]["password"] = ""
return jsonify(res)

View File

@ -0,0 +1,80 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from flask import Blueprint, jsonify, Response
from app.decorators import require_header_token, require_get_token
from app.classes.iocs import IOCs
import json
ioc_bp = Blueprint("ioc", __name__)
ioc = IOCs()
@ioc_bp.route('/add/<ioc_type>/<ioc_tag>/<ioc_tlp>/<path:ioc_value>', methods=['GET'])
@require_header_token
def add(ioc_type, ioc_tag, ioc_tlp, ioc_value):
"""
Parse and add an IOC to the database.
:return: status of the operation in JSON
"""
source = "backend"
res = IOCs.add(ioc_type, ioc_tag, ioc_tlp, ioc_value, source)
return jsonify(res)
@ioc_bp.route('/delete/<ioc_id>', methods=['GET'])
@require_header_token
def delete(ioc_id):
"""
Delete an IOC by its id to the database.
:return: status of the operation in JSON
"""
res = IOCs.delete(ioc_id)
return jsonify(res)
@ioc_bp.route('/search/<term>', methods=['GET'])
@require_header_token
def search(term):
"""
Search IOCs in the database.
:return: potential results in JSON.
"""
res = IOCs.search(term)
return jsonify({"results": [i for i in res]})
@ioc_bp.route('/get/types')
@require_header_token
def get_types():
"""
Retreive a list of IOCs types.
:return: list of types in JSON.
"""
res = IOCs.get_types()
return jsonify({"types": [t for t in res]})
@ioc_bp.route('/get/tags')
@require_header_token
def get_tags():
"""
Retreive a list of IOCs tags.
:return: list of types in JSON.
"""
res = IOCs.get_tags()
return jsonify({"tags": [t for t in res]})
@ioc_bp.route('/export')
@require_get_token
def get_all():
"""
Retreive a list of all IOCs.
:return: list of iocs in JSON.
"""
res = IOCs.get_all()
return Response(json.dumps({"iocs": [i for i in res]}),
mimetype='application/json',
headers={'Content-Disposition': 'attachment;filename=iocs-export.json'})

View File

@ -0,0 +1,68 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from flask import Blueprint, jsonify, Response
from app.decorators import require_header_token, require_get_token
from app.classes.whitelist import WhiteList
import json
whitelist_bp = Blueprint("whitelist", __name__)
whitelist = WhiteList()
@whitelist_bp.route('/add/<elem_type>/<path:elem_value>', methods=['GET'])
@require_header_token
def add(elem_type, elem_value):
"""
Parse and add an element to be whitelisted.
:return: status of the operation in JSON
"""
source = "backend"
res = whitelist.add(elem_type, elem_value, source)
return jsonify(res)
@whitelist_bp.route('/delete/<elem_id>', methods=['GET'])
@require_header_token
def delete(elem_id):
"""
Delete an element by its id to the database.
:return: status of the operation in JSON
"""
res = whitelist.delete(elem_id)
return jsonify(res)
@whitelist_bp.route('/search/<element>', methods=['GET'])
@require_header_token
def search(element):
"""
Search elements in the database.
:return: potential results in JSON.
"""
res = whitelist.search(element)
return jsonify({"results": [e for e in res]})
@whitelist_bp.route('/get/types')
@require_header_token
def get_types():
"""
Retrieve a list of whitelisted elements types.
:return: list of types in JSON.
"""
res = whitelist.get_types()
return jsonify({"types": [t for t in res]})
@whitelist_bp.route('/export')
@require_get_token
def get_all():
"""
Retreive a list of all elements.
:return: list of elements in JSON.
"""
res = whitelist.get_all()
return Response(json.dumps({"elements": [e for e in res]}),
mimetype='application/json',
headers={'Content-Disposition': 'attachment;filename=whitelist-export.json'})

View File

@ -0,0 +1,107 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import yaml
import sys
import io
import os
import re
import hashlib
from functools import reduce
from flask import send_file
class Config(object):
def __init__(self):
self.dir = "/".join(sys.path[0].split("/")[:-2])
return None
def read_config(self, path):
"""
Read a single value from the configuration
:return: value (it can be any type)
"""
config = yaml.load(
open(os.path.join(self.dir, "config.yaml"), "r"), Loader=yaml.SafeLoader)
return reduce(dict.get, path, config)
def export_config(self):
"""
Export the configuration
:return: dict (configuration content)
"""
config = yaml.load(
open(os.path.join(self.dir, "config.yaml"), "r"), Loader=yaml.SafeLoader)
config["interfaces"] = self.get_wireless_interfaces()
return config
def write_config(self, cat, key, value):
"""
Write a new value in the configuration
:return: bool, operation status
"""
config = yaml.load(
open(os.path.join(self.dir, "config.yaml"), "r"), Loader=yaml.SafeLoader)
config[cat][key] = value if key != "password" else self.make_password(
value)
if cat == "network" and key == "in":
self.edit_configuration_files(value)
with open(os.path.join(self.dir, "config.yaml"), "w") as yaml_file:
yaml_file.write(yaml.dump(config, default_flow_style=False))
return True
def make_password(self, clear_text):
"""
Make a simple password hash (without salt)
"""
return hashlib.sha256(clear_text.encode()).hexdigest()
def export_db(self):
"""
Export the database.
:return: send_file (the database)
"""
with open(os.path.join(self.dir, "tinycheck.sqlite3"), "rb") as f:
return send_file(
io.BytesIO(f.read()),
mimetype="application/octet-stream",
as_attachment=True,
attachment_filename='tinycheck-export-db.sqlite')
def get_wireless_interfaces(self):
"""
List the Wireless interfaces installed on the box
:return: list of the interfaces
"""
try:
return [i for i in os.listdir("/sys/class/net/") if i.startswith("wlan")]
except:
return ["Fake iface1", "Fake iface 2"]
def edit_configuration_files(self, iface):
"""
Edit the DNSMasq and DHCPCD configuration files
:return: nothing.
"""
try:
if re.match("^wlan[0-9]{1}$", iface):
# Edit of DHCPD.conf
with open("/etc/dhcpcd.conf", 'r') as file:
content = file.readlines()
for i, line in enumerate(content):
if line.startswith("interface"):
content[i] = "interface {}\n".format(iface)
with open("/etc/dhcpcd.conf", 'w') as file:
file.writelines(content)
# Edit of DNSMASQ.conf
with open("/etc/dnsmasq.conf", 'r') as file:
content = file.readlines()
for i, line in enumerate(content):
if line.startswith("interface"):
content[i] = "interface={}\n".format(iface)
with open("/etc/dnsmasq.conf", 'w') as file:
file.writelines(content)
except:
pass

View File

@ -0,0 +1,133 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from app import db
from app.db.models import Ioc
from sqlalchemy.sql import exists
from app.definitions import definitions
from flask import escape
import re
import time
class IOCs(object):
def __init__(self):
return None
@staticmethod
def add(ioc_type, ioc_tag, ioc_tlp, ioc_value, source):
"""
Parse and add an IOC to the database.
:return: status of the operation in JSON
"""
ioc_value = ioc_value.lower() if ioc_type != "snort" else ioc_value
ioc_valid = False
if db.session.query(exists().where(Ioc.value == ioc_value)).scalar():
return {"status": False,
"message": "IOC already exists",
"ioc": escape(ioc_value)}
elif ioc_tlp in ["white", "green", "amber", "red"]:
if ioc_type == "unknown":
for t in definitions["iocs_types"]:
if t["regex"] and t["auto"]:
if re.match(t["regex"], ioc_value):
ioc_type = t["type"]
ioc_valid = True
elif ioc_type in [t["type"] for t in definitions["iocs_types"]]:
for t in definitions["iocs_types"]:
if t["type"] == ioc_type and t["regex"]:
if re.match(t["regex"], ioc_value):
ioc_valid = True
break
elif t["type"] == "snort" and ioc_value[0:6] == "alert ":
ioc_valid = True
break
else:
return {"status": True,
"message": "Wrong IOC type",
"ioc": escape(ioc_value),
"type": escape(ioc_type)}
if ioc_valid:
added_on = int(time.time())
db.session.add(Ioc(ioc_value, ioc_type, ioc_tlp,
ioc_tag, source, added_on))
db.session.commit()
return {"status": True,
"message": "IOC added",
"ioc": escape(ioc_value)}
else:
return {"status": False,
"message": "Wrong IOC format",
"ioc": escape(ioc_value)}
else:
return {"status": False,
"message": "Wrong IOC TLP",
"ioc": escape(ioc_value),
"type": escape(ioc_tlp)}
@staticmethod
def delete(ioc_id):
"""
Delete an IOC by its id to the database.
:return: status of the operation in JSON
"""
if db.session.query(exists().where(Ioc.id == ioc_id)).scalar():
db.session.query(Ioc).filter_by(id=ioc_id).delete()
db.session.commit()
return {"status": True,
"message": "IOC deleted"}
else:
return {"status": False,
"message": "IOC not found"}
@staticmethod
def search(term):
"""
Search IOCs in the database.
:return: generator of results.
"""
iocs = db.session.query(Ioc).filter(
Ioc.value.like(term.replace("*", "%"))).all()
for ioc in iocs:
ioc = ioc.__dict__
yield {"id": ioc["id"],
"type": ioc["type"],
"tag": ioc["tag"],
"tlp": ioc["tlp"],
"value": ioc["value"]}
@staticmethod
def get_types():
"""
Retreive a list of IOCs types.
:return: generator of iocs types.
"""
for t in definitions["iocs_types"]:
yield {"type": t["type"],
"name": t["name"]}
@staticmethod
def get_tags():
"""
Retreive a list of IOCs tags.
:return: generator of iocs tags.
"""
for t in definitions["iocs_tags"]:
yield {"tag": t["tag"],
"name": t["name"]}
@staticmethod
def get_all():
"""
Get all IOCs from the database
:return: generator of the records.
"""
for ioc in db.session.query(Ioc).all():
ioc = ioc.__dict__
yield {"id": ioc["id"],
"type": ioc["type"],
"tag": ioc["tag"],
"tlp": ioc["tlp"],
"value": ioc["value"]}

View File

@ -0,0 +1,102 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from app import db
from app.db.models import Whitelist
from sqlalchemy.sql import exists
from app.definitions import definitions
from flask import escape
import re
import time
class WhiteList(object):
def __init__(self):
return None
@staticmethod
def add(elem_type, elem_value, source):
"""
Parse and add an element to be whitelisted.
:return: status of the operation in a dict
"""
elem_value = elem_value.lower()
elem_valid = False
if db.session.query(exists().where(Whitelist.element == elem_value)).scalar():
return {"status": False,
"message": "Element already whitelisted",
"element": escape(elem_value)}
elif elem_type == "unknown":
for t in definitions["whitelist_types"]:
if t["regex"] and t["auto"]:
if re.match(t["regex"], elem_value):
elem_type = t["type"]
elem_valid = True
break
elif elem_type in [t["type"] for t in definitions["whitelist_types"]]:
for t in definitions["whitelist_types"]:
if t["type"] == elem_type and t["regex"]:
if re.match(t["regex"], elem_value):
elem_valid = True
break
if elem_valid:
added_on = int(time.time())
db.session.add(Whitelist(elem_value, elem_type, source, added_on))
db.session.commit()
return {"status": True,
"message": "Element whitelisted",
"element": escape(elem_value)}
else:
return {"status": False,
"message": "Wrong element format",
"element": escape(elem_value)}
@staticmethod
def delete(elem_id):
"""
Delete an element by its id to the database.
:return: status of the operation in a dict
"""
if db.session.query(exists().where(Whitelist.id == elem_id)).scalar():
db.session.query(Whitelist).filter_by(id=elem_id).delete()
db.session.commit()
return {"status": True,
"message": "Element deleted"}
else:
return {"status": False,
"message": "Element not found"}
@staticmethod
def search(element):
"""
Search elements in the database.
:return: generator containing elements.
"""
elems = db.session.query(Whitelist).filter(
Whitelist.element.like(element.replace("*", "%"))).all()
for elem in elems:
elem = elem.__dict__
yield {"id": elem["id"],
"type": elem["type"],
"element": elem["element"]}
@staticmethod
def get_types():
"""
Get types of whitelisted elements.
:return: generator containing types.
"""
for t in definitions["whitelist_types"]:
yield {"type": t["type"], "name": t["name"]}
@staticmethod
def get_all():
"""
Retrieve all whitelisted elements.
:return: generator containing elements.
"""
for elem in db.session.query(Whitelist).all():
elem = elem.__dict__
yield {"type": elem["type"],
"element": elem["element"]}

View File

@ -0,0 +1,18 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from sqlalchemy import create_engine, MetaData, Table
from sqlalchemy.orm import scoped_session, mapper
from sqlalchemy.orm.session import sessionmaker
import sys
parent = "/".join(sys.path[0].split("/")[:-2])
engine = create_engine(
'sqlite:////{}/tinycheck.sqlite3'.format(parent), convert_unicode=True)
metadata = MetaData(bind=engine)
session = scoped_session(sessionmaker(
autocommit=False, autoflush=False, bind=engine))
class Model(object):
query = session.query_property()

View File

@ -0,0 +1,20 @@
from app import db
class Ioc(db.Model):
def __init__(self, value, type, tlp, tag, source, added_on):
self.value = value
self.type = type
self.tlp = tlp
self.tag = tag
self.source = source
self.added_on = added_on
class Whitelist(db.Model):
def __init__(self, element, type, source, added_on):
self.element = element
self.type = type
self.source = source
self.added_on = added_on
db.mapper(Whitelist, db.Table('whitelist', db.metadata, autoload=True))
db.mapper(Ioc, db.Table('iocs', db.metadata, autoload=True))

View File

@ -0,0 +1,65 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from flask import request, jsonify
from flask import current_app as app
from flask_httpauth import HTTPBasicAuth
from werkzeug.security import generate_password_hash, check_password_hash
from functools import wraps
from app.utils import read_config
import jwt
import hashlib
auth = HTTPBasicAuth()
@auth.verify_password
def check_creds(user, password):
"""
Check the credentials
:return: :bool: if the authentication succeed.
"""
if user == read_config(("backend", "login")) and check_password(password):
return True
def check_password(password):
"""
Password hashes comparison (submitted and the config one)
:return: True if there is a match between the two hases
"""
if read_config(("backend", "password")) == hashlib.sha256(password.encode()).hexdigest():
return True
def require_header_token(f):
"""
Check the JWT token validity in POST requests.
:return: decorated method
"""
@wraps(f)
def decorated(*args, **kwargs):
try:
token = request.headers['X-Token']
jwt.decode(token, app.config["SECRET_KEY"])
return f(*args, **kwargs)
except:
return jsonify({"message": "JWT verification failed"})
return decorated
def require_get_token(f):
"""
Check the JWT token validity in GET requests.
:return: decorated method
"""
@wraps(f)
def decorated(*args, **kwargs):
try:
token = request.args.get("token")
jwt.decode(token, app.config["SECRET_KEY"])
return f(*args, **kwargs)
except:
return jsonify({"message": "JWT verification failed"})
return decorated

View File

@ -0,0 +1,115 @@
definitions = {
"iocs_types" : [
{
"type" : "ip4addr",
"regex" : r"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$",
"name" : "IPv4 Address",
"auto" : True
},
{
"type" : "ip6addr",
"regex" : r"^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$",
"name" : "IPv6 Address",
"auto" : True
},
{
"type" : "cidr",
"regex" : r"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\/([0-9]|[1-2][0-9]|3[0-2]))?$",
"name" : "Network range",
"auto" : True
},
{
"type" : "domain",
"regex" : r"^((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}$",
"name" : "Domain name",
"auto" : True
},
{
"type" : "sha1cert",
"regex" : r"^[0-9a-f]{40}$",
"name" : "Certificate SHA1",
"auto" : True
},
{
"type" : "snort",
"regex" : False,
"name" : "Snort rule",
"auto" : False
},
{
"type" : "ns",
"regex" : r"^((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}$",
"name" : "Name Server",
"auto" : False
},
{
"type" : "freedns",
"regex" : r"^((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}$",
"name" : "Free DNS",
"auto" : False
},
{
"type" : "tld",
"regex" : r"^\.[a-z]{2,63}$",
"name" : "Suspect TLD",
"auto" : False
}
],
"iocs_tags" : [
{
"tag" : "apt",
"name" : "APT"
},
{
"tag" : "stalkerware",
"name" : "Stalkerware"
},
{
"tag" : "suspect",
"name" : "Suspect"
},
{
"tag" : "malicious",
"name" : "Malicious"
},
{
"tag" : "tracker",
"name" : "Tracker"
},
{
"tag" : "spyware",
"name" : "Spyware"
},
{
"tag" : "cybercrime",
"name" : "Cybercrime"
}
],
"whitelist_types" : [
{
"type" : "ip4addr",
"regex" : r"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$",
"name" : "IPv4 Address",
"auto" : True
},
{
"type" : "ip6addr",
"regex" : r"^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$",
"name" : "IPv6 Address",
"auto" : True
},
{
"type" : "cidr",
"regex" : r"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\/([0-9]|[1-2][0-9]|3[0-2]))?$",
"name" : "Network range",
"auto" : True
},
{
"type" : "domain",
"regex" : r"^((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}$",
"name" : "Domain name",
"auto" : True
}
]
}

View File

@ -0,0 +1,35 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import yaml
import sys
import os
from functools import reduce
def read_config(path):
"""
Read a value from the configuration
:return: value (it can be any type)
"""
dir = "/".join(sys.path[0].split("/")[:-2])
config = yaml.load(open(os.path.join(dir, "config.yaml"), "r"),
Loader=yaml.SafeLoader)
return reduce(dict.get, path, config)
def write_config(cat, key, value):
"""
Write a new value in the configuration
:return: bool, operation status
"""
try:
dir = "/".join(sys.path[0].split("/")[:-2])
config = yaml.load(open(os.path.join(dir, "config.yaml"),
"r"), Loader=yaml.SafeLoader)
config[cat][key] = value
with open(os.path.join(dir, "config.yaml"), "w") as yaml_file:
yaml_file.write(yaml.dump(config, default_flow_style=False))
return True
except:
return False

68
server/backend/main.py Normal file
View File

@ -0,0 +1,68 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from flask import Flask, render_template, send_from_directory, jsonify, redirect
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
import datetime
import secrets
import jwt
from OpenSSL import SSL
from app.utils import read_config
from sys import path
app = Flask(__name__, template_folder="../../app/backend/dist")
app.config["SECRET_KEY"] = secrets.token_bytes(32)
@app.route("/", methods=["GET"])
@auth.login_required
def main():
"""
Return the index.html generated by Vue
"""
return render_template("index.html")
@app.route("/api/get-token", methods=["GET"])
@auth.login_required
def get_token():
"""
Return the JWT token for API requests.
"""
token = jwt.encode({"exp": datetime.datetime.now() +
datetime.timedelta(hours=24)}, app.config["SECRET_KEY"])
return jsonify({"token": token.decode("utf8")})
@app.route("/<p>/<path:path>", methods=["GET"])
@auth.login_required
def get_file(p, path):
"""
Return the backend assets (css, js files, fonts etc.)
"""
rp = "../../app/backend/dist/{}".format(p)
return send_from_directory(rp, path) if p in ["css", "fonts", "js", "img"] else redirect("/")
@app.errorhandler(404)
def page_not_found(e):
return redirect("/")
# API Blueprints.
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')
if __name__ == '__main__':
ssl_cert = "{}/{}".format(path[0], 'cert.pem')
ssl_key = "{}/{}".format(path[0], 'key.pem')
if read_config(("backend", "remote_access")):
app.run(host="0.0.0.0", debug=True, port=443,
ssl_context=(ssl_cert, ssl_key))
else:
app.run(port=443, debug=True, ssl_context=(ssl_cert, ssl_key))

109
server/backend/watchers.py Normal file
View File

@ -0,0 +1,109 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from app.utils import read_config
from app.classes.iocs import IOCs
from app.classes.whitelist import WhiteList
import requests
import json
import urllib3
import time
from multiprocessing import Process
"""
This file is parsing the watchers present
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)
def watch_iocs():
"""
Retrieve IOCs from the remote URLs defined in config/watchers.
For each (new ?) IOC, add it to the DB.
"""
# Retrieve the URLs from the configuration
urls = read_config(("watchers", "iocs"))
watchers = [{"url": url, "status": False} for url in urls]
while True:
for w in watchers:
if w["status"] == False:
iocs = IOCs()
iocs_list = []
try:
res = requests.get(w["url"], verify=False)
if res.status_code == 200:
iocs_list = json.loads(res.content)["iocs"]
else:
w["status"] = False
except:
w["status"] = False
for ioc in iocs_list:
try:
iocs.add(ioc["type"], ioc["tag"],
ioc["tlp"], ioc["value"], "watcher")
w["status"] = True
except:
continue
# If at least one URL haven't be parsed, let's retry in 1min.
if False in [w["status"] for w in watchers]:
time.sleep(60)
else:
break
def watch_whitelists():
"""
Retrieve whitelist elements from the remote URLs
defined in config/watchers. For each (new ?) element,
add it to the DB.
"""
urls = read_config(("watchers", "whitelists"))
watchers = [{"url": url, "status": False} for url in urls]
while True:
for w in watchers:
if w["status"] == False:
whitelist = WhiteList()
elements = []
try:
res = requests.get(w["url"], verify=False)
if res.status_code == 200:
elements = json.loads(res.content)["elements"]
else:
w["status"] = False
except:
w["status"] = False
for elem in elements:
try:
whitelist.add(elem["type"], elem["element"], "watcher")
w["status"] = True
except:
continue
if False in [w["status"] for w in watchers]:
time.sleep(60)
else:
break
p1 = Process(target=watch_iocs)
p2 = Process(target=watch_whitelists)
p1.start()
p2.start()