First commit
This commit is contained in:
		
							
								
								
									
										15
									
								
								server/backend/app/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								server/backend/app/__init__.py
									
									
									
									
									
										Normal 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()
 | 
			
		||||
							
								
								
									
										88
									
								
								server/backend/app/blueprints/config.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								server/backend/app/blueprints/config.py
									
									
									
									
									
										Normal 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)
 | 
			
		||||
							
								
								
									
										80
									
								
								server/backend/app/blueprints/ioc.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								server/backend/app/blueprints/ioc.py
									
									
									
									
									
										Normal 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'})
 | 
			
		||||
							
								
								
									
										68
									
								
								server/backend/app/blueprints/whitelist.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								server/backend/app/blueprints/whitelist.py
									
									
									
									
									
										Normal 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'})
 | 
			
		||||
							
								
								
									
										107
									
								
								server/backend/app/classes/config.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								server/backend/app/classes/config.py
									
									
									
									
									
										Normal 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
 | 
			
		||||
							
								
								
									
										133
									
								
								server/backend/app/classes/iocs.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								server/backend/app/classes/iocs.py
									
									
									
									
									
										Normal 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"]}
 | 
			
		||||
							
								
								
									
										102
									
								
								server/backend/app/classes/whitelist.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								server/backend/app/classes/whitelist.py
									
									
									
									
									
										Normal 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"]}
 | 
			
		||||
							
								
								
									
										18
									
								
								server/backend/app/db/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								server/backend/app/db/__init__.py
									
									
									
									
									
										Normal 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()
 | 
			
		||||
							
								
								
									
										20
									
								
								server/backend/app/db/models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								server/backend/app/db/models.py
									
									
									
									
									
										Normal 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))
 | 
			
		||||
							
								
								
									
										65
									
								
								server/backend/app/decorators.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								server/backend/app/decorators.py
									
									
									
									
									
										Normal 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
 | 
			
		||||
							
								
								
									
										115
									
								
								server/backend/app/definitions.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								server/backend/app/definitions.py
									
									
									
									
									
										Normal 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
 | 
			
		||||
        }
 | 
			
		||||
    ]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										35
									
								
								server/backend/app/utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								server/backend/app/utils.py
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										68
									
								
								server/backend/main.py
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										109
									
								
								server/backend/watchers.py
									
									
									
									
									
										Normal 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()
 | 
			
		||||
		Reference in New Issue
	
	Block a user