Projects STRLCPY LogonTracer Commits 9cad1163
🤬
  • ■ ■ ■ ■ ■ ■
    config/config.yml
     1 +settings:
     2 + logontracer:
     3 + WEB_PORT: "8080" # Web application port
     4 + WEB_HOST: "0.0.0.0" # Web application address
     5 + database_name: "data.db" # LogonTracer user info database
     6 + default_user: "neo4j" # LogonTracer default login name
     7 + default_password: "password" # LogonTracer default login password
     8 + default_case: "neo4j" # Default neo4j database name
     9 + SESSION_COOKIE_SECURE: False # When using HTTPS, it is necessary to relay a web server such as nginx.
     10 + 
     11 + neo4j:
     12 + NEO4J_USER: "neo4j" # neo4j user name
     13 + NEO4J_PASSWORD: "password" # neo4j password
     14 + NEO4J_SERVER: "localhost" # neo4j server
     15 + NEO4J_PORT: "7474" # neo4j listen port
     16 + WS_PORT: "7687" # Websocket port
     17 + 
     18 + elastic:
     19 + ES_SERVER: "localhost:9200" # Elastic Search server
     20 + ES_INDEX: "winlogbeat-*" # Elastic index
     21 + ES_PREFIX: "winlog" # Elastic prefix
     22 + ES_USER: "elastic" # Elastic auth user
     23 + 
     24 + sigma:
     25 + git_url: "https://github.com/SigmaHQ/sigma.git" # Sigma rules url
     26 + results: "sigma_results.csv" # Sigma scan result file
  • ■ ■ ■ ■ ■ ■
    config/logging.yml
     1 +version: 1
     2 +formatters:
     3 + file:
     4 + format: "[%(asctime)s] [%(levelname)s] : %(message)s"
     5 + 
     6 +handlers:
     7 + file:
     8 + class: logging.handlers.TimedRotatingFileHandler
     9 + formatter: file
     10 + filename: logs/application.log
     11 + backupCount: 8
     12 + when: D
     13 + interval: 1
     14 + encoding: 'utf-8'
     15 + logontracer_handler:
     16 + class: logging.handlers.TimedRotatingFileHandler
     17 + formatter: file
     18 + filename: static/logontracer.log
     19 + backupCount: 8
     20 + when: D
     21 + interval: 1
     22 + encoding: 'utf-8'
     23 + console:
     24 + class: logging.StreamHandler
     25 + stream: ext://sys.stdout
     26 + 
     27 +loggers:
     28 + agent_logger:
     29 + level: INFO
     30 + handlers: [console]
     31 + propagate: no
     32 + 
     33 +root:
     34 + level: WARN
     35 + handlers: [file]
     36 + 
  • ■ ■ ■ ■ ■
    logontracer.py
    skipped 1 lines
    2 2  # -*- coding: utf-8 -*-
    3 3  #
    4 4  # LICENSE
    5  -# Please refer to the LICENSE.txt in the https://github.com/JPCERTCC/aa-tools/
     5 +# https://github.com/JPCERTCC/LogonTracer/blob/master/LICENSE.txt
    6 6  #
    7 7   
    8 8  import os
     9 +import re
    9 10  import sys
    10  -import re
     11 +import csv
     12 +import glob
    11 13  import pickle
    12 14  import shutil
    13 15  import argparse
    14 16  import datetime
    15 17  import subprocess
     18 +from functools import wraps
     19 +from logging import getLogger
     20 +from logging.config import dictConfig
    16 21  from ssl import create_default_context
    17 22   
    18 23  try:
    skipped 9 lines
    28 33   has_evtx = False
    29 34   
    30 35  try:
    31  - from py2neo import Graph, GraphService
     36 + from py2neo import Graph, GraphService, ClientError
    32 37   has_py2neo = True
    33 38  except ImportError:
    34 39   has_py2neo = False
    skipped 11 lines
    46 51   has_changefinder = False
    47 52   
    48 53  try:
    49  - from flask import Flask, render_template, request
     54 + from flask import Flask, render_template, request, redirect, session
    50 55   has_flask = True
    51 56  except ImportError:
    52 57   has_flask = False
    skipped 23 lines
    76 81  except ImportError:
    77 82   has_es = False
    78 83   
    79  -# neo4j password
    80  -NEO4J_PASSWORD = "password"
    81  -# neo4j user name
    82  -NEO4J_USER = "neo4j"
    83  -# neo4j server
    84  -NEO4J_SERVER = "localhost"
    85  -# neo4j listen port
    86  -NEO4J_PORT = "7474"
    87  -# Web application port
    88  -WEB_PORT = 8080
    89  -# Web application address
    90  -WEB_HOST = "0.0.0.0"
    91  -# Websocket port
    92  -WS_PORT = 7687
    93  -# Elastic Search server
    94  -ES_SERVER = "localhost:9200"
    95  -# Elastic index
    96  -ES_INDEX = "winlogbeat-*"
    97  -# Elastic prefix
    98  -ES_PREFIX = "winlog"
    99  -# Elastic auth user
    100  -ES_USER = "elastic"
     84 +try:
     85 + import yaml
     86 + has_pyyaml = True
     87 +except ImportError:
     88 + has_pyyaml = False
     89 + 
     90 +try:
     91 + from flask_login import UserMixin, LoginManager, login_user, logout_user, current_user
     92 + has_flask_login = True
     93 +except ImportError:
     94 + has_flask_login = False
     95 + 
     96 +try:
     97 + from flask_sqlalchemy import SQLAlchemy
     98 + has_flask_sqlalchemy = True
     99 +except ImportError:
     100 + has_flask_sqlalchemy = False
     101 + 
     102 +try:
     103 + from flask_wtf import FlaskForm
     104 + has_flask_wtf = True
     105 +except ImportError:
     106 + has_flask_wtf = False
     107 + 
     108 +try:
     109 + from wtforms import StringField, PasswordField
     110 + from wtforms.validators import ValidationError, DataRequired, EqualTo, Length
     111 + has_wtforms = True
     112 +except ImportError:
     113 + has_wtforms = False
     114 + 
     115 +try:
     116 + import git
     117 + has_git = True
     118 +except ImportError:
     119 + has_git = False
     120 + 
     121 +try:
     122 + from sigma.parser.rule import SigmaParser
     123 + from sigma.configuration import SigmaConfiguration
     124 + from sigma.parser.condition import ConditionAND, ConditionOR, ConditionNOT, NodeSubexpression
     125 + has_sigma = True
     126 +except ImportError:
     127 + has_sigma = False
    101 128   
    102 129  # Check Event Id
    103 130  EVENT_ID = [4624, 4625, 4662, 4768, 4769, 4776, 4672, 4720, 4726, 4728, 4729, 4732, 4733, 4756, 4757, 4719, 5137, 5141]
    skipped 81 lines
    185 212   "{0cce9242-69ae-11d9-bed3-505054503030}": "KerbCredentialValidation",
    186 213   "{0cce9243-69ae-11d9-bed3-505054503030}": "NPS"}
    187 214   
     215 +# Load logging config
     216 +with open(FPATH + "/config/logging.yml", 'r') as logging_open:
     217 + logging_data = yaml.safe_load(logging_open)
     218 + 
     219 +dictConfig(logging_data)
     220 +logger = getLogger("agent_logger")
     221 + 
    188 222  # Flask instance
    189 223  if not has_flask:
    190  - sys.exit("[!] Flask must be installed for this script.")
     224 + logger.error("[!] Flask must be installed for this script.")
     225 + sys.exit(1)
    191 226  else:
    192 227   app = Flask(__name__)
    193 228   
    194 229  parser = argparse.ArgumentParser(description="Visualizing and analyzing active directory Windows logon event logs.")
    195 230  parser.add_argument("-r", "--run", action="store_true", default=False,
    196 231   help="Start web application.")
    197  -parser.add_argument("-l", "--learn", action="store_true", default=False,
    198  - help="Machine learning event logs using Hidden Markov Model.")
    199 232  parser.add_argument("-o", "--port", dest="port", action="store", type=int, metavar="PORT",
    200 233   help="Port number to be started web application. (default: 8080).")
    201 234  parser.add_argument("--host", dest="host", action="store", type=str, metavar="HOST",
    202 235   help="Host address to bind the web application. (default: 0.0.0.0).")
     236 +parser.add_argument("-e", "--evtx", dest="evtx", nargs="*", action="store", type=str, metavar="EVTX",
     237 + help="Import to the AD EVTX file. (multiple files OK)")
     238 +parser.add_argument("-x", "--xml", dest="xmls", nargs="*", action="store", type=str, metavar="XML",
     239 + help="Import to the XML file for event log. (multiple files OK)")
     240 +parser.add_argument("-s", "--server", dest="server", action="store", type=str, metavar="SERVER",
     241 + help="Neo4j server. (default: localhost)")
     242 +parser.add_argument("-u", "--user", dest="user", action="store", type=str, metavar="USERNAME",
     243 + help="Neo4j account name. (default: neo4j)")
     244 +parser.add_argument("-p", "--password", dest="password", action="store", type=str, metavar="PASSWORD",
     245 + help="Neo4j password. (default: password).")
     246 +parser.add_argument("--wsport", dest="wsport", action="store", type=str, metavar="PORT",
     247 + help="Neo4j websocket port number. (default: 7687).")
     248 +parser.add_argument("-l", "--learn", action="store_true", default=False,
     249 + help="Machine learning event logs using Hidden Markov Model.")
     250 +parser.add_argument("--sigma", action="store_true", default=False,
     251 + help="Scan using Sigma rule. (default: False)")
    203 252  parser.add_argument("--es-server", dest="esserver", action="store", type=str, metavar="ESSERVER",
    204 253   help="Elastic Search server address. (default: localhost:9200)")
    205 254  parser.add_argument("--es-index", dest="esindex", action="store", type=str, metavar="ESINDEX",
    skipped 10 lines
    216 265   help="Import data from Elastic Search. (default: False)")
    217 266  parser.add_argument("--postes", action="store_true", default=False,
    218 267   help="Post data to Elastic Search. (default: False)")
    219  -parser.add_argument("-s", "--server", dest="server", action="store", type=str, metavar="SERVER",
    220  - help="Neo4j server. (default: localhost)")
    221  -parser.add_argument("-u", "--user", dest="user", action="store", type=str, metavar="USERNAME",
    222  - help="Neo4j account name. (default: neo4j)")
    223  -parser.add_argument("-p", "--password", dest="password", action="store", type=str, metavar="PASSWORD",
    224  - help="Neo4j password. (default: password).")
    225  -parser.add_argument("--wsport", dest="wsport", action="store", type=str, metavar="PORT",
    226  - help="Neo4j websocket port number. (default: 7687).")
    227  -parser.add_argument("-e", "--evtx", dest="evtx", nargs="*", action="store", type=str, metavar="EVTX",
    228  - help="Import to the AD EVTX file. (multiple files OK)")
    229  -parser.add_argument("-x", "--xml", dest="xmls", nargs="*", action="store", type=str, metavar="XML",
    230  - help="Import to the XML file for event log. (multiple files OK)")
    231 268  parser.add_argument("-z", "--timezone", dest="timezone", action="store", type=int, metavar="UTC",
    232 269   help="Event log time zone. (for example: +9) (default: GMT)")
    233 270  parser.add_argument("-f", "--from", dest="fromdate", action="store", type=str, metavar="DATE",
    234 271   help="Parse Security Event log from this time. (for example: 2017-01-01T00:00:00)")
    235 272  parser.add_argument("-t", "--to", dest="todate", action="store", type=str, metavar="DATE",
    236 273   help="Parse Security Event log to this time. (for example: 2017-02-28T23:59:59)")
     274 +parser.add_argument("-c", "--config", dest="config", action="store", type=str, metavar="FILE",
     275 + help="Configuration file path. (default: config/config.yml)")
     276 +parser.add_argument("--case", dest="case", action="store", type=str, metavar="CASE_NAME",
     277 + help="[for Neo4j Enterprise] Case management option. If you want to manage each EVTX files in case. (default: neo4j)")
     278 +parser.add_argument("--create_user", dest="create_user", action="store", type=str, metavar="USER",
     279 + help="Create a new Neo4j user.")
     280 +parser.add_argument("--create_password", dest="create_password", action="store", type=str, metavar="PASSWORD",
     281 + help="Create a new Neo4j password.")
     282 +parser.add_argument("--role", dest="role", action="store", type=str, metavar="ROLE",
     283 + help="[for Neo4j Enterprise] User role option [admin, architect, reader]. (default: reader)")
     284 +parser.add_argument("--delete_user", dest="delete_user", action="store", type=str, metavar="USER",
     285 + help="Delete a Neo4j user.")
    237 286  parser.add_argument("--add", action="store_true", default=False,
    238 287   help="Add additional data to Neo4j database. (default: False)")
    239 288  parser.add_argument("--delete", action="store_true", default=False,
    skipped 54 lines
    294 343   RETURN user, id
    295 344   """
    296 345   
     346 +statement_cd = """
     347 + CREATE DATABASE {case};
     348 + """
     349 + 
     350 +statement_dd = """
     351 + DROP DATABASE {case};
     352 + """
     353 + 
     354 +statement_cu = """
     355 + CREATE USER {username} SET PASSWORD '{password}' CHANGE NOT REQUIRED;
     356 + """
     357 + 
     358 +# for Neo4j enterprise edition
     359 +#statement_cu = """
     360 +# CREATE USER {username} SET PASSWORD '{password}' CHANGE NOT REQUIRED SET STATUS ACTIVE;
     361 +# """
     362 + 
     363 +statement_au = """
     364 + ALTER CURRENT USER SET PASSWORD FROM '{oldPassword}' TO '{newPassword}';
     365 + """
     366 + 
     367 +statement_du = """
     368 + DROP USER {username};
     369 + """
     370 + 
     371 +statement_su = """
     372 + ALTER USER {username} SET STATUS {action};
     373 + """
     374 + 
     375 +statement_role_add = """
     376 + CREATE OR REPLACE ROLE {username}_role AS COPY OF {role};
     377 + """
     378 + 
     379 +statement_role_revole = """
     380 + REVOKE GRANT ACCESS ON DATABASES {database} FROM {username}_role;
     381 + """
     382 + 
     383 +statement_role_set = """
     384 + GRANT ROLE {username}_role TO {username};
     385 + """
     386 + 
     387 +statement_role_set_admin = """
     388 + GRANT ROLE admin TO {username};
     389 + """
     390 + 
     391 +statement_default_db_access = """
     392 + GRANT ACCESS ON DATABASE neo4j TO {username}_role;
     393 + """
     394 + 
     395 +statement_db_access = """
     396 + GRANT ACCESS ON DATABASE {database} TO {username}_role;
     397 + """
     398 + 
    297 399  es_doc_user = """
    298 400   {{"@timestamp":"{datetime}", "user":"{user}", "rights":"{rights}", "sid":"{sid}", "status":"{status}", "rank":{rank}}}
    299 401   """
    skipped 2 lines
    302 404   {{"@timestamp":"{datetime}", "IP":"{IP}", "hostname":"{hostname}", "rank":{rank}}}
    303 405   """
    304 406   
     407 +if not has_flask_login:
     408 + logger.error("[!] flask-login must be installed for this script.")
     409 + sys.exit(1)
     410 + 
     411 +if not has_flask_sqlalchemy:
     412 + logger.error("[!] flask-sqlalchemy must be installed for this script.")
     413 + sys.exit(1)
     414 + 
     415 +if not has_pyyaml:
     416 + logger.error("[!] pyyaml must be installed for this script.")
     417 + sys.exit(1)
     418 + 
     419 +if not has_flask_wtf:
     420 + logger.error("[!] flask_wtf must be installed for this script.")
     421 + sys.exit(1)
     422 + 
     423 +if not has_wtforms:
     424 + logger.error("[!] wtforms must be installed for this script.")
     425 + sys.exit(1)
     426 + 
     427 +if args.config:
     428 + config_path = args.config
     429 +else:
     430 + config_path = FPATH + "/config/config.yml"
     431 + 
     432 +with open(config_path, 'r') as config_open:
     433 + config_data = yaml.safe_load(config_open)["settings"]
     434 + 
     435 +# neo4j password
     436 +NEO4J_PASSWORD = config_data["neo4j"]["NEO4J_PASSWORD"]
     437 +# neo4j user name
     438 +NEO4J_USER = config_data["neo4j"]["NEO4J_USER"]
     439 +# neo4j server
     440 +NEO4J_SERVER = config_data["neo4j"]["NEO4J_SERVER"]
     441 +# neo4j listen port
     442 +NEO4J_PORT = config_data["neo4j"]["NEO4J_PORT"]
     443 +# Web application port
     444 +WEB_PORT = config_data["logontracer"]["WEB_PORT"]
     445 +# Web application address
     446 +WEB_HOST = config_data["logontracer"]["WEB_HOST"]
     447 +# Flag for SESSION_COOKIE_SECURE
     448 +USE_HTTPS = config_data["logontracer"]["SESSION_COOKIE_SECURE"]
     449 +# Websocket port
     450 +WS_PORT = config_data["neo4j"]["WS_PORT"]
     451 +# Elastic Search server
     452 +ES_SERVER = config_data["elastic"]["ES_SERVER"]
     453 +# Elastic index
     454 +ES_INDEX = config_data["elastic"]["ES_INDEX"]
     455 +# Elastic prefix
     456 +ES_PREFIX = config_data["elastic"]["ES_PREFIX"]
     457 +# Elastic auth user
     458 +ES_USER = config_data["elastic"]["ES_USER"]
     459 +# logontracer default user
     460 +default_user = config_data["logontracer"]["default_user"]
     461 +# logontracer default password
     462 +default_password = config_data["logontracer"]["default_password"]
     463 +# logontracer user info database
     464 +database_name = config_data["logontracer"]["database_name"]
     465 +# Default neo4j database name
     466 +CASE_NAME = config_data["logontracer"]["default_case"]
     467 +# Sigma rules url
     468 +SIGMA_URL = config_data["sigma"]["git_url"]
     469 +# Sigma scan result file
     470 +SIGMA_RESULTS_FILE = config_data["sigma"]["results"]
     471 + 
    305 472  if args.user:
    306 473   NEO4J_USER = args.user
    307 474   
    skipped 29 lines
    337 504   
    338 505  if args.escafile:
    339 506   ES_CAFILE = args.escafile
     507 + 
     508 +if args.case:
     509 + CASE_NAME = args.case
     510 + 
     511 +# Setup login user
     512 +app.config["SESSION_COOKIE_SECURE"] = USE_HTTPS
     513 +app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
     514 +app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///" + database_name
     515 +app.config["SECRET_KEY"] = os.urandom(24)
     516 +app.permanent_session_lifetime = datetime.timedelta(minutes=60)
     517 +db = SQLAlchemy(app)
     518 + 
     519 +login_manager = LoginManager()
     520 +login_manager.init_app(app)
     521 + 
     522 +class User(UserMixin, db.Model):
     523 + id = db.Column(db.Integer, primary_key=True)
     524 + username = db.Column(db.String(50), nullable=False, unique=True)
     525 + urole = db.Column(db.String(20))
     526 + 
     527 + def __init__(self, username, urole):
     528 + self.username = username
     529 + self.urole = urole
     530 + def get_id(self):
     531 + return self.id
     532 + def get_username(self):
     533 + return self.username
     534 + def get_urole(self):
     535 + return self.urole
     536 + 
     537 + 
     538 +class SettingForm(FlaskForm):
     539 + password1 = PasswordField('Password', validators=[DataRequired(), EqualTo('password2', message='Passwords must match.'), Length(min=3, max=20)])
     540 + password2 = PasswordField('Password (again)', validators=[DataRequired(), Length(min=3, max=20)])
     541 + 
     542 +class LoginForm(FlaskForm):
     543 + username = StringField('Username', validators=[DataRequired(), Length(min=3, max=50)])
     544 + password = PasswordField('Password', validators=[DataRequired(), Length(min=3, max=20)])
     545 + 
     546 +class RegistrationForm(FlaskForm):
     547 + username = StringField('Username', validators=[DataRequired(), Length(min=3, max=50)])
     548 + password1 = PasswordField('Password', validators=[DataRequired(), EqualTo('password2', message='Passwords must match.'), Length(min=3, max=20)])
     549 + password2 = PasswordField('Password (again)', validators=[DataRequired(), Length(min=3, max=20)])
     550 + 
     551 + def validate_username(self, username):
     552 + user = User.query.filter_by(username=username.data).first()
     553 + if user is not None:
     554 + raise ValidationError('This username is already registered.')
     555 + 
     556 +class CaseForm(FlaskForm):
     557 + case = StringField('Case', validators=[DataRequired()])
     558 + 
     559 +with app.app_context():
     560 + db.create_all()
     561 + 
     562 + user_query = User.query.filter_by(username=default_user).first()
     563 + if user_query is None:
     564 + create_user = User(username=default_user, urole="ADMIN")
     565 + db.session.add(create_user)
     566 + db.session.commit()
     567 + 
     568 +@login_manager.user_loader
     569 +def load_user(user_id):
     570 + return User.query.get(int(user_id))
     571 + 
     572 +@login_manager.unauthorized_handler
     573 +def unauthorized():
     574 + return redirect('/login')
     575 + 
     576 +# Web application logging decorater
     577 +def http_request_logging(f):
     578 + @wraps(f)
     579 + def decorated_function(*args, **kwargs):
     580 + try:
     581 + app.logger.info('%s - %s - %s - %s', request.remote_addr, request.method, request.url, request.query_string)
     582 + except Exception as e:
     583 + app.logger.exception(e)
     584 + pass
     585 + return f(*args, **kwargs)
     586 + return decorated_function
     587 + 
     588 + 
     589 +# Costom login_required with role
     590 +def login_required(role="ANY"):
     591 + def wrapper(fn):
     592 + @wraps(fn)
     593 + def decorated_view(*args, **kwargs):
     594 + if not current_user.is_authenticated:
     595 + return login_manager.unauthorized()
     596 + urole = current_user.get_urole()
     597 + if ((urole != role) and (role != "ANY")):
     598 + return login_manager.unauthorized()
     599 + return fn(*args, **kwargs)
     600 + return decorated_view
     601 + return wrapper
     602 + 
     603 + 
     604 +# Web application login page
     605 +@app.route('/login', methods=['GET', 'POST'])
     606 +@http_request_logging
     607 +def login():
     608 + if current_user.is_authenticated:
     609 + return redirect('/')
     610 + 
     611 + session.permanent = True
     612 + session["case"] = CASE_NAME
     613 + 
     614 + form = LoginForm(request.form)
     615 + if form.validate_on_submit():
     616 + username = form.username.data
     617 + password = form.password.data
     618 + remember = True if request.form.get("remember") else False
     619 + 
     620 + session["username"] = username
     621 + session["password"] = password
     622 + 
     623 + try:
     624 + GraphService(host=NEO4J_SERVER, user=session["username"], password=session["password"])
     625 + user = User.query.filter_by(username=username).first()
     626 + logger.info("[+] login user {0}.".format(username))
     627 + login_user(user, remember=remember)
     628 + return redirect('/')
     629 + except:
     630 + logger.error("[!] login failed user {0}.".format(username))
     631 + return render_template('login.html', form=form, messages='<div class="alert alert-danger" role="alert">Invalid username or password.</div>')
     632 + 
     633 + return render_template('login.html', form=form, messages="")
     634 + 
     635 + 
     636 +# Web application signup page
     637 +@app.route('/signup', methods=['GET', 'POST'])
     638 +@http_request_logging
     639 +@login_required(role="ADMIN")
     640 +def signup():
     641 + form = RegistrationForm(request.form)
     642 + if form.validate_on_submit():
     643 + username = form.username.data
     644 + password = form.password1.data
     645 + admin = True if request.form.get("admin") else False
     646 + 
     647 + if admin:
     648 + role = "ADMIN"
     649 + role_neo4j = "admin"
     650 + else:
     651 + role = "USER"
     652 + role_neo4j = "architect"
     653 + 
     654 + with app.app_context():
     655 + user = User(username=username, urole=role)
     656 + db.session.add(user)
     657 + db.session.commit()
     658 + 
     659 + try:
     660 + service = GraphService(host=NEO4J_SERVER, user=session["username"], password=session["password"])
     661 + except:
     662 + logger.error("[!] Can't connect Neo4j Database GraphService.")
     663 + sys.exit(1)
     664 + 
     665 + create_neo4j_user(service, username, password, role_neo4j)
     666 + 
     667 + return redirect('/')
     668 + else:
     669 + return render_template('signup.html', form=form)
     670 + 
     671 + 
     672 +# Web application change password page
     673 +@app.route('/setting', methods=['GET', 'POST'])
     674 +@http_request_logging
     675 +@login_required(role="ANY")
     676 +def setting():
     677 + form = SettingForm(request.form)
     678 + if form.validate_on_submit():
     679 + username = current_user.username
     680 + password = form.password1.data
     681 + 
     682 + with app.app_context():
     683 + user_query = User.query.filter_by(username=username).first()
     684 + db.session.delete(user_query)
     685 + db.session.commit()
     686 + 
     687 + user = User(username=username, urole=user_query.urole)
     688 + db.session.add(user)
     689 + db.session.commit()
     690 + 
     691 + try:
     692 + service = GraphService(host=NEO4J_SERVER, user=session["username"], password=session["password"])
     693 + except:
     694 + logger.error("[!] Can't connect Neo4j Database GraphService.")
     695 + sys.exit(1)
     696 + 
     697 + try:
     698 + system = service.system_graph
     699 + system.run(statement_au.format(**{"oldPassword": session["password"], "newPassword": password}))
     700 + logger.info("[+] Change user {0} password for neo4j.".format(username))
     701 + except ClientError as e:
     702 + if "User does not exist" in str(e):
     703 + logger.error("[!] User does not exist {0}.".format(username))
     704 + elif "Unsupported administration command" in str(e):
     705 + logger.error("[!] Can't change password.")
     706 + else:
     707 + logger.error(str(e))
     708 + 
     709 + session["password"] = password
     710 + 
     711 + return redirect('/')
     712 + else:
     713 + return render_template('setting.html', form=form)
     714 + 
     715 + 
     716 +# Web application logout
     717 +@app.route('/logout')
     718 +@http_request_logging
     719 +@login_required(role="ANY")
     720 +def logout():
     721 + logout_user()
     722 + return redirect('/login')
     723 + 
     724 + 
     725 +# Web application create case
     726 +@app.route('/addcase', methods=['GET', 'POST'])
     727 +@http_request_logging
     728 +@login_required(role="ADMIN")
     729 +def addcase():
     730 + form = CaseForm(request.form)
     731 + if form.validate_on_submit():
     732 + try:
     733 + service = GraphService(host=NEO4J_SERVER, user=session["username"], password=session["password"])
     734 + except:
     735 + logger.error("[!] Can't connect Neo4j Database GraphService.")
     736 + sys.exit(1)
     737 + 
     738 + if "Enterprise" in service.product:
     739 + case = form.case.data
     740 + if not re.search(r"\A[0-9a-zA-Z]{2,20}\Z", case):
     741 + return render_template('addcase.html', form=form, messages='<div class="alert alert-danger" role="alert">You can use letters upper/lowercase and numbers.</div>')
     742 + 
     743 + session["case"] = case
     744 + create_database(service, case)
     745 + 
     746 + return render_template("index.html", server_ip=NEO4J_SERVER, ws_port=WS_PORT, neo4j_password=session["password"], neo4j_user=session["username"], case_name=case)
     747 + else:
     748 + return render_template("index.html", server_ip=NEO4J_SERVER, ws_port=WS_PORT, neo4j_password=session["password"], neo4j_user=session["username"], case_name=CASE_NAME)
     749 + else:
     750 + return render_template('addcase.html', form=form, messages='<div class="alert alert-info" role="alert">This feature is in Neo4j Enterprise.</div>')
     751 + 
     752 + 
     753 +# Web application delete case
     754 +@app.route('/delcase', methods=['GET', 'POST'])
     755 +@http_request_logging
     756 +@login_required(role="ADMIN")
     757 +def delcase():
     758 + if request.method == "POST":
     759 + case_name = request.form.get('caseName')
     760 + 
     761 + try:
     762 + service = GraphService(host=NEO4J_SERVER, user=session["username"], password=session["password"])
     763 + except:
     764 + logger.error("[!] Can't connect Neo4j Database GraphService.")
     765 + sys.exit(1)
     766 + 
     767 + if "Enterprise" in service.product:
     768 + if re.search(r"\A[0-9a-zA-Z]{2,20}\Z", case_name):
     769 + delete_database(service, case_name)
     770 + 
     771 + return render_template("delcase.html", server_ip=NEO4J_SERVER, ws_port=WS_PORT, neo4j_password=session["password"], neo4j_user=session["username"], case_name=session["case"], messages='<div><div class="alert alert-success" role="alert">Deleted case ' + case_name + '</div></div>')
     772 + else:
     773 + return render_template("delcase.html", server_ip=NEO4J_SERVER, ws_port=WS_PORT, neo4j_password=session["password"], neo4j_user=session["username"], case_name=session["case"], messages='<div><div class="alert alert-danger" role="alert">This feature is in Neo4j Enterprise.</div></div>')
     774 + else:
     775 + return render_template("delcase.html", server_ip=NEO4J_SERVER, ws_port=WS_PORT, neo4j_password=session["password"], neo4j_user=session["username"], case_name=session["case"], messages='')
     776 + 
     777 + 
     778 +# Web application change case
     779 +@app.route('/changecase', methods=['GET', 'POST'])
     780 +@http_request_logging
     781 +@login_required(role="ANY")
     782 +def changecase():
     783 + if request.method == "POST":
     784 + case_name = request.form.get('caseName')
     785 + if not re.search(r"\A[0-9a-zA-Z]{2,20}\Z", case_name):
     786 + return redirect('/')
     787 + 
     788 + session["case"] = case_name
     789 + 
     790 + return render_template("index.html", server_ip=NEO4J_SERVER, ws_port=WS_PORT, neo4j_password=session["password"], neo4j_user=session["username"], case_name=case_name)
     791 + else:
     792 + return render_template("changecase.html", server_ip=NEO4J_SERVER, ws_port=WS_PORT, neo4j_password=session["password"], neo4j_user=session["username"])
     793 + 
     794 + 
     795 +@app.route('/changecase_t', methods=['GET', 'POST'])
     796 +@http_request_logging
     797 +@login_required(role="ANY")
     798 +def changecase_t():
     799 + if request.method == "POST":
     800 + case_name = request.form.get('caseName')
     801 + if not re.search(r"\A[0-9a-zA-Z]{2,20}\Z", case_name):
     802 + return redirect('/')
     803 + 
     804 + session["case"] = case_name
     805 + 
     806 + return render_template("timeline.html", server_ip=NEO4J_SERVER, ws_port=WS_PORT, neo4j_password=session["password"], neo4j_user=session["username"], case_name=case_name)
     807 + else:
     808 + return render_template("changecase.html", server_ip=NEO4J_SERVER, ws_port=WS_PORT, neo4j_password=session["password"], neo4j_user=session["username"])
     809 + 
     810 + 
     811 +# Web application add case management
     812 +@app.route('/casemng', methods=['GET', 'POST'])
     813 +@http_request_logging
     814 +@login_required(role="ADMIN")
     815 +def case_management():
     816 + if request.method == "POST":
     817 + user = request.form.get("userSelect")
     818 + case_name = request.form.get('caseName')
     819 + 
     820 + try:
     821 + service = GraphService(host=NEO4J_SERVER, user=session["username"], password=session["password"])
     822 + except:
     823 + logger.error("[!] Can't connect Neo4j Database GraphService.")
     824 + sys.exit(1)
     825 + 
     826 + if "Enterprise" in service.product:
     827 + if not re.search(UCHECK, user) and re.search(r"\A[0-9a-zA-Z]{2,20}\Z", case_name):
     828 + add_db_access_role(service, user, case_name)
     829 + 
     830 + return render_template("casemng.html", server_ip=NEO4J_SERVER, ws_port=WS_PORT, neo4j_password=session["password"], neo4j_user=session["username"], case_name=session["case"], messages='<div><div class="alert alert-success" role="alert">Added access role for case ' + case_name + ' of user ' + user + '</div></div>')
     831 + else:
     832 + return render_template("casemng.html", server_ip=NEO4J_SERVER, ws_port=WS_PORT, neo4j_password=session["password"], neo4j_user=session["username"], case_name=session["case"], messages='<div><div class="alert alert-danger" role="alert">This feature is in Neo4j Enterprise.</div></div>')
     833 + else:
     834 + return render_template("casemng.html", server_ip=NEO4J_SERVER, ws_port=WS_PORT, neo4j_password=session["password"], neo4j_user=session["username"], case_name=session["case"], messages='')
     835 + 
     836 + 
     837 +# Web application delete case management
     838 +@app.route('/delcasemng', methods=['GET', 'POST'])
     839 +@http_request_logging
     840 +@login_required(role="ADMIN")
     841 +def case_management_del():
     842 + if request.method == "POST":
     843 + user_db = [userlist.split("_") for userlist in request.form.getlist("userlist")]
     844 + 
     845 + try:
     846 + service = GraphService(host=NEO4J_SERVER, user=session["username"], password=session["password"])
     847 + except:
     848 + logger.error("[!] Can't connect Neo4j Database GraphService.")
     849 + sys.exit(1)
     850 + 
     851 + if "Enterprise" in service.product:
     852 + for user, case_name in user_db:
     853 + if not re.search(UCHECK, user) and re.search(r"\A[0-9a-zA-Z]{2,20}\Z", case_name):
     854 + delete_db_access_role(service, user, case_name)
     855 + 
     856 + return render_template("delcasemng.html", server_ip=NEO4J_SERVER, ws_port=WS_PORT, neo4j_password=session["password"], neo4j_user=session["username"], case_name=session["case"], messages='<div><div class="alert alert-success" role="alert">Deleted access role to case ' + case_name + ' for user ' + user + '</div></div>')
     857 + else:
     858 + return render_template("delcasemng.html", server_ip=NEO4J_SERVER, ws_port=WS_PORT, neo4j_password=session["password"], neo4j_user=session["username"], case_name=session["case"], messages='<div><div class="alert alert-danger" role="alert">This feature is in Neo4j Enterprise.</div></div>')
     859 + else:
     860 + return render_template("delcasemng.html", server_ip=NEO4J_SERVER, ws_port=WS_PORT, neo4j_password=session["password"], neo4j_user=session["username"], case_name=session["case"], messages='')
     861 + 
     862 + 
     863 +# Web application user management
     864 +@app.route('/usermng', methods=['GET', 'POST'])
     865 +@http_request_logging
     866 +@login_required(role="ADMIN")
     867 +def user_management():
     868 + if request.method == "POST":
     869 + users = [userlist.strip("Check_") for userlist in request.form.getlist("userlist")]
     870 + action = request.form.get("action")
     871 + 
     872 + try:
     873 + service = GraphService(host=NEO4J_SERVER, user=session["username"], password=session["password"])
     874 + except:
     875 + logger.error("[!] Can't connect Neo4j Database GraphService.")
     876 + sys.exit(1)
     877 + 
     878 + if "delete" in action:
     879 + for user in users:
     880 + if not re.search(UCHECK, user):
     881 + delete_neo4j_user(service, user)
     882 + with app.app_context():
     883 + user_query = User.query.filter_by(username=user).first()
     884 + db.session.delete(user_query)
     885 + db.session.commit()
     886 + elif ("suspended" in action or "active" in action) and "Enterprise" in service.product:
     887 + for user in users:
     888 + if not re.search(UCHECK, user):
     889 + change_status_neo4j_user(service, user, action)
     890 + 
     891 + return render_template("usermng.html", server_ip=NEO4J_SERVER, ws_port=WS_PORT, neo4j_password=session["password"], neo4j_user=session["username"])
     892 + else:
     893 + return render_template("usermng.html", server_ip=NEO4J_SERVER, ws_port=WS_PORT, neo4j_password=session["password"], neo4j_user=session["username"])
     894 + 
    340 895   
    341 896  # Web application index.html
    342 897  @app.route('/')
     898 +@http_request_logging
     899 +@login_required(role="ANY")
    343 900  def index():
    344  - return render_template("index.html", server_ip=NEO4J_SERVER, ws_port=WS_PORT, neo4j_password=NEO4J_PASSWORD, neo4j_user=NEO4J_USER)
     901 + return render_template("index.html", server_ip=NEO4J_SERVER, ws_port=WS_PORT, neo4j_password=session["password"], neo4j_user=session["username"], case_name=session["case"])
    345 902   
    346 903   
    347 904  # Timeline view
    348 905  @app.route('/timeline')
     906 +@login_required(role="ANY")
     907 +@http_request_logging
    349 908  def timeline():
    350  - return render_template("timeline.html", server_ip=NEO4J_SERVER, ws_port=WS_PORT, neo4j_password=NEO4J_PASSWORD, neo4j_user=NEO4J_USER)
     909 + return render_template("timeline.html", server_ip=NEO4J_SERVER, ws_port=WS_PORT, neo4j_password=session["password"], neo4j_user=session["username"], case_name=session["case"])
    351 910   
    352 911   
    353 912  # Web application logs
    354 913  @app.route('/log')
     914 +@login_required(role="ANY")
    355 915  def logs():
    356 916   with open(FPATH + "/static/logontracer.log", "r") as lf:
    357 917   logdata = lf.read()
    358 918   return logdata
    359 919   
    360 920   
     921 +# Sigma rule scan results
     922 +@app.route('/sigma')
     923 +@login_required(role="ANY")
     924 +def sigma():
     925 + with open(FPATH + "/static/sigma_results.csv", "r") as sf:
     926 + sigma_logs = sf.read()
     927 + return sigma_logs
     928 + 
     929 + 
    361 930  # Web application upload
    362 931  @app.route("/upload", methods=["POST"])
     932 +@login_required(role="ANY")
     933 +@http_request_logging
    363 934  def do_upload():
    364 935   UPLOAD_DIR = os.path.join(FPATH, 'upload')
    365 936   filelist = ""
    366 937   
    367 938   if os.path.exists(UPLOAD_DIR) is False:
    368  - os.mkdir(UPLOAD_DIR)
    369  - print("[+] make upload folder {0}.".format(UPLOAD_DIR))
     939 + os.makedirs(UPLOAD_DIR)
     940 + logger.info("[+] make upload folder {0}.".format(UPLOAD_DIR))
    370 941   
    371 942   try:
    372 943   timezone = request.form["timezone"]
    373 944   logtype = request.form["logtype"]
    374 945   addlog = request.form["addlog"]
     946 + sigmascan = request.form["sigmascan"]
     947 + casename = request.form["casename"]
    375 948   for i in range(0, len(request.files)):
    376 949   loadfile = "file" + str(i)
    377 950   file = request.files[loadfile]
    skipped 15 lines
    393 966   if not re.search(r"\A-{0,1}[0-9]{1,2}\Z", timezone):
    394 967   return "FAIL"
    395 968   if addlog in "true":
    396  - log_option = "--add"
     969 + add_option = "--add"
    397 970   else:
    398  - log_option = "--delete"
     971 + add_option = "--delete"
     972 + if sigmascan in "true":
     973 + add_option += " --sigma"
     974 + if not re.search(r"\A[0-9a-zA-Z]{2,20}\Z", casename):
     975 + return "FAIL"
    399 976   
    400  - parse_command = "nohup python3 " + FPATH + "/logontracer.py " + log_option + " -z " + timezone + logoption + filelist + " -s " + NEO4J_SERVER + " -u " + NEO4J_USER + " -p " + NEO4J_PASSWORD + " > " + FPATH + "/static/logontracer.log 2>&1 &"
     977 + parse_command = "nohup python3 " + FPATH + "/logontracer.py " + add_option + " --case " + casename + " -z " + timezone + logoption + filelist + " -s " + NEO4J_SERVER + " -u " + session["username"] + " -p " + session["password"] + " > " + FPATH + "/static/logontracer.log 2>&1 &"
    401 978   subprocess.call("rm -f " + FPATH + "/static/logontracer.log > /dev/null", shell=True)
    402 979   subprocess.call(parse_command, shell=True)
    403 980   # parse_evtx(filename)
    skipped 5 lines
    409 986   
    410 987  # Load from Elasticsearch
    411 988  @app.route("/esload", methods=["POST"])
     989 +@login_required(role="ANY")
     990 +@http_request_logging
    412 991  def es_load():
    413 992   try:
    414 993   fromdatetime = request.form["fromdatetime"]
    skipped 1 lines
    416 995   timezone = request.form["timezone"]
    417 996   es_server = request.form["es_server"]
    418 997   addlog = request.form["addlog"]
     998 + casename = request.form["casename"]
    419 999   addes = request.form["addes"]
    420 1000   
    421 1001   if fromdatetime not in "false":
    skipped 33 lines
    455 1035   else:
    456 1036   es_option = ""
    457 1037   
    458  - parse_command = "nohup python3 " + FPATH + "/logontracer.py --es " + log_option + es_option + " -z " + timezone + fromdatetime + todatetime + es_server + " -s " + NEO4J_SERVER + " -u " + NEO4J_USER + " -p " + NEO4J_PASSWORD + " --es-index " + ES_INDEX + " --es-prefix " + ES_PREFIX + " > " + FPATH + "/static/logontracer.log 2>&1 &"
     1038 + if not re.search(r"\A[0-9a-zA-Z]{2,20}\Z", casename):
     1039 + return "FAIL"
     1040 + 
     1041 + parse_command = "nohup python3 " + FPATH + "/logontracer.py --es " + log_option + es_option + " --case " + casename + " -z " + timezone + fromdatetime + todatetime + es_server + " -s " + NEO4J_SERVER + " -u " + session["username"] + " -p " + session["password"] + " --es-index " + ES_INDEX + " --es-prefix " + ES_PREFIX + " > " + FPATH + "/static/logontracer.log 2>&1 &"
    459 1042   subprocess.call("rm -f " + FPATH + "/static/logontracer.log > /dev/null", shell=True)
    460 1043   subprocess.call(parse_command, shell=True)
    461 1044   return "SUCCESS"
    462 1045   
    463 1046   except:
    464 1047   return "FAIL"
     1048 + 
     1049 + 
     1050 +@app.route("/favicon.ico")
     1051 +def favicon():
     1052 + return app.send_static_file("favicon.ico")
    465 1053   
    466 1054   
    467 1055  # Calculate ChangeFinder
    skipped 233 lines
    701 1289   return datetime.datetime.strptime(tzless, "%Y-%m-%dT%H:%M:%S") + datetime.timedelta(hours=tzone)
    702 1290   
    703 1291   
     1292 +# Create database for neo4j
     1293 +def create_database(service, database):
     1294 + try:
     1295 + system = service.system_graph
     1296 + system.run(statement_cd.format(**{"case": database}))
     1297 + logger.info("[+] Created database {0}.".format(database))
     1298 + except ClientError as e:
     1299 + if "Database already exists" in str(e):
     1300 + logger.info("[+] Use database {0}.".format(database))
     1301 + elif "Unsupported administration command" in str(e):
     1302 + logger.info("[+] Can't create database. This feature is in Neo4j Enterprise.")
     1303 + database = "neo4j"
     1304 + except:
     1305 + database = "neo4j"
     1306 + 
     1307 + return database
     1308 + 
     1309 + 
     1310 +# Create database for neo4j
     1311 +def delete_database(service, database):
     1312 + try:
     1313 + system = service.system_graph
     1314 + system.run(statement_dd.format(**{"case": database}))
     1315 + logger.info("[+] Delete database {0}.".format(database))
     1316 + except ClientError as e:
     1317 + if "Database does not exist" in str(e):
     1318 + logger.error("[!] Database does not exist {0}.".format(database))
     1319 + elif "Unsupported administration command" in str(e):
     1320 + logger.error("[!] Can't delete database. This feature is in Neo4j Enterprise.")
     1321 + else:
     1322 + logger.error(str(e))
     1323 + 
     1324 + 
     1325 +# Create user for neo4j
     1326 +def create_neo4j_user(service, username, password, role):
     1327 + system = service.system_graph
     1328 + 
     1329 + try:
     1330 + system.run(statement_cu.format(**{"username": username, "password": password}))
     1331 + logger.info("[+] Created user {0} for neo4j.".format(username))
     1332 + except ClientError as e:
     1333 + if "User already exists" in str(e):
     1334 + logger.error("[!] User already exists {0}.".format(username))
     1335 + elif "Unsupported administration command" in str(e):
     1336 + logger.error("[!] Can't create user.")
     1337 + else:
     1338 + logger.error(str(e))
     1339 + 
     1340 + if "Enterprise" in service.product:
     1341 + try:
     1342 + # For admin role, do not revokes database access.
     1343 + if "admin" in role:
     1344 + system.run(statement_role_set_admin.format(**{"username": username}))
     1345 + logger.info("[+] Set {0} admin role for neo4j.".format(username))
     1346 + else:
     1347 + system.run(statement_role_add.format(**{"username": username, "role": role}))
     1348 + system.run(statement_role_revole.format(**{"database": "*", "username": username}))
     1349 + system.run(statement_role_set.format(**{"username": username}))
     1350 + system.run(statement_default_db_access.format(**{"username": username}))
     1351 + logger.info("[+] Created {0}_role for neo4j.".format(username))
     1352 + except ClientError as e:
     1353 + if "Role already exists" in str(e):
     1354 + logger.error("[!] Role already exists {0}.".format(username))
     1355 + elif "Unsupported administration command" in str(e):
     1356 + logger.error("[!] Can't create role. This feature is in Neo4j Enterprise.")
     1357 + else:
     1358 + logger.error(str(e))
     1359 + 
     1360 + 
     1361 +# Delete user for neo4j
     1362 +def delete_neo4j_user(service, username):
     1363 + try:
     1364 + system = service.system_graph
     1365 + system.run(statement_du.format(**{"username": username}))
     1366 + logger.info("[+] Delete user {0} for neo4j.".format(username))
     1367 + except ClientError as e:
     1368 + if "User does not exist" in str(e):
     1369 + logger.error("[!] User does not exist {0}.".format(username))
     1370 + elif "Unsupported administration command" in str(e):
     1371 + logger.error("[!] Can't delete user.")
     1372 + else:
     1373 + logger.error(str(e))
     1374 + 
     1375 + 
     1376 +# Change user status for neo4j
     1377 +def change_status_neo4j_user(service, username, action):
     1378 + try:
     1379 + system = service.system_graph
     1380 + system.run(statement_su.format(**{"username": username, "action": action}))
     1381 + logger.info("[+] Change user {0} status {1} for neo4j.".format(username, action))
     1382 + except ClientError as e:
     1383 + if "User does not exist" in str(e):
     1384 + logger.error("[!] User does not exist {0}.".format(username))
     1385 + elif "Unsupported administration command" in str(e):
     1386 + logger.error("[!] Can't delete user.")
     1387 + else:
     1388 + logger.error(str(e))
     1389 + 
     1390 + 
     1391 +# Add user access role for database
     1392 +def add_db_access_role(service, username, dbname):
     1393 + try:
     1394 + system = service.system_graph
     1395 + system.run(statement_db_access.format(**{"username": username, "database": dbname}))
     1396 + logger.info("[+] Added database access role: user {0} database {1}.".format(username, dbname))
     1397 + except ClientError as e:
     1398 + if "Role does not exist" in str(e):
     1399 + logger.error("[!] User does not exist {0}.".format(username))
     1400 + elif "Unsupported administration command" in str(e):
     1401 + logger.error("[!] Can't delete user.")
     1402 + else:
     1403 + logger.error(str(e))
     1404 + 
     1405 + 
     1406 +# Delete user access role for database
     1407 +def delete_db_access_role(service, username, dbname):
     1408 + print("test")
     1409 + try:
     1410 + system = service.system_graph
     1411 + system.run(statement_role_revole.format(**{"database": dbname, "username": username}))
     1412 + logger.info("[+] Deleted database access role: user {0} database {1}.".format(username, dbname))
     1413 + except ClientError as e:
     1414 + if "Role does not exist" in str(e):
     1415 + logger.error("[!] User does not exist {0}.".format(username))
     1416 + elif "Unsupported administration command" in str(e):
     1417 + logger.error("[!] Can't delete user.")
     1418 + else:
     1419 + logger.error(str(e))
     1420 + 
     1421 + 
     1422 +# git clone or pull from url
     1423 +def git_clone_pull(url, download_path):
     1424 + if os.path.exists(download_path):
     1425 + try:
     1426 + repo = git.Repo(download_path)
     1427 + o = repo.remotes.origin
     1428 + o.pull()
     1429 + logger.info("[+] git pull {0} repository.".format(download_path))
     1430 + except:
     1431 + logger.error("[!] Can't pull {0} repository.".format(download_path))
     1432 + else:
     1433 + try:
     1434 + git.Repo.clone_from(url, download_path)
     1435 + logger.info("[+] git clone {0} to {1}.".format(url, download_path))
     1436 + except:
     1437 + logger.error("[!] Can't clone git repository {0}.".format(url))
     1438 + 
     1439 + 
     1440 +# Load sigma rules
     1441 +def load_sigma(download_path):
     1442 + sigma_status = ["stable", "test", None]
     1443 + sigma_rules = []
     1444 + eventids = []
     1445 + 
     1446 + config = SigmaConfiguration()
     1447 + 
     1448 + if os.path.exists(download_path):
     1449 + logger.info("[+] Load sigma rules from {0}.".format(download_path))
     1450 + sigma_rules_files = glob.glob(download_path + '/**/*.yml', recursive=True)
     1451 + for rules_file in sigma_rules_files:
     1452 + # ignore rules
     1453 + if ".github" in rules_file or "config" in rules_file or "test" in rules_file:
     1454 + continue
     1455 + 
     1456 + with open(rules_file, "r", encoding='utf-8') as file:
     1457 + try:
     1458 + parser = SigmaParser(yaml.safe_load(file), config)
     1459 + except:
     1460 + logger.info("[+] Can't load sigma rule file {0}.".format(rules_file))
     1461 + continue
     1462 + 
     1463 + if not 'product' in parser.parsedyaml["logsource"].keys() or not 'service' in parser.parsedyaml["logsource"].keys():
     1464 + continue
     1465 + 
     1466 + if "windows" in parser.parsedyaml["logsource"]["product"] and "security" in parser.parsedyaml["logsource"]["service"] and parser.parsedyaml["status"] in sigma_status:
     1467 + if not re.search("count", str(parser.parsedyaml["detection"]["condition"])):
     1468 + for parsed in parser.condparsed:
     1469 + try:
     1470 + parsed_sigma = generateQuery(parsed)[0]
     1471 + except:
     1472 + logger.info("[+] Can't parse sigma rule file {0}.".format(rules_file))
     1473 +
     1474 + for sigma_rule_path in list(load_sigma_rules(parsed_sigma)):
     1475 + #print(sigma_rule_path)
     1476 + if depth(sigma_rule_path) == 1:
     1477 + break
     1478 + else:
     1479 + continue
     1480 +
     1481 + # export event id from sigma rules
     1482 + eid = []
     1483 + if isinstance(parsed_sigma, dict):
     1484 + eid.append(parsed_sigma['EventID'])
     1485 + else:
     1486 + for d in flatten(parsed_sigma):
     1487 + if isinstance(d, dict):
     1488 + if 'EventID' in d.keys():
     1489 + eid.append(d['EventID'])
     1490 + eid_list = list(flatten(eid))
     1491 + eventids.extend(eid_list)
     1492 + sigma_rules.append([eid_list, parsed_sigma, parser.parsedyaml["title"], parser.parsedyaml["description"], parser.parsedyaml["level"]])
     1493 + else:
     1494 + logger.error("[!] Not found {0}.".format(download_path))
     1495 + 
     1496 + logger.info("[+] Loaded {0} sigma rules for security event log analysis.".format(len(sigma_rules)))
     1497 +
     1498 + return sigma_rules, set(eventids)
     1499 + 
     1500 + 
     1501 +# Sigma rule parse helpers
     1502 +def generateQuery(parsed):
     1503 + nodes = []
     1504 + 
     1505 + if type(parsed.parsedSearch) == NodeSubexpression:
     1506 + nodes.append(parsed.parsedSearch.items)
     1507 + elif isinstance(parsed.parsedSearch, tuple):
     1508 + nodes.append(parsed.parsedSearch)
     1509 + else:
     1510 + nodes.append(parsed.parsedSearch)
     1511 +
     1512 + return generateANDNode(nodes)
     1513 + 
     1514 + 
     1515 +def generateNode(node):
     1516 + if type(node) == ConditionAND:
     1517 + return generateANDNode(node)
     1518 + elif type(node) == ConditionOR:
     1519 + return generateORNode(node)
     1520 + elif type(node) == ConditionNOT:
     1521 + return generateNOTNode(node)
     1522 + elif type(node) == NodeSubexpression:
     1523 + return generateSubexpressionNode(node)
     1524 + elif type(node) == tuple:
     1525 + return dict((node,))
     1526 + else:
     1527 + raise TypeError("Node type %s was not expected in Sigma parse tree" % (str(type(node))))
     1528 + 
     1529 + 
     1530 +def generateANDNode(node):
     1531 + if type(node) == ConditionAND:
     1532 + return ["AND", [generateNode(val) for val in node]]
     1533 + else:
     1534 + return [generateNode(val) for val in node]
     1535 + 
     1536 + 
     1537 +def generateORNode(node):
     1538 + return ["OR", [generateNode(val) for val in node]]
     1539 + 
     1540 + 
     1541 +def generateNOTNode(node):
     1542 + return ["NOT", generateNode(node.item)]
     1543 + 
     1544 + 
     1545 +def generateSubexpressionNode(node):
     1546 + if type(node.items) == NodeSubexpression:
     1547 + return [check_condition(node), dict(list(flatten(node.items)))]
     1548 + else:
     1549 + return generateNode(node.items)
     1550 + 
     1551 + 
     1552 +def check_condition(parsed):
     1553 + if isinstance(parsed.items, ConditionOR):
     1554 + return "OR"
     1555 + elif isinstance(parsed.items, ConditionAND):
     1556 + return "AND"
     1557 + elif isinstance(parsed, ConditionNOT):
     1558 + return "NOT"
     1559 + else:
     1560 + return None
     1561 + 
     1562 + 
     1563 +# Sigma compare helpers
     1564 +def load_sigma_rules(node):
     1565 + val, *children = node
     1566 + if any(children):
     1567 + for child in children:
     1568 + if isinstance(child, dict):
     1569 + yield [val, child]
     1570 + else:
     1571 + for path in load_sigma_rules(child):
     1572 + yield [val] + path
     1573 + else:
     1574 + yield [val]
     1575 + 
     1576 + 
     1577 +def sigma_search(sigma_filter, event_data):
     1578 + sigma_hit = 1
     1579 + for sigma_key, sigma_text in sigma_filter.items():
     1580 + for data in event_data:
     1581 + if data.get("Name") in sigma_key and data.text is not None:
     1582 + if type(sigma_text) is list and sigma_hit >= 1:
     1583 + for data_field in sigma_text:
     1584 + if re.fullmatch(reescape(data_field), data.text):
     1585 + sigma_hit = 2
     1586 + break
     1587 + else:
     1588 + sigma_hit = 0
     1589 + elif sigma_hit >= 1:
     1590 + if re.fullmatch(reescape(sigma_text), data.text):
     1591 + sigma_hit = 2
     1592 + else:
     1593 + sigma_hit = 0
     1594 + break
     1595 + if sigma_hit == 0:
     1596 + break
     1597 + return sigma_hit
     1598 + 
     1599 + 
     1600 +# Helpers
     1601 +def flatten(l):
     1602 + for i in l:
     1603 + if type(i) == list:
     1604 + yield from flatten(i)
     1605 + else:
     1606 + yield i
     1607 + 
     1608 +def reescape(data):
     1609 + return str(data).replace('*', '.*').replace('\\', '\\\\' ).replace('$', '\\$' )
     1610 + 
     1611 +def depth(k):
     1612 + if not k:
     1613 + return 0
     1614 + else:
     1615 + if isinstance(k, list):
     1616 + return 1 + max(depth(i) for i in k)
     1617 + else:
     1618 + return 0
     1619 + 
     1620 + 
    704 1621  # Parse the EVTX file
    705  -def parse_evtx(evtx_list):
    706  - cache_dir = os.path.join(FPATH, 'cache')
     1622 +def parse_evtx(evtx_list, case):
     1623 + cache_dir = os.path.join(FPATH, 'cache', case)
     1624 + 
     1625 + # Download sigma rules from github
     1626 + if args.sigma:
     1627 + git_clone_pull(SIGMA_URL, os.path.join(FPATH, 'sigma'))
     1628 + 
     1629 + # Load sigma rules
     1630 + sigma_rules, sigma_eventids = load_sigma(os.path.join(FPATH, 'sigma'))
     1631 + else:
     1632 + sigma_eventids = []
    707 1633   
    708 1634   # Load cache files
    709 1635   if args.add and os.path.exists(cache_dir) and len(os.listdir(cache_dir)):
    710  - print("[+] Load cashe files.")
     1636 + logger.info("[+] Load cashe files.")
    711 1637   event_set = pd.read_pickle(os.path.join(cache_dir, "event_set.pkl"))
    712 1638   count_set = pd.read_pickle(os.path.join(cache_dir, "count_set.pkl"))
    713 1639   ml_frame = pd.read_pickle(os.path.join(cache_dir, "ml_frame.pkl"))
    skipped 40 lines
    754 1680   ntmlauth = []
    755 1681   deletelog = []
    756 1682   policylist = []
     1683 + sigma_results = []
    757 1684   addusers = {}
    758 1685   delusers = {}
    759 1686   addgroups = {}
    skipped 11 lines
    771 1698   record_sum = 0
    772 1699   
    773 1700   if os.path.exists(cache_dir) is False:
    774  - os.mkdir(cache_dir)
    775  - print("[+] make cache folder {0}.".format(cache_dir))
     1701 + os.makedirs(cache_dir)
     1702 + logger.info("[+] make cache folder {0}.".format(cache_dir))
    776 1703   
    777 1704   if args.timezone:
    778 1705   try:
    779 1706   datetime.timezone(datetime.timedelta(hours=args.timezone))
    780 1707   tzone = args.timezone
    781  - print("[+] Time zone is {0}.".format(args.timezone))
     1708 + logger.info("[+] Time zone is {0}.".format(args.timezone))
    782 1709   except:
    783  - sys.exit("[!] Can't load time zone {0}.".format(args.timezone))
     1710 + logger.error("[!] Can't load time zone {0}.".format(args.timezone))
     1711 + sys.exit(1)
    784 1712   else:
    785 1713   tzone = 0
    786 1714   
    787 1715   if args.fromdate:
    788 1716   try:
    789 1717   fdatetime = datetime.datetime.strptime(args.fromdate, "%Y-%m-%dT%H:%M:%S")
    790  - print("[+] Parse the EVTX from {0}.".format(fdatetime.strftime("%Y-%m-%d %H:%M:%S")))
     1718 + logger.info("[+] Parse the EVTX from {0}.".format(fdatetime.strftime("%Y-%m-%d %H:%M:%S")))
    791 1719   except:
    792  - sys.exit("[!] From date does not match format '%Y-%m-%dT%H:%M:%S'.")
     1720 + logger.error("[!] From date does not match format '%Y-%m-%dT%H:%M:%S'.")
     1721 + sys.exit(1)
    793 1722   
    794 1723   if args.todate:
    795 1724   try:
    796 1725   tdatetime = datetime.datetime.strptime(args.todate, "%Y-%m-%dT%H:%M:%S")
    797  - print("[+] Parse the EVTX from {0}.".format(tdatetime.strftime("%Y-%m-%d %H:%M:%S")))
     1726 + logger.info("[+] Parse the EVTX from {0}.".format(tdatetime.strftime("%Y-%m-%d %H:%M:%S")))
    798 1727   except:
    799  - sys.exit("[!] To date does not match format '%Y-%m-%dT%H:%M:%S'.")
     1728 + logger.error("[!] To date does not match format '%Y-%m-%dT%H:%M:%S'.")
     1729 + sys.exit(1)
    800 1730   
    801 1731   for evtx_file in evtx_list:
    802 1732   if args.evtx:
    803 1733   with open(evtx_file, "rb") as fb:
    804 1734   fb_data = fb.read(8)
    805 1735   if fb_data != EVTX_HEADER:
    806  - sys.exit("[!] This file is not EVTX format {0}.".format(evtx_file))
     1736 + logger.error("[!] This file is not EVTX format {0}.".format(evtx_file))
     1737 + sys.exit(1)
    807 1738   
    808 1739   with open(evtx_file, "rb") as evtx:
    809 1740   parser = PyEvtxParser(evtx)
    skipped 4 lines
    814 1745   with open(evtx_file, "r", encoding="utf8", errors="ignore") as fb:
    815 1746   fb_header = fb.read(6)
    816 1747   if "<?xml" not in fb_header:
    817  - sys.exit("[!] This file is not XML format {0}.".format(evtx_file))
     1748 + logger.error("[!] This file is not XML format {0}.".format(evtx_file))
     1749 + sys.exit(1)
    818 1750   for line in fb:
    819 1751   record_sum += line.count("<System>")
    820 1752   
    821  - print("[+] Last record number is {0}.".format(record_sum))
     1753 + logger.info("[+] Last record number is {0}.".format(record_sum))
    822 1754   
    823 1755   # Parse Event log
    824  - print("[+] Start parsing the EVTX file.")
     1756 + logger.info("[+] Start parsing the EVTX file.")
    825 1757   
    826 1758   for evtx_file in evtx_list:
    827  - print("[+] Parse the EVTX file {0}.".format(evtx_file))
     1759 + logger.info("[+] Parse the EVTX file {0}.".format(evtx_file))
    828 1760   
    829 1761   for node, err in xml_records(evtx_file):
    830 1762   if err is not None:
    skipped 5 lines
    836 1768   sys.stdout.write("\r[+] Now loading {0} records.".format(count))
    837 1769   sys.stdout.flush()
    838 1770   
    839  - if eventid in EVENT_ID:
     1771 + if eventid in EVENT_ID or eventid in sigma_eventids:
    840 1772   logtime = node.xpath("/Event/System/TimeCreated")[0].get("SystemTime")
    841 1773   etime = convert_logtime(logtime, tzone)
    842 1774   stime = datetime.datetime(*etime.timetuple()[:4])
    skipped 208 lines
    1051 1983   
    1052 1984   if authname in "NTML" and authname not in ntmlauth:
    1053 1985   ntmlauth.append(username)
     1986 + ###
     1987 + # Sigma rule detection
     1988 + ###
     1989 + if args.sigma:
     1990 + if eventid in sigma_eventids:
     1991 + for search_eid, sigma_filters, sigma_title, sigma_details, sigma_level in sigma_rules:
     1992 + if eventid in search_eid:
     1993 + sigma_hit = 1
     1994 + 
     1995 + # If the detection rule is only event id
     1996 + if isinstance(sigma_filters, dict):
     1997 + sigma_results.append([etime.strftime("%Y-%m-%d %H:%M:%S"), sigma_level, sigma_title, sigma_details, etree.tostring(node, encoding="utf-8")])
     1998 + continue
     1999 +
     2000 + for sigma_filter_list in load_sigma_rules(sigma_filters):
     2001 + for sigma_filter_list_path in sigma_filter_list:
     2002 + if isinstance(sigma_filter_list_path, dict):
     2003 + sigma_hit = sigma_search(sigma_filter_list_path, event_data)
     2004 + if sigma_hit == 0:
     2005 + break
     2006 + if sigma_hit == 0:
     2007 + break
     2008 + 
     2009 + if sigma_hit == 2:
     2010 + sigma_results.append([etime.strftime("%Y-%m-%d %H:%M:%S"), sigma_level, sigma_title, sigma_details, etree.tostring(node, encoding="utf-8")])
     2011 +
    1054 2012   ###
    1055 2013   # Detect the audit log deletion
    1056 2014   # EventID 1102: The audit log was cleared
    skipped 21 lines
    1078 2036   else:
    1079 2037   deletelog.append("-")
    1080 2038   
    1081  - print("\n[+] Load finished.")
    1082  - print("[+] Total Event log is {0}.".format(count))
     2039 + logger.info("\n[+] Load finished.")
     2040 + logger.info("[+] Total Event log is {0}.".format(count))
    1083 2041   
    1084 2042   if not username_set or not len(event_set):
    1085  - sys.exit("[!] This event log did not include logs to be visualized. Please check the details of the event log.")
     2043 + logger.error("[!] This event log did not include logs to be visualized. Please check the details of the event log.")
     2044 + sys.exit(1)
    1086 2045   else:
    1087  - print("[+] Filtered Event log is {0}.".format(len(event_set)))
     2046 + logger.info("[+] Filtered Event log is {0}.".format(len(event_set)))
    1088 2047   
    1089 2048   tohours = int((endtime - starttime).total_seconds() / 3600)
    1090 2049   
    1091 2050   # Create Event log cache files
    1092  - print("[+] Create cache files.")
     2051 + logger.info("[+] Create cache files.")
    1093 2052   pd.to_pickle(event_set, os.path.join(cache_dir, "event_set.pkl"))
    1094 2053   pd.to_pickle(count_set, os.path.join(cache_dir, "count_set.pkl"))
    1095 2054   pd.to_pickle(ml_frame, os.path.join(cache_dir, "ml_frame.pkl"))
    skipped 43 lines
    1139 2098   count_set = count_set.drop_duplicates()
    1140 2099   domain_set_uniq = list(map(list, set(map(tuple, domain_set))))
    1141 2100   
     2101 + # Create Sigma scan results file
     2102 + if args.sigma:
     2103 + logger.info("[+] {0} event logs hit the Sigma rules.".format(len(sigma_results)))
     2104 + with open(FPATH + "/static/" + SIGMA_RESULTS_FILE, 'w', newline='', encoding='utf8') as f:
     2105 + writer = csv.writer(f)
     2106 + writer.writerow(["date","sigma_level","sigma_title","sigma_details","event_log"])
     2107 + writer.writerows(sigma_results)
     2108 + logger.info("[+] Created Sigma scan results file {0}.".format(FPATH + "/static/" + SIGMA_RESULTS_FILE))
     2109 + 
    1142 2110   # Learning event logs using Hidden Markov Model
    1143 2111   if hosts:
    1144 2112   ml_frame = ml_frame.replace(hosts)
    1145 2113   ml_frame = ml_frame.sort_values(by="date")
    1146 2114   if args.learn:
    1147  - print("[+] Learning event logs using Hidden Markov Model.")
     2115 + logger.info("[+] Learning event logs using Hidden Markov Model.")
    1148 2116   learnhmm(ml_frame, username_set, datetime.datetime(*starttime.timetuple()[:3]))
    1149 2117   
    1150 2118   # Calculate ChangeFinder
    1151  - print("[+] Calculate ChangeFinder.")
     2119 + logger.info("[+] Calculate ChangeFinder.")
    1152 2120   timelines, detects, detect_cf = adetection(count_set, username_set, starttime, tohours)
    1153 2121   
    1154 2122   # Calculate Hidden Markov Model
    1155  - print("[+] Calculate Hidden Markov Model.")
     2123 + logger.info("[+] Calculate Hidden Markov Model.")
    1156 2124   detect_hmm = decodehmm(ml_frame, username_set, datetime.datetime(*starttime.timetuple()[:3]))
    1157 2125   
    1158 2126   # Calculate PageRank
    1159  - print("[+] Calculate PageRank.")
     2127 + logger.info("[+] Calculate PageRank.")
    1160 2128   ranks = pagerank(event_set, admins, detect_hmm, detect_cf, ntmlauth)
    1161 2129   
    1162 2130   # Create node
    1163  - print("[+] Creating a graph data.")
     2131 + logger.info("[+] Creating a graph data.")
    1164 2132   
    1165 2133   try:
    1166 2134   graph_http = "http://" + NEO4J_USER + ":" + NEO4J_PASSWORD + "@" + NEO4J_SERVER + ":" + NEO4J_PORT + "/db/data/"
    1167  - GRAPH = Graph(graph_http)
     2135 + GRAPH = Graph(graph_http, name=case)
    1168 2136   except:
    1169  - sys.exit("[!] Can't connect Neo4j Database.")
     2137 + logger.error("[!] Can't connect Neo4j Database.")
     2138 + sys.exit(1)
    1170 2139   
    1171 2140   if args.postes:
    1172 2141   # Parse Event log
    1173  - print("[+] Start sending the ES.")
     2142 + logger.info("[+] Start sending the ES.")
    1174 2143   
    1175 2144   # Create a new ES client
    1176 2145   if args.espassword and args.escafile:
    skipped 6 lines
    1183 2152   client = Elasticsearch(ES_SERVER)
    1184 2153   
    1185 2154   if client.indices.exists(index="logontracer-user-index") and client.indices.exists(index="logontracer-host-index") :
    1186  - print("[+] Already created index mappings to ES.")
     2155 + logger.info("[+] Already created index mappings to ES.")
    1187 2156   else:
    1188 2157   create_map(client, "logontracer-host-index")
    1189 2158   create_map(client, "logontracer-user-index")
    1190  - print("[+] Creating index mappings to ES.")
     2159 + logger.info("[+] Creating index mappings to ES.")
    1191 2160   
    1192 2161   es_timestamp = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S.%fZ')
    1193 2162   
    skipped 95 lines
    1289 2258   # for py2neo 2021.0 or earlier
    1290 2259   tx.commit()
    1291 2260   
    1292  - print("[+] Creation of a graph data finished.")
     2261 + logger.info("[+] Creation of a graph data finished.")
    1293 2262   
    1294 2263  # Parse from Elastic Search cluster
    1295 2264  # Porting by 0xThiebaut
    1296  -def parse_es():
     2265 +def parse_es(case):
    1297 2266   event_set = pd.DataFrame(index=[], columns=["eventid", "ipaddress", "username", "logintype", "status", "authname", "date"])
    1298 2267   count_set = pd.DataFrame(index=[], columns=["dates", "eventid", "username"])
    1299 2268   ml_frame = pd.DataFrame(index=[], columns=["date", "user", "host", "id"])
    skipped 24 lines
    1324 2293   try:
    1325 2294   datetime.timezone(datetime.timedelta(hours=args.timezone))
    1326 2295   tzone = args.timezone
    1327  - print("[+] Time zone is {0}.".format(args.timezone))
     2296 + logger.info("[+] Time zone is {0}.".format(args.timezone))
    1328 2297   except:
    1329  - sys.exit("[!] Can't load time zone {0}.".format(args.timezone))
     2298 + logger.error("[!] Can't load time zone {0}.".format(args.timezone))
     2299 + sys.exit(1)
     2300 + 
    1330 2301   else:
    1331 2302   tzone = 0
    1332 2303   
    1333 2304   if args.fromdate:
    1334 2305   try:
    1335 2306   fdatetime = datetime.datetime.strptime(args.fromdate, "%Y-%m-%dT%H:%M:%S")
    1336  - print("[+] Search ES from {0}.".format(fdatetime.strftime("%Y-%m-%d %H:%M:%S")))
     2307 + logger.info("[+] Search ES from {0}.".format(fdatetime.strftime("%Y-%m-%d %H:%M:%S")))
    1337 2308   except:
    1338  - sys.exit("[!] From date does not match format '%Y-%m-%dT%H:%M:%S'.")
     2309 + logger.error("[!] From date does not match format '%Y-%m-%dT%H:%M:%S'.")
     2310 + sys.exit(1)
    1339 2311   
    1340 2312   if args.todate:
    1341 2313   try:
    1342 2314   tdatetime = datetime.datetime.strptime(args.todate, "%Y-%m-%dT%H:%M:%S")
    1343  - print("[+] Search ES to {0}.".format(tdatetime.strftime("%Y-%m-%d %H:%M:%S")))
     2315 + logger.info("[+] Search ES to {0}.".format(tdatetime.strftime("%Y-%m-%d %H:%M:%S")))
    1344 2316   except:
    1345  - sys.exit("[!] To date does not match format '%Y-%m-%dT%H:%M:%S'.")
     2317 + logger.error("[!] To date does not match format '%Y-%m-%dT%H:%M:%S'.")
     2318 + sys.exit(1)
    1346 2319   # Parse Event log
    1347  - print("[+] Start searching the ES.")
     2320 + logger.info("[+] Start searching the ES.")
    1348 2321   
    1349 2322   # Create a new ES client
    1350 2323   if args.espassword and args.escafile:
    skipped 251 lines
    1602 2575   
    1603 2576   if authname in "NTML" and authname not in ntmlauth:
    1604 2577   ntmlauth.append(username)
     2578 +
    1605 2579   ###
    1606 2580   # Detect the audit log deletion
    1607 2581   # EventID 1102: The audit log was cleared
    skipped 18 lines
    1626 2600   deletelog.append("-")
    1627 2601   
    1628 2602   print("\n[+] Load finished.")
    1629  - print("[+] Total Event log is {0}.".format(count))
     2603 + logger.info("[+] Total Event log is {0}.".format(count))
    1630 2604   
    1631 2605   if not username_set or not len(event_set):
    1632  - sys.exit("[!] This event log did not include logs to be visualized. Please check the details of the event log.")
     2606 + logger.error("[!] This event log did not include logs to be visualized. Please check the details of the event log.")
     2607 + sys.exit(1)
    1633 2608   else:
    1634  - print("[+] Filtered Event log is {0}.".format(len(event_set)))
     2609 + logger.info("[+] Filtered Event log is {0}.".format(len(event_set)))
    1635 2610   
    1636 2611   tohours = int((endtime - starttime).total_seconds() / 3600)
    1637 2612   
    skipped 14 lines
    1652 2627   ml_frame = ml_frame.replace(hosts)
    1653 2628   ml_frame = ml_frame.sort_values(by="date")
    1654 2629   if args.learn:
    1655  - print("[+] Learning event logs using Hidden Markov Model.")
     2630 + logger.info("[+] Learning event logs using Hidden Markov Model.")
    1656 2631   learnhmm(ml_frame, username_set, datetime.datetime(*starttime.timetuple()[:3]))
    1657 2632   
    1658 2633   # Calculate ChangeFinder
    1659  - print("[+] Calculate ChangeFinder.")
     2634 + logger.info("[+] Calculate ChangeFinder.")
    1660 2635   timelines, detects, detect_cf = adetection(count_set, username_set, starttime, tohours)
    1661 2636   
    1662 2637   # Calculate Hidden Markov Model
    1663  - print("[+] Calculate Hidden Markov Model.")
     2638 + logger.info("[+] Calculate Hidden Markov Model.")
    1664 2639   detect_hmm = decodehmm(ml_frame, username_set, datetime.datetime(*starttime.timetuple()[:3]))
    1665 2640   
    1666 2641   # Calculate PageRank
    1667  - print("[+] Calculate PageRank.")
     2642 + logger.info("[+] Calculate PageRank.")
    1668 2643   ranks = pagerank(event_set, admins, detect_hmm, detect_cf, ntmlauth)
    1669 2644   
    1670 2645   # Create node
    1671  - print("[+] Creating a graph data.")
     2646 + logger.info("[+] Creating a graph data.")
    1672 2647   
    1673 2648   try:
    1674 2649   graph_http = "http://" + NEO4J_USER + ":" + NEO4J_PASSWORD + "@" + NEO4J_SERVER + ":" + NEO4J_PORT + "/db/data/"
    1675  - GRAPH = Graph(graph_http)
     2650 + GRAPH = Graph(graph_http, name=case)
    1676 2651   except:
    1677  - sys.exit("[!] Can't connect Neo4j Database.")
     2652 + logger.error("[!] Can't connect Neo4j Database.")
     2653 + sys.exit(1)
    1678 2654   
    1679 2655   if args.postes:
    1680 2656   # Parse Event log
    1681  - print("[+] Start sending the ES.")
     2657 + logger.info("[+] Start sending the ES.")
    1682 2658   
    1683 2659   if client.indices.exists(index="logontracer-user-index") and client.indices.exists(index="logontracer-host-index") :
    1684  - print("[+] Already created index mappings to ES.")
     2660 + logger.info("[+] Already created index mappings to ES.")
    1685 2661   else:
    1686 2662   create_map(client, "logontracer-host-index")
    1687 2663   create_map(client, "logontracer-user-index")
    1688  - print("[+] Creating index mappings to ES.")
     2664 + logger.info("[+] Creating index mappings to ES.")
    1689 2665   
    1690 2666   es_timestamp = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S.%fZ')
    1691 2667   
    skipped 95 lines
    1787 2763   # for py2neo 2021.0 or earlier
    1788 2764   tx.commit()
    1789 2765   
    1790  - print("[+] Creation of a graph data finished.")
     2766 + logger.info("[+] Creation of a graph data finished.")
    1791 2767   
    1792 2768  def main():
    1793 2769   if not has_py2neo:
    1794  - sys.exit("[!] py2neo must be installed for this script.")
     2770 + logger.error("[!] py2neo must be installed for this script.")
     2771 + sys.exit(1)
    1795 2772   
    1796 2773   if not has_evtx:
    1797  - sys.exit("[!] evtx must be installed for this script.")
     2774 + logger.error("[!] evtx must be installed for this script.")
     2775 + sys.exit(1)
    1798 2776   
    1799 2777   if not has_lxml:
    1800  - sys.exit("[!] lxml must be installed for this script.")
     2778 + logger.error("[!] lxml must be installed for this script.")
     2779 + sys.exit(1)
    1801 2780   
    1802 2781   if not has_numpy:
    1803  - sys.exit("[!] numpy must be installed for this script.")
     2782 + logger.error("[!] numpy must be installed for this script.")
     2783 + sys.exit(1)
    1804 2784   
    1805 2785   if not has_changefinder:
    1806  - sys.exit("[!] changefinder must be installed for this script.")
     2786 + logger.error("[!] changefinder must be installed for this script.")
     2787 + sys.exit(1)
    1807 2788   
    1808 2789   if not has_pandas:
    1809  - sys.exit("[!] pandas must be installed for this script.")
     2790 + logger.error("[!] pandas must be installed for this script.")
     2791 + sys.exit(1)
    1810 2792   
    1811 2793   if not has_hmmlearn:
    1812  - sys.exit("[!] hmmlearn must be installed for this script.")
     2794 + logger.error("[!] hmmlearn must be installed for this script.")
     2795 + sys.exit(1)
    1813 2796   
    1814 2797   if not has_sklearn:
    1815  - sys.exit("[!] scikit-learn must be installed for this script.")
     2798 + logger.error("[!] scikit-learn must be installed for this script.")
     2799 + sys.exit(1)
    1816 2800   
    1817 2801   if not has_es:
    1818  - sys.exit("[!] elasticsearch-dsl must be installed for this script.")
     2802 + logger.error("[!] elasticsearch-dsl must be installed for this script.")
     2803 + sys.exit(1)
     2804 + 
     2805 + if not has_git:
     2806 + logger.error("[!] GitPython must be installed for this script.")
     2807 + sys.exit(1)
     2808 + 
     2809 + if not has_sigma:
     2810 + logger.error("[!] sigma must be installed for this script.")
     2811 + sys.exit(1)
    1819 2812  
    1820 2813   try:
    1821  - graph_http = "http://" + NEO4J_USER + ":" + NEO4J_PASSWORD + "@" + NEO4J_SERVER + ":" + NEO4J_PORT + "/db/data/"
    1822  - GRAPH = Graph(graph_http)
    1823  - db = GraphService(host=NEO4J_SERVER, user=NEO4J_USER, password=NEO4J_PASSWORD)
     2814 + service = GraphService(host=NEO4J_SERVER, user=NEO4J_USER, password=NEO4J_PASSWORD)
    1824 2815   except:
    1825  - sys.exit("[!] Can't connect Neo4j Database.")
     2816 + logger.error("[!] Can't connect Neo4j Database GraphService.")
     2817 + sys.exit(1)
    1826 2818   
    1827  - print("[+] Script start. {0}".format(datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S")))
     2819 + logger.info("[+] Script start. {0}".format(datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S")))
    1828 2820   
    1829 2821   try:
    1830  - print("[+] Neo4j Kernel version: {0}".format(db.kernel_version))
     2822 + logger.info("[+] {0}".format(service.product))
    1831 2823   except:
    1832  - print("[!] Can't get Neo4j kernel version.")
     2824 + logger.warning("[!] Can't get Neo4j kernel version.")
     2825 + 
     2826 + case = create_database(service, CASE_NAME)
     2827 + 
     2828 + if args.create_user and args.create_password:
     2829 + if args.role:
     2830 + role = args.role
     2831 + else:
     2832 + role = "reader"
     2833 + create_neo4j_user(service, args.create_user, args.create_password, role)
     2834 + 
     2835 + if args.delete_user:
     2836 + delete_neo4j_user(service, args.delete_user)
    1833 2837   
    1834 2838   if args.run:
    1835 2839   try:
    1836 2840   app.run(threaded=True, host=WEB_HOST, port=WEB_PORT)
    1837 2841   except:
    1838  - sys.exit("[!] Can't runnning web application.")
     2842 + logger.error("[!] Can't runnning web application.")
     2843 + sys.exit(1)
    1839 2844   
    1840 2845   # Delete database data
    1841 2846   if args.delete:
     2847 + try:
     2848 + graph_http = "http://" + NEO4J_USER + ":" + NEO4J_PASSWORD + "@" + NEO4J_SERVER + ":" + NEO4J_PORT + "/db/data/"
     2849 + GRAPH = Graph(graph_http, name=case)
     2850 + except:
     2851 + logger.error("[!] Can't connect Neo4j Database.")
     2852 + sys.exit(1)
     2853 + 
    1842 2854   GRAPH.delete_all()
    1843  - print("[+] Delete all nodes and relationships from this Neo4j database.")
     2855 + logger.info("[+] Delete all nodes and relationships from this Neo4j database.")
    1844 2856   
    1845  - cache_dir = os.path.join(FPATH, 'cache')
     2857 + cache_dir = os.path.join(FPATH, 'cache', case)
    1846 2858   if os.path.exists(cache_dir):
    1847 2859   shutil.rmtree(cache_dir)
    1848  - print("[+] Delete cache folder {0}.".format(cache_dir))
     2860 + logger.info("[+] Delete cache folder {0}.".format(cache_dir))
    1849 2861   
    1850 2862   if args.evtx:
    1851 2863   for evtx_file in args.evtx:
    1852 2864   if not os.path.isfile(evtx_file):
    1853  - sys.exit("[!] Can't open file {0}.".format(evtx_file))
    1854  - parse_evtx(args.evtx)
     2865 + logger.error("[!] Can't open file {0}.".format(evtx_file))
     2866 + sys.exit(1)
     2867 + parse_evtx(args.evtx, case)
    1855 2868   
    1856 2869   if args.xmls:
    1857 2870   for xml_file in args.xmls:
    1858 2871   if not os.path.isfile(xml_file):
    1859  - sys.exit("[!] Can't open file {0}.".format(xml_file))
    1860  - parse_evtx(args.xmls)
     2872 + logger.error("[!] Can't open file {0}.".format(xml_file))
     2873 + sys.exit(1)
     2874 + parse_evtx(args.xmls, case)
    1861 2875   
    1862 2876   if args.es:
    1863  - parse_es()
     2877 + parse_es(case)
    1864 2878   
    1865  - print("[+] Script end. {0}".format(datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S")))
     2879 + logger.info("[+] Script end. {0}".format(datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S")))
    1866 2880   
    1867 2881   
    1868 2882  if __name__ == "__main__":
    skipped 2 lines
  • ■ ■ ■ ■ ■
    logs/.gitkeep
     1 + 
  • ■ ■ ■ ■ ■
    requirements.txt
    skipped 7 lines
    8 8  hmmlearn>=0.2.8
    9 9  scikit-learn
    10 10  elasticsearch-dsl>=7.0.0,<8.0.0
    11  - 
     11 +pyyaml
     12 +flask-sqlalchemy
     13 +flask-login
     14 +flask_wtf
     15 +wtforms
     16 +GitPython
     17 +sigmatools
  • ■ ■ ■ ■ ■ ■
    static/css/dark-mode.css
    skipped 165 lines
    166 166   color: WHITE;
    167 167  }
    168 168   
     169 +[data-theme="dark"] .navbar-light .navbar-nav .nav-link {
     170 + color: rgba(255, 255, 255, 0.55);
     171 +}
     172 + 
    169 173  [data-theme="dark"] .dropdown-item:hover {
    170 174   color: Black;
    171 175  }
    skipped 3 lines
    175 179  }
    176 180   
    177 181  [data-theme="dark"] .bgcolorSun {
    178  - background-color: #ff5050;
     182 + background-color: #ff5050 !important;
    179 183  }
    180 184   
    181 185  [data-theme="dark"] .bgcolorSat {
    182  - background-color: #5891db;
     186 + background-color: #5891db !important;
    183 187  }
    184 188   
    185 189  [data-theme="dark"] .bgcolorDay {
    186  - background-color: #202020;
     190 + background-color: #202020 !important;
    187 191  }
    188 192   
    189 193  [data-theme="dark"] .bgcolornormal {
    190  - background-color: #4d0715;
     194 + background-color: #4d0715 !important;
    191 195  }
    192 196   
    193 197  [data-theme="dark"] .bgcolorlow {
    194  - background-color: #800b23;
     198 + background-color: #800b23 !important;
    195 199  }
    196 200   
    197 201  [data-theme="dark"] .bgcolormid {
    198  - background-color: #b31031;
     202 + background-color: #b31031 !important;
    199 203  }
    200 204   
    201 205  [data-theme="dark"] .bgcolorhigh {
    202  - background-color: #dc143c;
     206 + background-color: #dc143c !important;
    203 207  }
    204 208   
  • ■ ■ ■ ■ ■
    static/css/style.css
    skipped 44 lines
    45 45  }
    46 46   
    47 47  .bgcolorSun {
    48  - background-color: #ff7f50;
     48 + background-color: #ff7f50 !important;
    49 49  }
    50 50   
    51 51  .bgcolorSat {
    52  - background-color: #b0c4de;
     52 + background-color: #b0c4de !important;
    53 53  }
    54 54   
    55 55  .bgcolorDay {
    56  - background-color: #efefef;
     56 + background-color: #efefef !important;
    57 57  }
    58 58   
    59 59  .bgcolornormal {
    60  - background-color: #ffeaee;
     60 + background-color: #ffeaee !important;
    61 61  }
    62 62   
    63 63  .bgcolorlow {
    64  - background-color: #ffbaee;
     64 + background-color: #ffbaee !important;
    65 65  }
    66 66   
    67 67  .bgcolormid {
    68  - background-color: #ff8aee;
     68 + background-color: #ff8aee !important;
    69 69  }
    70 70   
    71 71  .bgcolorhigh {
    72  - background-color: #ff5aee;
     72 + background-color: #ff5aee !important;
     73 +}
     74 + 
     75 +.login-form {
     76 + max-width: none !important;
     77 + width: 415px;
     78 +}
     79 + 
     80 +.login-form input#username, input#password, input#password1, input#password2, input#case {
     81 + max-width: none !important;
     82 + width: 370px;
     83 + height: 2.4em;
     84 + padding: 0 16px;
     85 + border-radius: 4px;
     86 + border: none;
     87 + box-shadow: 0 0 0 1px #ccc inset;
     88 + appearance: none;
     89 + -webkit-appearance: none;
     90 + -moz-appearance: none;
     91 +}
     92 + 
     93 +.drop-hover:hover > .dropdown-menu {
     94 + display: block !important;
     95 + margin-left: 100%;
     96 + margin-top: -22%;
    73 97  }
    74 98   
  • ■ ■ ■ ■ ■ ■
    static/js/script.js
    skipped 687 lines
    688 688   var loading = document.getElementById('loading');
    689 689   loading.classList.remove('loaded');
    690 690   
    691  - var session = driver.session();
     691 + var session = driver.session({database: caseName});
    692 692   session.run(queryStr)
    693 693   .subscribe({
    694 694   onNext: function(record) {
    skipped 33 lines
    728 728  function executeQuery(queryStr, root) {
    729 729   var countStr = queryStr.replace("user, event, ip", "COUNT(event)");
    730 730   
    731  - var session = driver.session();
     731 + var session = driver.session({database: caseName});
    732 732   session.run(countStr)
    733 733   .subscribe({
    734 734   onNext: function(record) {
    skipped 34 lines
    769 769   
    770 770   queryStr1st = 'MATCH (user)-[event:Event]-(ip) WHERE event.date >= ' + date1st + ' AND event.date <= ' + (date1st + 86400) + ' RETURN user, event, ip';
    771 771   
    772  - var session = driver.session();
     772 + var session = driver.session({database: caseName});
    773 773   session.run(queryStr1st)
    774 774   .subscribe({
    775 775   onNext: function(record) {
    skipped 29 lines
    805 805   var loading = document.getElementById('loading');
    806 806   loading.classList.remove('loaded');
    807 807   
    808  - var session = driver.session();
     808 + var session = driver.session({database: caseName});
    809 809   session.run(queryStr2nd)
    810 810   .subscribe({
    811 811   onNext: function(record) {
    skipped 87 lines
    899 899   var startRunk = currentPage * 10;
    900 900   queryStr = queryStr + " SKIP " + startRunk + " LIMIT " + 10;
    901 901   
    902  - var session = driver.session();
     902 + var session = driver.session({database: caseName});
    903 903   session.run(queryStr)
    904 904   .subscribe({
    905 905   onNext: function(record) {
    skipped 32 lines
    938 938   var queryStr = 'MATCH (user:Username)-[event:Event]-(ip:IPAddress) RETURN user, ip, event';
    939 939   var events = new Array();
    940 940   
    941  - var session = driver.session();
     941 + var session = driver.session({database: caseName});
    942 942   session.run(queryStr)
    943 943   .subscribe({
    944 944   onNext: function(record) {
    skipped 55 lines
    1000 1000   var queryStr = 'MATCH (date:Date) MATCH (user:Username) RETURN date, user';
    1001 1001   var users = new Array();
    1002 1002   
    1003  - var session = driver.session();
     1003 + var session = driver.session({database: caseName});
    1004 1004   session.run(queryStr)
    1005 1005   .subscribe({
    1006 1006   onNext: function(record) {
    skipped 93 lines
    1100 1100   }
    1101 1101   }
    1102 1102   
    1103  - var session = driver.session();
     1103 + var session = driver.session({database: caseName});
    1104 1104   session.run(queryStr)
    1105 1105   .subscribe({
    1106 1106   onNext: function(record) {
    skipped 164 lines
    1271 1271   var starttime = "";
    1272 1272   var endtime = "";
    1273 1273   
    1274  - var session = driver.session();
     1274 + var session = driver.session({database: caseName});
    1275 1275   session.run(queryStr)
    1276 1276   .subscribe({
    1277 1277   onNext: function(record) {
    skipped 222 lines
    1500 1500   var queryStr = "MATCH (date:Deletetime) RETURN date";
    1501 1501   var ddata = "";
    1502 1502   
    1503  - var session = driver.session();
     1503 + var session = driver.session({database: caseName});
    1504 1504   session.run(queryStr)
    1505 1505   .subscribe({
    1506 1506   onNext: function(record) {
    skipped 42 lines
    1549 1549   var timezone = document.getElementById("utcTime").value;
    1550 1550   var logtype = document.getElementById("logType").value;
    1551 1551   var addlog = document.getElementById("add_log").checked;
     1552 + var sigmascan = document.getElementById("sigma_scan").checked;
    1552 1553   
    1553 1554   if (timezone == "Time Zone") {
    1554 1555   document.getElementById("status").innerHTML = '<div class="alert alert-danger"><strong>ERROR</strong>: Please set the time zone of the event logs.</div>';
    skipped 9 lines
    1564 1565   formData.append("timezone", timezone);
    1565 1566   formData.append("logtype", logtype);
    1566 1567   formData.append("addlog", addlog);
     1568 + formData.append("sigmascan", sigmascan);
     1569 + formData.append("casename", caseName);
    1567 1570   var xmlhttp = new XMLHttpRequest();
    1568 1571   xmlhttp.upload.addEventListener("progress", progressHandler, false);
    1569 1572   xmlhttp.addEventListener("load", completeHandler, false);
    skipped 38 lines
    1608 1611   formData.append("timezone", timezone);
    1609 1612   formData.append("es_server", es_server);
    1610 1613   formData.append("addlog", addlog);
     1614 + formData.append("casename", caseName);
    1611 1615   formData.append("addes", addes);
    1612 1616   var xmlhttp = new XMLHttpRequest();
    1613 1617   xmlhttp.upload.addEventListener("progress", progressHandlerES, false);
    skipped 141 lines
    1755 1759  function loaddate() {
    1756 1760   var queryStr = 'MATCH (date:Date) RETURN date';
    1757 1761   
    1758  - var session = driver.session();
     1762 + var session = driver.session({database: caseName});
    1759 1763   session.run(queryStr)
    1760 1764   .subscribe({
    1761 1765   onNext: function(record) {
    skipped 93 lines
  • ■ ■ ■ ■ ■ ■
    templates/addcase.html
     1 +<!DOCTYPE html>
     2 +<html lang="en">
     3 + 
     4 +<head>
     5 + <meta charset="utf-8">
     6 + <meta name="viewport" content="width=device-width, initial-scale=1">
     7 + <title>LogonTracer</title>
     8 + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">
     9 + <link rel="stylesheet" href="static/css/style.css">
     10 + <link rel="stylesheet" href="static/css/dark-mode.css">
     11 +</head>
     12 + 
     13 +<body>
     14 + <nav class="navbar navbar-expand-lg navbar-light bg-light p-1 shadow">
     15 + <a class="navbar-brand" href="/"><img class="my_svg" src="static/images/logo_top.svg" alt="top" width="190"></a>
     16 + <div class="collapse navbar-collapse">
     17 + </div>
     18 + </nav>
     19 + 
     20 + <div class="container">
     21 + <div class="row">
     22 + <div class="col-md-4 offset-md-4">
     23 + <div class="login-form bg-light mt-4 p-4">
     24 + <form action="" method="POST" class="row g-3">
     25 + {{ form.hidden_tag() }}
     26 + <h4>Add New Case</h4>
     27 + {{ messages | safe }}
     28 + 
     29 + <div class="col-12">
     30 + <label>{{ form.case.label }}</label>
     31 + {{ form.case(size=34) }}<br>
     32 + </div>
     33 + <div class="col-12">
     34 + <div class="float-end">
     35 + <button type="submit" class="btn btn-secondary">Add</button>
     36 + <a href="/" name="cancel" class="btn btn-dark">Cancel</a>
     37 + </div>
     38 + </div>
     39 + </form>
     40 + </div>
     41 + </div>
     42 + </div>
     43 + </div>
     44 + 
     45 + <!-- Bootstrap JS -->
     46 + <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js" integrity="sha384-Atwg2Pkwv9vp0ygtn1JAojH0nYbwNJLPhwyoVbhoPwBhjQPR5VtM2+xf0Uwh9KtT" crossorigin="anonymous"></script>
     47 +</body>
     48 + 
     49 +</html>
     50 + 
  • ■ ■ ■ ■ ■ ■
    templates/casemng.html
     1 +<!DOCTYPE html>
     2 +<html lang="en">
     3 + 
     4 +<head>
     5 + <meta charset="utf-8">
     6 + <meta name="viewport" content="width=device-width, initial-scale=1">
     7 + <title>LogonTracer</title>
     8 + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">
     9 + <link rel="stylesheet" href="static/css/style.css">
     10 + <link rel="stylesheet" href="static/css/dark-mode.css">
     11 + <!-- Neo4j JavaScript Driver -->
     12 + <script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/browser/neo4j-web.min.js" integrity="sha384-g97Tx/JpmmKp7i9H8mPaOaMLiH742OFCLAa+tT0ZavZS/3spwuHUirCDwiA3wFgE" crossorigin="anonymous"></script>
     13 +</head>
     14 + 
     15 +<body>
     16 + <nav class="navbar navbar-expand-lg navbar-light bg-light p-1 shadow">
     17 + <a class="navbar-brand" href="/"><img class="my_svg" src="static/images/logo_top.svg" alt="top" width="190"></a>
     18 + <div class="collapse navbar-collapse">
     19 + </div>
     20 + </nav>
     21 + 
     22 + <div class="container">
     23 + <div class="row">
     24 + <div class="col-md-4 offset-md-4">
     25 + <div class="login-form bg-light mt-4 p-4">
     26 + <form action="" method="POST" class="row g-3">
     27 + <h4>Add Access to Case</h4>
     28 + <div id="currentCase"></div>
     29 + {{ messages | safe }}
     30 + <div class="col-12">
     31 + <label>Case</label>
     32 + <div id="caseSelect"></div>
     33 + </div>
     34 + <div class="col-12">
     35 + <label>Access User</label>
     36 + <div id="userSelect"></div>
     37 + </div>
     38 + <div class="col-12">
     39 + <div class="float-end">
     40 + <button type="submit" class="btn btn-secondary" name="action" value="add">Add User</button>
     41 + <a href="/" name="cancel" class="btn btn-dark">Cancel</a>
     42 + </div>
     43 + </div>
     44 + </form>
     45 + </div>
     46 + </div>
     47 + </div>
     48 + </div>
     49 + <script type="text/javascript">
     50 + var caseName = '{{ case_name }}';
     51 + 
     52 + if (caseName.indexOf("neo4j") != -1) {
     53 + document.getElementById("currentCase").innerHTML = '<div class="alert alert-info" role="alert">Current case: default</div>';
     54 + } else {
     55 + document.getElementById("currentCase").innerHTML = '<div class="alert alert-info" role="alert">Current case: ' + caseName + '</div>';
     56 + }
     57 + 
     58 + var users = new Array();
     59 + var neo = neo4j.default;
     60 + //Neo4j access settings
     61 + if (location.protocol == 'http:') {
     62 + var driver = neo.driver("bolt://{{ server_ip }}:{{ ws_port }}", neo.auth.basic("{{ neo4j_user }}", "{{ neo4j_password }}"));
     63 + } else if (location.protocol == 'https:') {
     64 + var driver = neo.driver("bolt+ssc://{{ server_ip }}:{{ ws_port }}", neo.auth.basic("{{ neo4j_user }}", "{{ neo4j_password }}"));
     65 + }
     66 + var session1 = driver.session({database: 'system'});
     67 + session1.run('SHOW USERS')
     68 + .subscribe({
     69 + onNext: record => {
     70 + var userdata = new Array();
     71 + users.push(record.get('user'));
     72 + },
     73 + onCompleted: () => {
     74 + session1.close()
     75 + var tableMenu = '<select class="form-select" name="userSelect">';
     76 + for (i = 0; i < users.length; i++) {
     77 + tableMenu += '<option value="' + users[i] + '">' + users[i] + '</option>';
     78 + }
     79 + tableMenu += '</select>';
     80 + document.getElementById("userSelect").innerHTML = tableMenu;
     81 + },
     82 + onError: error => {
     83 + console.log(error)
     84 + }
     85 + })
     86 + 
     87 + var cases = new Array();
     88 + var session2 = driver.session({database: 'system'});
     89 + session2.run('SHOW DATABASES')
     90 + .subscribe({
     91 + onNext: record => {
     92 + if (record.get('name').indexOf("system") == -1) {
     93 + cases.push(record.get('name'))
     94 + // console.log(record.get('name'))
     95 + }
     96 + },
     97 + onCompleted: () => {
     98 + session2.close()
     99 + var downMenu = '<select class="form-select" name="caseName"><option value="case_name">Case</option>';
     100 + for (i = 0; i < cases.length; i++) {
     101 + if (cases[i].indexOf("neo4j") != -1) {
     102 + downMenu += '<option value="' + cases[i] + '">default</option>';
     103 + } else {
     104 + downMenu += '<option value="' + cases[i] + '">' + cases[i] + '</option>';
     105 + }
     106 + }
     107 + downMenu += '</select>';
     108 + document.getElementById("caseSelect").innerHTML = downMenu;
     109 + },
     110 + onError: error => {
     111 + console.log(error)
     112 + }
     113 + })
     114 + 
     115 + </script>
     116 + <!-- Bootstrap JS -->
     117 + <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js" integrity="sha384-Atwg2Pkwv9vp0ygtn1JAojH0nYbwNJLPhwyoVbhoPwBhjQPR5VtM2+xf0Uwh9KtT" crossorigin="anonymous"></script>
     118 +</body>
     119 + 
     120 +</html>
     121 + 
  • ■ ■ ■ ■ ■ ■
    templates/changecase.html
     1 +<!DOCTYPE html>
     2 +<html lang="en">
     3 + 
     4 +<head>
     5 + <meta charset="utf-8">
     6 + <meta name="viewport" content="width=device-width, initial-scale=1">
     7 + <title>LogonTracer</title>
     8 + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">
     9 + <link rel="stylesheet" href="static/css/style.css">
     10 + <link rel="stylesheet" href="static/css/dark-mode.css">
     11 + <!-- Neo4j JavaScript Driver -->
     12 + <script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/browser/neo4j-web.min.js" integrity="sha384-g97Tx/JpmmKp7i9H8mPaOaMLiH742OFCLAa+tT0ZavZS/3spwuHUirCDwiA3wFgE" crossorigin="anonymous"></script>
     13 +</head>
     14 + 
     15 +<body>
     16 + <nav class="navbar navbar-expand-lg navbar-light bg-light p-1 shadow">
     17 + <a class="navbar-brand" href="/"><img class="my_svg" src="static/images/logo_top.svg" alt="top" width="190"></a>
     18 + <div class="collapse navbar-collapse">
     19 + </div>
     20 + </nav>
     21 + 
     22 + <div class="container">
     23 + <div class="row">
     24 + <div class="col-md-4 offset-md-4">
     25 + <div class="login-form bg-light mt-4 p-4">
     26 + <form action="" method="POST" class="row g-3">
     27 + <h4>Change Case</h4>
     28 + 
     29 + <div class="col-12">
     30 + <label>Case</label>
     31 + <div id="caseSelect"></div>
     32 + </div>
     33 + <div class="col-12">
     34 + <div class="float-end">
     35 + <button type="submit" class="btn btn-secondary">Submit</button>
     36 + <a href="/" name="cancel" class="btn btn-dark">Cancel</a>
     37 + </div>
     38 + </div>
     39 + </form>
     40 + </div>
     41 + </div>
     42 + </div>
     43 + </div>
     44 + <script type="text/javascript">
     45 + var cases = new Array();
     46 + var neo = neo4j.default;
     47 + //Neo4j access settings
     48 + if (location.protocol == 'http:') {
     49 + var driver = neo.driver("bolt://{{ server_ip }}:{{ ws_port }}", neo.auth.basic("{{ neo4j_user }}", "{{ neo4j_password }}"));
     50 + } else if (location.protocol == 'https:') {
     51 + var driver = neo.driver("bolt+ssc://{{ server_ip }}:{{ ws_port }}", neo.auth.basic("{{ neo4j_user }}", "{{ neo4j_password }}"));
     52 + }
     53 + var session = driver.session({database: 'system'});
     54 + session.run('SHOW DATABASES')
     55 + .subscribe({
     56 + onNext: record => {
     57 + if (record.get('name').indexOf("system") == -1) {
     58 + cases.push(record.get('name'))
     59 + // console.log(record.get('name'))
     60 + }
     61 + },
     62 + onCompleted: () => {
     63 + session.close()
     64 + var downMenu = '<select class="form-select" name="caseName"><option value="case_name">Case</option>';
     65 + for (i = 0; i < cases.length; i++) {
     66 + if (cases[i].indexOf("neo4j") != -1) {
     67 + downMenu += '<option value="' + cases[i] + '">default</option>';
     68 + } else {
     69 + downMenu += '<option value="' + cases[i] + '">' + cases[i] + '</option>';
     70 + }
     71 + }
     72 + downMenu += '</select>';
     73 + document.getElementById("caseSelect").innerHTML = downMenu;
     74 + },
     75 + onError: error => {
     76 + console.log(error)
     77 + }
     78 + })
     79 + </script>
     80 + <!-- Bootstrap JS -->
     81 + <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js" integrity="sha384-Atwg2Pkwv9vp0ygtn1JAojH0nYbwNJLPhwyoVbhoPwBhjQPR5VtM2+xf0Uwh9KtT" crossorigin="anonymous"></script>
     82 +</body>
     83 + 
     84 +</html>
     85 + 
  • ■ ■ ■ ■ ■ ■
    templates/delcase.html
     1 +<!DOCTYPE html>
     2 +<html lang="en">
     3 + 
     4 +<head>
     5 + <meta charset="utf-8">
     6 + <meta name="viewport" content="width=device-width, initial-scale=1">
     7 + <title>LogonTracer</title>
     8 + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">
     9 + <link rel="stylesheet" href="static/css/style.css">
     10 + <link rel="stylesheet" href="static/css/dark-mode.css">
     11 + <!-- Neo4j JavaScript Driver -->
     12 + <script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/browser/neo4j-web.min.js" integrity="sha384-g97Tx/JpmmKp7i9H8mPaOaMLiH742OFCLAa+tT0ZavZS/3spwuHUirCDwiA3wFgE" crossorigin="anonymous"></script>
     13 +</head>
     14 + 
     15 +<body>
     16 + <nav class="navbar navbar-expand-lg navbar-light bg-light p-1 shadow">
     17 + <a class="navbar-brand" href="/"><img class="my_svg" src="static/images/logo_top.svg" alt="top" width="190"></a>
     18 + <div class="collapse navbar-collapse">
     19 + </div>
     20 + </nav>
     21 + 
     22 + <div class="container">
     23 + <div class="row">
     24 + <div class="col-md-4 offset-md-4">
     25 + <div class="login-form bg-light mt-4 p-4">
     26 + <form action="" method="POST" class="row g-3">
     27 + <h4>Delete Case</h4>
     28 + {{ messages | safe }}
     29 + <div class="col-12">
     30 + <label>Case</label>
     31 + <div id="caseSelect"></div>
     32 + </div>
     33 + <div class="col-12">
     34 + <div class="float-end">
     35 + <button type="submit" class="btn btn-secondary" name="action" value="del">Delete</button>
     36 + <a href="/" name="cancel" class="btn btn-dark">Cancel</a>
     37 + </div>
     38 + </div>
     39 + </form>
     40 + </div>
     41 + </div>
     42 + </div>
     43 + </div>
     44 + <script type="text/javascript">
     45 + var neo = neo4j.default;
     46 + //Neo4j access settings
     47 + if (location.protocol == 'http:') {
     48 + var driver = neo.driver("bolt://{{ server_ip }}:{{ ws_port }}", neo.auth.basic("{{ neo4j_user }}", "{{ neo4j_password }}"));
     49 + } else if (location.protocol == 'https:') {
     50 + var driver = neo.driver("bolt+ssc://{{ server_ip }}:{{ ws_port }}", neo.auth.basic("{{ neo4j_user }}", "{{ neo4j_password }}"));
     51 + }
     52 + 
     53 + var cases = new Array();
     54 + var session = driver.session({database: 'system'});
     55 + session.run('SHOW DATABASES')
     56 + .subscribe({
     57 + onNext: record => {
     58 + if (record.get('name').indexOf("system") == -1) {
     59 + cases.push(record.get('name'))
     60 + // console.log(record.get('name'))
     61 + }
     62 + },
     63 + onCompleted: () => {
     64 + session.close()
     65 + var downMenu = '<select class="form-select" name="caseName"><option value="case_name">Case</option>';
     66 + for (i = 0; i < cases.length; i++) {
     67 + if (cases[i].indexOf("neo4j") != -1) {
     68 + downMenu += '<option value="' + cases[i] + '">default</option>';
     69 + } else {
     70 + downMenu += '<option value="' + cases[i] + '">' + cases[i] + '</option>';
     71 + }
     72 + }
     73 + downMenu += '</select>';
     74 + document.getElementById("caseSelect").innerHTML = downMenu;
     75 + },
     76 + onError: error => {
     77 + console.log(error)
     78 + }
     79 + })
     80 + 
     81 + </script>
     82 + <!-- Bootstrap JS -->
     83 + <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js" integrity="sha384-Atwg2Pkwv9vp0ygtn1JAojH0nYbwNJLPhwyoVbhoPwBhjQPR5VtM2+xf0Uwh9KtT" crossorigin="anonymous"></script>
     84 +</body>
     85 + 
     86 +</html>
     87 + 
  • ■ ■ ■ ■ ■ ■
    templates/delcasemng.html
     1 +<!DOCTYPE html>
     2 +<html lang="en">
     3 + 
     4 +<head>
     5 + <meta charset="utf-8">
     6 + <meta name="viewport" content="width=device-width, initial-scale=1">
     7 + <title>LogonTracer</title>
     8 + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">
     9 + <link rel="stylesheet" href="static/css/style.css">
     10 + <link rel="stylesheet" href="static/css/dark-mode.css">
     11 + <!-- Neo4j JavaScript Driver -->
     12 + <script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/browser/neo4j-web.min.js" integrity="sha384-g97Tx/JpmmKp7i9H8mPaOaMLiH742OFCLAa+tT0ZavZS/3spwuHUirCDwiA3wFgE" crossorigin="anonymous"></script>
     13 +</head>
     14 + 
     15 +<body>
     16 + <nav class="navbar navbar-expand-lg navbar-light bg-light p-1 shadow">
     17 + <a class="navbar-brand" href="/"><img class="my_svg" src="static/images/logo_top.svg" alt="top" width="190"></a>
     18 + <div class="collapse navbar-collapse">
     19 + </div>
     20 + </nav>
     21 + 
     22 + <div class="container">
     23 + <div class="row">
     24 + <div class="col-md-6 offset-md-3">
     25 + <div class="mt-8 p-8">
     26 + <form action="" method="POST">
     27 + <h4 class="text-center mt-3">Delete Access to Case</h4>
     28 + {{ messages | safe }}
     29 + <div id="table_body"></div>
     30 + <div class="float-end">
     31 + <button type="submit" class="btn btn-secondary" name="action" value="delete">Delete</button>
     32 + <a href="/" name="cancel" class="btn btn-dark">Cancel</a>
     33 + </div>
     34 + </form>
     35 + </div>
     36 + </div>
     37 + </div>
     38 + </div>
     39 + <script type="text/javascript">
     40 + var users = new Array();
     41 + var neo = neo4j.default;
     42 + //Neo4j access settings
     43 + if (location.protocol == 'http:') {
     44 + var driver = neo.driver("bolt://{{ server_ip }}:{{ ws_port }}", neo.auth.basic("{{ neo4j_user }}", "{{ neo4j_password }}"));
     45 + } else if (location.protocol == 'https:') {
     46 + var driver = neo.driver("bolt+ssc://{{ server_ip }}:{{ ws_port }}", neo.auth.basic("{{ neo4j_user }}", "{{ neo4j_password }}"));
     47 + }
     48 + var session = driver.session({database: 'system'});
     49 + session.run('SHOW USERS')
     50 + .subscribe({
     51 + onNext: record => {
     52 + var userdata = new Array();
     53 + users.push(record.get('user'));
     54 + },
     55 + onCompleted: () => {
     56 + session.close()
     57 + 
     58 + for (i = 0; i < users.length; i++) {
     59 + accessTable(users[i])
     60 + }
     61 + },
     62 + onError: error => {
     63 + console.log(error)
     64 + }
     65 + })
     66 + 
     67 + var caseAccess = new Array();
     68 + function accessTable(user) {
     69 + var session = driver.session({database: 'system'});
     70 + session.run('SHOW USER ' + user + ' PRIVILEGES')
     71 + .subscribe({
     72 + onNext: record => {
     73 + var cdata = new Array();
     74 + if (record.get('graph').indexOf("*") == -1 && record.get('graph').indexOf("DEFAULT") == -1 ) {
     75 + cdata.push(record.get('user'))
     76 + cdata.push(record.get('graph'))
     77 + caseAccess.push(cdata)
     78 + }
     79 + },
     80 + onCompleted: () => {
     81 + session.close()
     82 + var tableMenu = '<table class="table table-hover"><thead><tr><th scope="col"></th><th scope="col">User</th><th scope="col">Access Case</th></tr></thead><tbody>';
     83 + for (i = 0; i < caseAccess.length; i++) {
     84 + tableMenu += '<th><div class="form-check"><input class="form-check-input" type="checkbox" name="userlist" value="' + caseAccess[i][0] + '_' + caseAccess[i][1] + '"></div></th>';
     85 + tableMenu += '<td>' + caseAccess[i][0] + '</td>';
     86 + if (caseAccess[i][1].indexOf("neo4j") == -1) {
     87 + tableMenu += '<td>' + caseAccess[i][1] + '</td></tr>';
     88 + } else {
     89 + tableMenu += '<td>default</td></tr>';
     90 + }
     91 + }
     92 + tableMenu += '</tbody></table>';
     93 + document.getElementById("table_body").innerHTML = tableMenu;
     94 + //console.log(caseAccess)
     95 + },
     96 + onError: error => {
     97 + console.log(error)
     98 + }
     99 + })
     100 + }
     101 + 
     102 + </script>
     103 + <!-- Bootstrap JS -->
     104 + <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js" integrity="sha384-Atwg2Pkwv9vp0ygtn1JAojH0nYbwNJLPhwyoVbhoPwBhjQPR5VtM2+xf0Uwh9KtT" crossorigin="anonymous"></script>
     105 +</body>
     106 + 
     107 +</html>
     108 + 
  • ■ ■ ■ ■ ■
    templates/index.html
    skipped 4 lines
    5 5   <meta charset="utf-8">
    6 6   <title>LogonTracer</title>
    7 7   <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">
    8  - <link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/qtip2/2.2.1/jquery.qtip.css" integrity="sha384-EG4MkHYaMXjB6f2q1t0Jfs+W6DpGsGZls4D6PYHr9yhXwZf27Z10ReappeV2ZXcU" crossorigin="anonymous">
     8 + <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/qtip2/2.2.1/jquery.qtip.css" integrity="sha384-EG4MkHYaMXjB6f2q1t0Jfs+W6DpGsGZls4D6PYHr9yhXwZf27Z10ReappeV2ZXcU" crossorigin="anonymous">
    9 9   <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/gitbrent/[email protected]/css/bootstrap4-toggle.min.css" integrity="sha384-yakM86Cz9KJ6CeFVbopALOEQGGvyBFdmA4oHMiYuHcd9L59pLkCEFSlr6M9m434E" crossorigin="anonymous">
    10 10   <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" integrity="sha384-CmLV3WR+cw/TcN50vJSYAs2EAzhDD77tQvGcmoZ1KEzxtpl2K5xkrpFz9N2H9ClN" crossorigin="anonymous">
    11 11   <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tempusdominus-bootstrap-4/5.0.1/css/tempusdominus-bootstrap-4.min.css" integrity="sha384-8wYGNo4TwC9xzqNRdt7OUN789eBPzNQlO/sxIKaJR1gkX0+Ok1kXxhHR4pZU+gP2" crossorigin="anonymous">
    skipped 20 lines
    32 32   <a class="navbar-brand" href="#"><img class="my_svg" src="static/images/logo_top.svg" alt="top" width="190"></a>
    33 33   <div class="collapse navbar-collapse">
    34 34   <form class="navbar-nav" role="search">
     35 + <div id="caseStatus" class="mt-2 h-25"></div>
    35 36   <div id="itemForm">
    36  - <div class="row mt-2" style="--bs-gutter-x: 0.5rem;">
     37 + <div class="row ms-1 mt-2" style="--bs-gutter-x: 0.5rem;">
    37 38   <div class="col-auto">
    38 39   <select class="form-select" id="InputSelect">
    39 40   <option value="Username">Username</option>
    skipped 24 lines
    64 65   <a class="dropdown-item" download="image.jpeg" id="export-jpeg" onclick="exportJPEG()" href="#">JPEG</a>
    65 66   </div>
    66 67   </div>
    67  - <div class="form-check form-switch ms-4 mt-3">
     68 + <div class="form-check form-switch ms-5 mt-3">
    68 69   <input class="form-check-input" type="checkbox" id="darkSwitch">
    69 70   <label class="form-check-label" for="darkSwitch">Dark Mode</label>
    70 71   </div>
    71 72   
     73 + <li class="nav-item dropdown ms-5 mt-2 h-25" style="margin-left:auto">
     74 + <a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" role="button" data-bs-toggle="dropdown" aria-expanded="false">
     75 + Settings
     76 + </a>
     77 + <ul class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink">
     78 + <li><h6 class="dropdown-header">User Menu</h6></li>
     79 + <li><a class="dropdown-item" href="setting">Change Password</a></li>
     80 + <li><a class="dropdown-item" href="changecase">Change Case</a></li>
     81 + <div class="dropdown-divider"></div>
     82 + <li><h6 class="dropdown-header">Admin Menu</h6></li>
     83 + <li class="dropend drop-hover">
     84 + <a class="dropdown-item dropdown-toggle" href="#">User Management</a>
     85 + <ul class="dropdown-menu">
     86 + <li><a class="dropdown-item" href="signup">Add User</a></li>
     87 + <li><a class="dropdown-item" href="usermng">Delete User</a></li>
     88 + </ul>
     89 + </li>
     90 + <li class="dropend drop-hover">
     91 + <a class="dropdown-item dropdown-toggle" href="#">Case Management</a>
     92 + <ul class="dropdown-menu">
     93 + <li><a class="dropdown-item" href="addcase">Add New Case</a></li>
     94 + <li><a class="dropdown-item" href="delcase">Delete Case</a></li>
     95 + <li><a class="dropdown-item" href="casemng">Add Access to Case</a></li>
     96 + <li><a class="dropdown-item" href="delcasemng">Delete Access to Case</a></li>
     97 + </ul>
     98 + </li>
     99 + <div class="dropdown-divider"></div>
     100 + <li><h6 class="dropdown-header">User: {{ neo4j_user }}</h6></li>
     101 + <li><a class="dropdown-item" href="logout">Logout</a></li>
     102 + </ul>
     103 + </li>
     104 + 
    72 105   <script src="static/js/dark-mode-switch.min.js"></script>
    73 106   </form>
    74 107   </div>
    skipped 18 lines
    93 126   <button type="button" class="list-group-item list-group-item-action list-group-item-light" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Visualizing changed audit policy." onclick="policyQuery()">Audit Policy Change</button>
    94 127   <button type="button" class="list-group-item list-group-item-action list-group-item-light" data-bs-toggle="modal" data-bs-target="#Diff">Diff Graph</button>
    95 128   <button type="button" class="list-group-item list-group-item-action list-group-item-light" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Displays hourly event log counts in time series." onclick="window.open('timeline')">Create Timeline</button>
     129 + <a type="button" class="list-group-item list-group-item-action list-group-item-light" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Sigma rule scan results." href="/sigma" download="sigma.csv">Sigma Scan</a>
    96 130   </div>
    97 131   <hr>
    98 132   <a data-bs-toggle="tooltip" data-bs-placement="bottom" title="Add value to edges of visualization graph.">Add event value</a><br>
    skipped 79 lines
    178 212   <input class="form-check-input" type="checkbox" value="" id="add_log">
    179 213   <label class="form-check-label" for="add_log" data-bs-toggle="tooltip" data-bs-placement="bottom" title="If you want to add more logs, please enable the checkbox.">
    180 214   Add additional EVTX or XML files
     215 + </label>
     216 + </div>
     217 + <div class="form-check mt-3">
     218 + <input class="form-check-input" type="checkbox" value="" id="sigma_scan">
     219 + <label class="form-check-label" for="sigma_scan" data-bs-toggle="tooltip" data-bs-placement="bottom" title="If you want to scan using sigma rules, please enable the checkbox.">
     220 + Run scan using Sigma rules
    181 221   </label>
    182 222   </div>
    183 223   <div id="uploadBar"></div>
    skipped 207 lines
    391 431   <script type="text/javascript">
    392 432   var neo = neo4j.default;
    393 433   //Neo4j access settings
    394  - var driver = neo.driver("bolt://{{ server_ip }}:{{ ws_port }}", neo.auth.basic("{{ neo4j_user }}", "{{ neo4j_password }}"));
    395  - var session = driver.session();
     434 + if (location.protocol == 'http:') {
     435 + var driver = neo.driver("bolt://{{ server_ip }}:{{ ws_port }}", neo.auth.basic("{{ neo4j_user }}", "{{ neo4j_password }}"));
     436 + } else if (location.protocol == 'https:') {
     437 + var driver = neo.driver("bolt+ssc://{{ server_ip }}:{{ ws_port }}", neo.auth.basic("{{ neo4j_user }}", "{{ neo4j_password }}"));
     438 + }
     439 + var caseName = '{{ case_name }}';
     440 + var session = driver.session({database: caseName});
    396 441   var cy = cytoscape();
    397 442   var rankpageUser = 0
    398 443   var rankpageHost = 0
    skipped 11 lines
    410 455   var currentNumber = 0;
    411 456   var ItemField = {
    412 457   currentNumber: 0,
    413  - itemTemplate: '<div class="row mt-1" style="--bs-gutter-x: 0.5rem;">\
     458 + itemTemplate: '<div class="row ms-1 mt-1" style="--bs-gutter-x: 0.5rem;">\
    414 459   <div class="col-auto">\
    415 460   <select class="form-select" id="InputSelect_count_">\
    416 461   <option value="Username">Username</option>\
    skipped 44 lines
    461 506   }
    462 507   downMenuES += '</select></div>';
    463 508   document.getElementById("zoneTimeES").innerHTML = downMenuES;
     509 + 
     510 + if (caseName.indexOf("neo4j") != -1) {
     511 + document.getElementById("caseStatus").innerHTML = '<button type="button" class="btn btn-secondary" disabled>case: default</button>';
     512 + } else {
     513 + document.getElementById("caseStatus").innerHTML = '<button type="button" class="btn btn-secondary" disabled>case: ' + caseName + '</button>';
     514 + }
    464 515   
    465 516   $('input[id=lefile]').change(function() {
    466 517   var inFile = "";
    skipped 21 lines
  • ■ ■ ■ ■ ■ ■
    templates/login.html
     1 +<!DOCTYPE html>
     2 +<html lang="en">
     3 + 
     4 +<head>
     5 + <meta charset="utf-8">
     6 + <meta name="viewport" content="width=device-width, initial-scale=1">
     7 + <title>LogonTracer</title>
     8 + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">
     9 + <link rel="stylesheet" href="static/css/style.css">
     10 + <link rel="stylesheet" href="static/css/dark-mode.css">
     11 +</head>
     12 + 
     13 +<body>
     14 + 
     15 + <div class="container">
     16 + <div class="row">
     17 + <div class="col-md-4 offset-md-4">
     18 + <div class="login-form bg-light mt-4 p-4">
     19 + <form action="" method="POST" class="row g-3">
     20 + {{ form.hidden_tag() }}
     21 + <p style="text-align: center;">
     22 + <img src="static/images/logo_top.svg" alt="top" width="300">
     23 + </p>
     24 + {{ messages | safe }}
     25 + 
     26 + <div class="col-12">
     27 + <label>{{ form.username.label }}</label>
     28 + {{ form.username(size=34) }}<br>
     29 + </div>
     30 + <div class="col-12">
     31 + <label>{{ form.password.label }}</label>
     32 + {{ form.password(size=34) }}<br>
     33 + </div>
     34 + <div class="col-12">
     35 + <div class="form-check">
     36 + <input class="form-check-input" type="checkbox" name="remember" id="rememberMe">
     37 + <label class="form-check-label" for="rememberMe"> Remember me</label>
     38 + </div>
     39 + </div>
     40 + <div class="col-12">
     41 + <button type="submit" class="btn btn-dark float-end">Login</button>
     42 + </div>
     43 + </form>
     44 + </div>
     45 + </div>
     46 + </div>
     47 + </div>
     48 + 
     49 + <!-- Bootstrap JS -->
     50 + <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js" integrity="sha384-Atwg2Pkwv9vp0ygtn1JAojH0nYbwNJLPhwyoVbhoPwBhjQPR5VtM2+xf0Uwh9KtT" crossorigin="anonymous"></script>
     51 +</body>
     52 + 
     53 +</html>
     54 + 
  • ■ ■ ■ ■ ■ ■
    templates/setting.html
     1 +<!DOCTYPE html>
     2 +<html lang="en">
     3 + 
     4 +<head>
     5 + <meta charset="utf-8">
     6 + <meta name="viewport" content="width=device-width, initial-scale=1">
     7 + <title>LogonTracer</title>
     8 + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">
     9 + <link rel="stylesheet" href="static/css/style.css">
     10 + <link rel="stylesheet" href="static/css/dark-mode.css">
     11 +</head>
     12 + 
     13 +<body>
     14 + <nav class="navbar navbar-expand-lg navbar-light bg-light p-1 shadow">
     15 + <a class="navbar-brand" href="/"><img class="my_svg" src="static/images/logo_top.svg" alt="top" width="190"></a>
     16 + <div class="collapse navbar-collapse">
     17 + </div>
     18 + </nav>
     19 + 
     20 + <div class="container">
     21 + <div class="row">
     22 + <div class="col-md-4 offset-md-4">
     23 + <div class="login-form bg-light mt-4 p-4">
     24 + <form action="" method="POST" class="row g-3">
     25 + {{ form.hidden_tag() }}
     26 + <h4>Change Password</h4>
     27 + {% for error in form.password1.errors %}
     28 + <div class="alert alert-danger" role="alert">{{ error }}</div>
     29 + {% endfor %}
     30 + <div class="col-12">
     31 + <label>{{ form.password1.label }}</label>
     32 + {{ form.password1(size=34) }}<br>
     33 + </div>
     34 + <div class="col-12">
     35 + <label>{{ form.password2.label }}</label>
     36 + {{ form.password2(size=34) }}<br>
     37 + </div>
     38 + <div class="col-12">
     39 + <div class="float-end">
     40 + <button type="submit" class="btn btn-secondary">Submit</button>
     41 + <a href="/" name="cancel" class="btn btn-dark">Cancel</a>
     42 + </div>
     43 + </div>
     44 + </form>
     45 + </div>
     46 + </div>
     47 + </div>
     48 + </div>
     49 + 
     50 + <!-- Bootstrap JS -->
     51 + <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js" integrity="sha384-Atwg2Pkwv9vp0ygtn1JAojH0nYbwNJLPhwyoVbhoPwBhjQPR5VtM2+xf0Uwh9KtT" crossorigin="anonymous"></script>
     52 +</body>
     53 + 
     54 +</html>
     55 + 
  • ■ ■ ■ ■ ■ ■
    templates/signup.html
     1 +<!DOCTYPE html>
     2 +<html lang="en">
     3 + 
     4 +<head>
     5 + <meta charset="utf-8">
     6 + <meta name="viewport" content="width=device-width, initial-scale=1">
     7 + <title>LogonTracer</title>
     8 + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">
     9 + <link rel="stylesheet" href="static/css/style.css">
     10 + <link rel="stylesheet" href="static/css/dark-mode.css">
     11 +</head>
     12 + 
     13 +<body>
     14 + <nav class="navbar navbar-expand-lg navbar-light bg-light p-1 shadow">
     15 + <a class="navbar-brand" href="/"><img class="my_svg" src="static/images/logo_top.svg" alt="top" width="190"></a>
     16 + <div class="collapse navbar-collapse">
     17 + </div>
     18 + </nav>
     19 + 
     20 + <div class="container">
     21 + <div class="row">
     22 + <div class="col-md-4 offset-md-4">
     23 + <div class="login-form bg-light mt-4 p-4">
     24 + <form action="" method="POST" class="row g-3">
     25 + {{ form.hidden_tag() }}
     26 + <h4>Add User</h4>
     27 + {% for error in form.username.errors %}
     28 + <div class="alert alert-danger" role="alert">{{ error }}</div>
     29 + {% endfor %}
     30 + {% for error in form.password1.errors %}
     31 + <div class="alert alert-danger" role="alert">{{ error }}</div>
     32 + {% endfor %}
     33 + <div class="col-12">
     34 + <label>{{ form.username.label }}</label>
     35 + {{ form.username(size=34) }}<br>
     36 + </div>
     37 + <div class="col-12">
     38 + <label>{{ form.password1.label }}</label>
     39 + {{ form.password1(size=34) }}<br>
     40 + </div>
     41 + <div class="col-12">
     42 + <label>{{ form.password2.label }}</label>
     43 + {{ form.password2(size=34) }}<br>
     44 + </div>
     45 + <div class="col-12">
     46 + <div class="form-check">
     47 + <input class="form-check-input" type="checkbox" name="admin" id="adminrole">
     48 + <label class="form-check-label" for="adminrole"> Administration role</label>
     49 + </div>
     50 + </div>
     51 + <div class="col-12">
     52 + <div class="float-end">
     53 + <button type="submit" class="btn btn-secondary">Submit</button>
     54 + <a href="/" name="cancel" class="btn btn-dark">Cancel</a>
     55 + </div>
     56 + </div>
     57 + </form>
     58 + </div>
     59 + </div>
     60 + </div>
     61 + </div>
     62 + 
     63 + <!-- Bootstrap JS -->
     64 + <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js" integrity="sha384-Atwg2Pkwv9vp0ygtn1JAojH0nYbwNJLPhwyoVbhoPwBhjQPR5VtM2+xf0Uwh9KtT" crossorigin="anonymous"></script>
     65 +</body>
     66 + 
     67 +</html>
     68 + 
  • ■ ■ ■ ■ ■ ■
    templates/timeline.html
    skipped 3 lines
    4 4  <head>
    5 5   <meta charset="utf-8">
    6 6   <title>LogonTracer</title>
    7  - <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
     7 + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">
    8 8   <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/gitbrent/[email protected]/css/bootstrap4-toggle.min.css" integrity="sha384-yakM86Cz9KJ6CeFVbopALOEQGGvyBFdmA4oHMiYuHcd9L59pLkCEFSlr6M9m434E" crossorigin="anonymous">
    9 9   <link rel="stylesheet" href="static/css/style.css">
    10 10   <link rel="stylesheet" href="static/css/dark-mode.css">
    11 11   <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script>
    12 12   <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.min.js" integrity="sha384-0saKbDOWtYAw5aP4czPUm6ByY5JojfQ9Co6wDgkuM7Zn+anp+4Rj92oGK8cbV91S" crossorigin="anonymous"></script>
    13  - <script src="https://cdnjs.cloudflare.com/ajax/libs/floatthead/2.1.2/jquery.floatThead.min.js" integrity="sha384-ASdxdxzGplF9B/P3mStsNmNS1x+OFhChbFeLziK29WOvaWkGDMspp2xw5+Vha6Ck" crossorigin="anonymous"></script>
     13 + <script src="https://cdnjs.cloudflare.com/ajax/libs/floatthead/2.2.4/jquery.floatThead.min.js" integrity="sha384-5oSb2xBk02MJqTEAHfhRuhT4EvXfHOaFRN5xkcfAZyj9BYtHBcc+HNVYjpxSNDvd" crossorigin="anonymous"></script>
    14 14   <script src="https://cdn.jsdelivr.net/gh/gitbrent/[email protected]/js/bootstrap4-toggle.min.js" integrity="sha384-Q9RsZ4GMzjlu4FFkJw4No9Hvvm958HqHmXI9nqo5Np2dA/uOVBvKVxAvlBQrDhk4" crossorigin="anonymous"></script>
    15 15   <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.15.0/umd/popper.min.js" integrity="sha384-L2pyEeut/H3mtgCBaUNw7KWzp5n9+4pDQiExs933/5QfaTh8YStYFFkOzSoXjlTb" crossorigin="anonymous"></script>
    16  - <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
     16 + <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/js/bootstrap.min.js" integrity="sha384-Atwg2Pkwv9vp0ygtn1JAojH0nYbwNJLPhwyoVbhoPwBhjQPR5VtM2+xf0Uwh9KtT" crossorigin="anonymous"></script>
    17 17   <script src="https://cdn.jsdelivr.net/npm/[email protected]/desvg.min.js" integrity="sha384-J1rV4940pYhBtFhx6SqDEMJP35rgSVSVx+44+TPf67jyXL8dsBtYMZeBCNLf/2zk" crossorigin="anonymous"></script>
    18 18   <!-- Neo4j JavaScript Driver -->
    19 19   <script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/browser/neo4j-web.min.js" integrity="sha384-g97Tx/JpmmKp7i9H8mPaOaMLiH742OFCLAa+tT0ZavZS/3spwuHUirCDwiA3wFgE" crossorigin="anonymous"></script>
    skipped 4 lines
    24 24   <nav class="navbar navbar-expand-lg navbar-light bg-light p-1 shadow">
    25 25   <a class="navbar-brand" href="#"><img class="my_svg" src="static/images/logo_timeline.svg" alt="top" width="144"></a>
    26 26   <div class="collapse navbar-collapse">
    27  - <form class="navbar-nav mr-auto" role="search">
    28  - <div class="form-group mt-3" id="itemForm">
    29  - <div class="form-inline">
    30  - <label class="sr-only" for="InputSelect">select</label>
    31  - <select class="custom-select" id="InputSelect">
    32  - <option>Username</option>
    33  - </select>
    34  - <input class="form-control my-2 my-lg-0 ml-1" type="text" value="administrator" id="query-input">
     27 + <form class="navbar-nav" role="search">
     28 + <div id="caseStatus" class="mt-2 h-25"></div>
     29 + <div id="itemForm">
     30 + <div class="row ms-1 mt-2" style="--bs-gutter-x: 0.5rem;">
     31 + <div class="col-auto">
     32 + <select class="form-select" id="InputSelect">
     33 + <option value="Username">Username</option>
     34 + </select>
     35 + </div>
     36 + <div class="col-auto">
     37 + <input class="form-control" type="text" value="administrator" id="query-input">
     38 + </div>
    35 39   </div>
    36 40   </div>
    37  - <input type="button" class="btn btn-primary ml-1 mt-3 h-25" value="+" onclick="ItemField.add();" />
    38  - <input type="button" class="btn btn-primary ml-1 mt-3 h-25 mr-1" value="-" onclick="ItemField.del();" />
    39  - <span class="mt-3">
    40  - <input type="checkbox" data-toggle="toggle" data-on="Table" data-onstyle="outline-info" data-height="38" data-off="Graph" data-offstyle="outline-primary" id="timelineTypes">
     41 + <input type="button" class="btn btn-primary ms-1 mt-2 h-25" value="+" onclick="ItemField.add();" />
     42 + <input type="button" class="btn btn-primary ms-1 mt-2 h-25" value="-" onclick="ItemField.del();" />
     43 + <span class="ms-1 mt-2">
     44 + <input type="checkbox" data-toggle="toggle" data-on="Table" data-onstyle="secondary" data-height="38" data-off="Graph" data-offstyle="outline-secondary" id="timelineTypes">
    41 45   </span>
    42  - <button type="button" class="btn btn-outline-primary ml-1 mt-3 h-25" onclick="searchTimeline()">search</button>
    43  - <button type="button" class="btn btn-outline-primary ml-1 mt-3 h-25" onclick="createAlltimeline()">all</button>
    44  - <div class="btn-group ml-1 mt-3 h-25">
     46 + <button type="button" class="btn btn-outline-primary ms-1 mt-2 h-25" onclick="searchTimeline()">search</button>
     47 + <button type="button" class="btn btn-outline-primary ms-1 mt-2 h-25" onclick="createAlltimeline()">all</button>
     48 + <div class="btn-group ms-1 mt-2 h-25">
    45 49   <button class="btn btn-outline-secondary" type="button">Download</button>
    46  - <button class="btn btn-outline-secondary dropdown-toggle dropdown-toggle-split" type="button" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
     50 + <button class="btn btn-outline-secondary dropdown-toggle dropdown-toggle-split" type="button" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
    47 51   <span class="sr-only"></span>
    48 52   </button>
    49 53   <div class="dropdown-menu" aria-labelledby="navbarDropdown">
    skipped 1 lines
    51 55   <a class="dropdown-item" onclick="downloadDetail()" href="#">Details</a>
    52 56   </div>
    53 57   </div>
    54  - <div class="custom-control custom-switch ml-4 mt-4">
    55  - <input type="checkbox" class="custom-control-input" id="darkSwitch">
    56  - <label class="custom-control-label" for="darkSwitch">Dark Mode</label>
     58 + <div class="form-check form-switch ms-5 mt-3">
     59 + <input class="form-check-input" type="checkbox" id="darkSwitch">
     60 + <label class="form-check-label" for="darkSwitch">Dark Mode</label>
    57 61   </div>
    58 62   
     63 + <li class="nav-item dropdown ms-5 mt-2 h-25" style="margin-left:auto">
     64 + <a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" role="button" data-bs-toggle="dropdown" aria-expanded="false">
     65 + Settings
     66 + </a>
     67 + <ul class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink">
     68 + <li><h6 class="dropdown-header">User Menu</h6></li>
     69 + <li><a class="dropdown-item" href="setting">Change Password</a></li>
     70 + <li><a class="dropdown-item" href="changecase_t">Change Case</a></li>
     71 + <div class="dropdown-divider"></div>
     72 + <li><h6 class="dropdown-header">Admin Menu</h6></li>
     73 + <li class="dropend drop-hover">
     74 + <a class="dropdown-item dropdown-toggle" href="#">User Management</a>
     75 + <ul class="dropdown-menu">
     76 + <li><a class="dropdown-item" href="signup">Add User</a></li>
     77 + <li><a class="dropdown-item" href="usermng">Delete User</a></li>
     78 + </ul>
     79 + </li>
     80 + <li class="dropend drop-hover">
     81 + <a class="dropdown-item dropdown-toggle" href="#">Case Management</a>
     82 + <ul class="dropdown-menu">
     83 + <li><a class="dropdown-item" href="addcase">Add New Case</a></li>
     84 + <li><a class="dropdown-item" href="delcase">Delete Case</a></li>
     85 + <li><a class="dropdown-item" href="casemng">Add Access to Case</a></li>
     86 + <li><a class="dropdown-item" href="delcasemng">Delete Access to Case</a></li>
     87 + </ul>
     88 + </li>
     89 + <div class="dropdown-divider"></div>
     90 + <li><h6 class="dropdown-header">User: {{ neo4j_user }}</h6></li>
     91 + <li><a class="dropdown-item" href="logout">Logout</a></li>
     92 + </ul>
     93 + </li>
     94 + 
     95 + 
    59 96   <script src="static/js/dark-mode-switch.min.js"></script>
    60 97   </form>
    61 98   </div>
    skipped 11 lines
    73 110   <script type="text/javascript">
    74 111   var neo = neo4j.default;
    75 112   //Neo4j access settings
    76  - var driver = neo.driver("bolt://{{ server_ip }}:{{ ws_port }}", neo.auth.basic("{{ neo4j_user }}", "{{ neo4j_password }}"));
    77  - var session = driver.session();
     113 + if (location.protocol == 'http:') {
     114 + var driver = neo.driver("bolt://{{ server_ip }}:{{ ws_port }}", neo.auth.basic("{{ neo4j_user }}", "{{ neo4j_password }}"));
     115 + } else if (location.protocol == 'https:') {
     116 + var driver = neo.driver("bolt+ssc://{{ server_ip }}:{{ ws_port }}", neo.auth.basic("{{ neo4j_user }}", "{{ neo4j_password }}"));
     117 + }
     118 + var caseName = '{{ case_name }}';
     119 + var session = driver.session({database: caseName});
    78 120   //createAlltimeline();
    79 121   
    80 122   var currentNumber = 0;
    81 123   var ItemField = {
    82 124   currentNumber: 0,
    83  - itemTemplate: '<div class="form-inline my-5 my-lg-0"><label class="sr-only" for="InputSelect">select</label>\
    84  - <select class="custom-select" id="InputSelect_count_">\
    85  - <option>Username</option></select>\
    86  - <input class="form-control my-2 my-lg-0 ml-1"" type="text" id="query-input_count_"></div>',
     125 + itemTemplate: '<div class="row ms-1 mt-1" style="--bs-gutter-x: 0.5rem;">\
     126 + <div class="col-auto">\
     127 + <select class="form-select" id="InputSelect_count_">\
     128 + <option value="Username">Username</option>\
     129 + </select>\
     130 + </div>\
     131 + <div class="col-auto">\
     132 + <input class="form-control" type="text" id="query-input_count_">\
     133 + </div>',
    87 134   add: function() {
    88 135   currentNumber++;
    89 136   if (currentNumber <= 10) {
    skipped 13 lines
    103 150   field.removeChild(field.lastChild);
    104 151   currentNumber--;
    105 152   }
     153 + }
     154 + 
     155 + if (caseName.indexOf("neo4j") != -1) {
     156 + document.getElementById("caseStatus").innerHTML = '<button type="button" class="btn btn-secondary" disabled>case: default</button>';
     157 + } else {
     158 + document.getElementById("caseStatus").innerHTML = '<button type="button" class="btn btn-secondary" disabled>case: ' + caseName + '</button>';
    106 159   }
    107 160   
    108 161   $(function(){
    skipped 7 lines
  • ■ ■ ■ ■ ■ ■
    templates/usermng.html
     1 +<!DOCTYPE html>
     2 +<html lang="en">
     3 + 
     4 +<head>
     5 + <meta charset="utf-8">
     6 + <meta name="viewport" content="width=device-width, initial-scale=1">
     7 + <title>LogonTracer</title>
     8 + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">
     9 + <link rel="stylesheet" href="static/css/style.css">
     10 + <link rel="stylesheet" href="static/css/dark-mode.css">
     11 + <!-- Neo4j JavaScript Driver -->
     12 + <script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/browser/neo4j-web.min.js" integrity="sha384-g97Tx/JpmmKp7i9H8mPaOaMLiH742OFCLAa+tT0ZavZS/3spwuHUirCDwiA3wFgE" crossorigin="anonymous"></script>
     13 +</head>
     14 + 
     15 +<body>
     16 + <nav class="navbar navbar-expand-lg navbar-light bg-light p-1 shadow">
     17 + <a class="navbar-brand" href="/"><img class="my_svg" src="static/images/logo_top.svg" alt="top" width="190"></a>
     18 + <div class="collapse navbar-collapse">
     19 + </div>
     20 + </nav>
     21 + 
     22 + <div class="container">
     23 + <div class="row">
     24 + <div class="col-md-8 offset-md-2">
     25 + <div class="mt-8 p-8">
     26 + <form action="" method="POST">
     27 + <h4 class="text-center mt-3">User Management</h4>
     28 + <div id="table_body"></div>
     29 + <div class="float-end">
     30 + <button type="submit" class="btn btn-secondary" name="action" value="active">Active</button>
     31 + <button type="submit" class="btn btn-secondary" name="action" value="suspended">Suspend</button>
     32 + <button type="submit" class="btn btn-secondary" name="action" value="delete">Delete</button>
     33 + <a href="/" name="cancel" class="btn btn-dark">Cancel</a>
     34 + </div>
     35 + </form>
     36 + </div>
     37 + </div>
     38 + </div>
     39 + </div>
     40 + <script type="text/javascript">
     41 + var users = new Array();
     42 + var neo = neo4j.default;
     43 + //Neo4j access settings
     44 + if (location.protocol == 'http:') {
     45 + var driver = neo.driver("bolt://{{ server_ip }}:{{ ws_port }}", neo.auth.basic("{{ neo4j_user }}", "{{ neo4j_password }}"));
     46 + } else if (location.protocol == 'https:') {
     47 + var driver = neo.driver("bolt+ssc://{{ server_ip }}:{{ ws_port }}", neo.auth.basic("{{ neo4j_user }}", "{{ neo4j_password }}"));
     48 + }
     49 + var session = driver.session({database: 'system'});
     50 + session.run('SHOW USERS')
     51 + .subscribe({
     52 + onNext: record => {
     53 + var userdata = new Array();
     54 + userdata.push(record.get('user'));
     55 + userdata.push(record.get('roles'));
     56 + userdata.push(record.get('suspended'));
     57 + users.push(userdata);
     58 + //console.log(userdata);
     59 + },
     60 + onCompleted: () => {
     61 + session.close()
     62 + var tableMenu = '<table class="table table-hover"><thead><tr><th scope="col"></th><th scope="col">User</th><th scope="col">Role</th><th scope="col">Suspended</th></tr></thead><tbody>';
     63 + for (i = 0; i < users.length; i++) {
     64 + tableMenu += '<th><div class="form-check"><input class="form-check-input" type="checkbox" name="userlist" value="Check_' + users[i][0] + '"></div></th>';
     65 + tableMenu += '<td>' + users[i][0] + '</td>';
     66 + try {
     67 + if (users[i][1].indexOf("admin") != -1) {
     68 + tableMenu += '<td>admin</td>';
     69 + } else {
     70 + tableMenu += '<td>user</td>';
     71 + }
     72 + } catch (error) {
     73 + //console.error(error);
     74 + tableMenu += '<td>admin</td>';
     75 + }
     76 + tableMenu += '<td>' + users[i][2] + '</td></tr>';
     77 + }
     78 + tableMenu += '</tbody></table>';
     79 + document.getElementById("table_body").innerHTML = tableMenu;
     80 + },
     81 + onError: error => {
     82 + console.log(error)
     83 + }
     84 + })
     85 + </script>
     86 + <!-- Bootstrap JS -->
     87 + <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js" integrity="sha384-Atwg2Pkwv9vp0ygtn1JAojH0nYbwNJLPhwyoVbhoPwBhjQPR5VtM2+xf0Uwh9KtT" crossorigin="anonymous"></script>
     88 +</body>
     89 + 
     90 +</html>
     91 + 
Please wait...
Page is in error, reload to recover