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