Projects STRLCPY LogonTracer Commits b2c2fc68
🤬
  • ■ ■ ■ ■ ■ ■
    .gitignore
     1 +static/*
     2 +!.gitkeep
     3 + 
  • ■ ■ ■ ■ ■ ■
    README.md
    1 1  # LogonTracer
    2  -Investigate malicious Windows logon by visualizing and analyzing Windows active directory event logs
     2 + Investigate malicious logon by visualizing and analyzing Windows active directory event logs.
     3 + LogonTracer uses [PageRank](https://en.wikipedia.org/wiki/PageRank) and [ChangeFinder](https://pdfs.semanticscholar.org/c5bc/7ca31914d3cdfe1b2932cbc779875e645bbb.pdf) to detect malicious hosts and accounts from event log.
     4 + This tool can visualize the following event id related to Windows logon based on [this research](https://www.first.org/resources/papers/conf2016/FIRST-2016-105.pdf).
     5 + * **4624**: Successful logon
     6 + * **4625**: Logon failure
     7 + * **4768**: Kerberos Authentication (TGT Request)
     8 + * **4769**: Kerberos Service Ticket (ST Request)
     9 + * **4776**: NTLM Authentication
     10 + * **4672**: Assign special privileges
     11 + 
     12 + ![LogonTracer sample](images/sample.png)
     13 + 
     14 +## Requirements
     15 + The following tools are used
     16 + 
     17 + * Python 3
     18 + * [Neo4j](https://neo4j.com) for a graph database.
     19 + * [Neo4j JavaScript driver](https://github.com/neo4j/neo4j-javascript-driver) for connects to Neo4j using the binary protocol.
     20 + * [Cytoscape](http://www.cytoscape.org/) for visualizing a graph network.
     21 + * [Flask](http://flask.pocoo.org/) is a microframework for Python.
     22 + 
     23 +## Usage
     24 +### Install
     25 +1. Download and install [Neo4j community edition](https://neo4j.com/download/).
     26 + 
     27 +2. Clone or download LogonTracer.
     28 + ```shell
     29 + $ git clone https://github.com/JPCERTCC/LogonTracer.git
     30 + ```
     31 + 
     32 +3. Install [Neo4j JavaScript driver](https://github.com/neo4j/neo4j-javascript-driver) to **static** directory.
     33 + ```shell
     34 + $ cd LogonTracer/static
     35 + $ npm install neo4j-driver
     36 + ```
     37 + 
     38 +4. Install Python modules.
     39 + ```shell
     40 + $ pip install -r requirements.txt
     41 + ```
     42 + or
     43 + ```shell
     44 + $ pip install numpy py2neo python-evtx lxml changefinder flask
     45 + ```
     46 + If statsmodels installation fails, install numpy first.
     47 + 
     48 +5. Start the Neo4j server.
     49 + 
     50 +### How to Use
     51 +#### Start LogonTracer
     52 + Start LogonTracer by the following command option **-r**.
     53 + Use -h to see help message.
     54 + ```shell
     55 + $ python3 logontracer.py -r -o 8080 -u neo4j -p password -s localhost
     56 + ```
     57 + Access **http://[LogonTracer_Server]:8080/** via Web browser.
     58 +#### Import EVTX
     59 + Import the event log using **Web GUI** or **logontracer.py**.
     60 +##### Use Web GUI
     61 + Event log can be imported with upload EVTX button.
     62 + ![Upload EVTX File](images/upload.png)
     63 +##### Use python script
     64 + Event log can be imported by logontracer.py option **-e**.
     65 + ```shell
     66 + $ python3 logontracer.py -e Security.evtx -z +9 -u neo4j -p password -s localhost
     67 + ```
     68 +#### Search and visualize the event log
     69 + Using the navigation bar to search for account name, host name, IP address, event id and event count.
     70 + Export button can download graph data of CSV, JPG, PNG and JSON.
     71 + ![Web GUI Navigation Bar](images/nav_bar.png)
     72 + Using the side-bar to search for account names matching specific criteria.
     73 + ![Web GUI Side Bar](images/side_bar.png)
     74 + * All users: Visualizing all users and hosts.
     75 + * SYSTEM privileges: Visualizing users with system privileges.
     76 + * RDP Logon: Visualizing RDP logon users and hosts (Logon type: 10).
     77 + * Network Logon: Visualizing logon users and hosts from remote network (Logon type: 3).
     78 + * Batch Logon: Visualizing batch server logon (Logon type: 4).
     79 + * Service Logon: Visualizing Services Control Manager logon (Logon type: 5).
     80 + * ms14-068 exploit failure: Visualizing the error log that the ms14-068 exploit failed.
     81 + * Logon failure: Visualizing users who failed to log on.
     82 + 
     83 +##### Node details
     84 + * ![Node Red](images/node_red.png) SYSTEM privileges account
     85 + * ![Node Blue](images/node_blue.png) Standard user account
     86 + * ![Node Green](images/node_green.png) IP address and host
     87 + 
     88 +#### PageRank
     89 + PageRank is an algorithm for checking the importance of web pages.
     90 + LogonTracer uses PageRank to examine the importance of accounts and hosts in a domain network.
     91 + An account with high PageRank logs on to many hosts and may be used by the attackers' lateral movement.
     92 + ![PageRank List](images/rank.png)
     93 + 
     94 +#### Timeline
     95 + Timeline button displays hourly event log counts in time series.
     96 + Hosts with drastic changes are highlighted.
     97 + For anomaly detection using this index, use change point analysis algorithm Change Finder.
     98 + For downloading timeline summary and detailed CSV data, click "Download".
     99 + ![PageRank List](images/timeline.png)
     100 + 
     101 +## Docker Image
     102 + If you are using Docker, you can pull the following image.
     103 + https://hub.docker.com/r/jpcertcc/docker-logontracer/
     104 + 
     105 + ```shell
     106 + $ docker run \
     107 + --detach \
     108 + --publish=7474:7474 --publish=7687:7687 --publish=8080:8080 \
     109 + -e LTHOSTNAME=[IP Address] \
     110 + jpcertcc/docker-logontracer
     111 + ```
     112 + 
     113 +## Notes
     114 + Event logs that LogonTracer analyzes are not recorded by default settings.
     115 + If you have not enabled the audit policy, you need to enable the audit policy.
     116 + You can change the audit policy from Local Group Policy Editor (gpedit.msc).
     117 + ```
     118 + Computer Configuration > Windows Settings > Security Settings > Advanced Audit Policy Configuration > System Audit Policies - Local Group Policy Object
     119 + ```
     120 + By enabling the following items, the event ID will be recorded.
     121 + * Account Logon
     122 + - Audit Credential Validation
     123 + - Audit Kerberos Authentication Service
     124 + - Audit Kerberos Service Ticket Operations
     125 + * Logon/Logoff
     126 + - Audit Logon
     127 + - Audit Special Logon
    3 128   
  • ■ ■ ■ ■ ■ ■
    docker/Dockerfile
     1 +FROM neo4j:3.2.3
     2 + 
     3 +RUN apk add --no-cache --virtual build-temp \
     4 + build-base \
     5 + bzip2-dev \
     6 + coreutils \
     7 + dpkg-dev dpkg \
     8 + expat-dev \
     9 + gdbm-dev \
     10 + linux-headers \
     11 + ncurses-dev \
     12 + openssl \
     13 + openssl-dev \
     14 + pax-utils \
     15 + readline-dev \
     16 + sqlite-dev \
     17 + tcl-dev \
     18 + tk \
     19 + tk-dev \
     20 + xz-dev \
     21 + gfortran \
     22 + zlib-dev \
     23 + git
     24 +RUN apk add --no-cache \
     25 + bash \
     26 + curl \
     27 + python \
     28 + py-pip \
     29 + nodejs \
     30 + nodejs-npm
     31 + 
     32 +## python3.6 and modules install
     33 +ENV PYTHON_VERSION=3.6.2 \
     34 + LANG=C.UTF-8
     35 + 
     36 +WORKDIR /usr/local/src
     37 +RUN apk add --no-cache lapack-dev\
     38 + libxml2-dev \
     39 + libxslt-dev \
     40 + && curl --fail --silent --show-error --location --output python.tgz https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}.tgz \
     41 + && tar zxf python.tgz \
     42 + && rm python.tgz \
     43 + && cd Python-${PYTHON_VERSION} \
     44 + && ./configure \
     45 + && make altinstall \
     46 + && ln -s /usr/local/bin/python3.6 /usr/local/bin/python3 \
     47 + && pip install supervisor
     48 +ENV PYTHONIOENCODING=utf-8
     49 + 
     50 +## LogonTracer install
     51 +WORKDIR /usr/local/src
     52 + 
     53 +RUN git clone https://github.com/JPCERTCC/LogonTracer.git \
     54 + && cd LogonTracer-Private \
     55 + && pip3.6 install numpy \
     56 + && pip3.6 install -r requirements.txt \
     57 + && unlink /var/lib/neo4j/data \
     58 + && mkdir -p /var/lib/neo4j/data/databases \
     59 + && tar xzf sample/graph.db.tar.gz -C /var/lib/neo4j/data/databases \
     60 + && cd static \
     61 + && npm install neo4j-driver \
     62 + && echo "dbms.allow_format_migration=true" >> /var/lib/neo4j/conf/neo4j.conf
     63 + 
     64 +## Create supervisord.conf
     65 +RUN touch /etc/supervisord.conf \
     66 + && echo "[supervisord]" >> /etc/supervisord.conf \
     67 + && echo "nodaemon=true" >> /etc/supervisord.conf \
     68 + && echo "[program:neo4j]" >> /etc/supervisord.conf \
     69 + && echo "command=/docker-entrypoint.sh neo4j" >> /etc/supervisord.conf \
     70 + && echo "[program:logontracer]" >> /etc/supervisord.conf \
     71 + && echo "command=/usr/local/src/run.sh" >> /etc/supervisord.conf \
     72 + && echo "[program:setup]" >> /etc/supervisord.conf \
     73 + && echo "command=/usr/local/src/setup.sh" >> /etc/supervisord.conf
     74 + 
     75 +## Create setup file
     76 +RUN echo "#!/bin/bash" > setup.sh \
     77 + && echo "sleep 30" >> setup.sh \
     78 + && echo "curl -H \"Content-Type: application/json\" -X POST -d '{\"password\":\"password\"}' -u neo4j:neo4j http://localhost:7474/user/neo4j/password" >> setup.sh \
     79 + && echo "rm -f /usr/local/src/setup.sh" >> setup.sh \
     80 + && chmod 755 setup.sh
     81 +RUN echo "#!/bin/bash" > run.sh \
     82 + && echo "cd /usr/local/src/LogonTracer-Private" >> run.sh \
     83 + && echo "python3 logontracer.py -r -o 8080 -u neo4j -p password -s \${LTHOSTNAME}" >> run.sh \
     84 + && chmod 755 run.sh
     85 + 
     86 +## delete build apk
     87 +WORKDIR /var/lib/neo4j
     88 +RUN apk del --purge build-temp
     89 + 
     90 +EXPOSE 8080
     91 + 
     92 +CMD ["supervisord", "-n"]
     93 + 
  • ■ ■ ■ ■ ■ ■
    docker/README.md
     1 +# docker-LogonTracer
     2 + Dockerfile for LogonTracer.
     3 + The Docker image is the following URL.
     4 + https://hub.docker.com/r/jpcertcc/docker-logontracer/
     5 + 
     6 +## Usage
     7 + ```shell
     8 + $ docker run \
     9 + --detach \
     10 + --publish=7474:7474 --publish=7687:7687 --publish=8080:8080 \
     11 + -e LTHOSTNAME=[IP Address] \
     12 + jpcertcc/docker-logontracer
     13 + ```
     14 + 
  • images/nav_bar.png
  • images/node_blue.png
  • images/node_green.png
  • images/node_red.png
  • images/rank.png
  • images/sample.png
  • images/side_bar.png
  • images/timeline.png
  • images/upload.png
  • ■ ■ ■ ■ ■ ■
    logontracer.py
     1 +#!/usr/bin/env python
     2 +# -*- coding: utf-8 -*-
     3 +#
     4 +# LICENSE
     5 +# Please refer to the LICENSE.txt in the https://github.com/JPCERTCC/aa-tools/
     6 +#
     7 + 
     8 +import os
     9 +import sys
     10 +import argparse
     11 +import itertools
     12 +import datetime
     13 +import subprocess
     14 + 
     15 +try:
     16 + from lxml import etree
     17 + has_lxml = True
     18 +except ImportError:
     19 + has_lxml = False
     20 + 
     21 +try:
     22 + from Evtx.Evtx import Evtx
     23 + from Evtx.Views import evtx_file_xml_view
     24 + has_evtx = True
     25 +except ImportError:
     26 + has_evtx = False
     27 + 
     28 +try:
     29 + from py2neo import Graph
     30 + has_py2neo = True
     31 +except ImportError:
     32 + has_py2neo = False
     33 + 
     34 +try:
     35 + import numpy as np
     36 + has_numpy = True
     37 +except ImportError:
     38 + has_numpy = False
     39 + 
     40 +try:
     41 + import changefinder
     42 + has_changefinder = True
     43 +except ImportError:
     44 + has_changefinder = False
     45 + 
     46 +try:
     47 + from flask import Flask, render_template, request
     48 + has_flask = True
     49 +except ImportError:
     50 + has_flask = False
     51 + 
     52 +# neo4j password
     53 +NEO4J_PASSWORD = "password"
     54 +# neo4j user name
     55 +NEO4J_USER = "neo4j"
     56 +# neo4j server
     57 +NEO4J_SERVER = "localhost"
     58 +# neo4j listen port
     59 +NEO4J_PORT = "7474"
     60 +# Web application port
     61 +WEB_PORT = "8080"
     62 + 
     63 +# Check Event Id
     64 +EVENT_ID = [4624, 4625, 4768, 4769, 4776]
     65 + 
     66 +# EVTX Header
     67 +EVTX_HEADER = b"\x45\x6C\x66\x46\x69\x6C\x65\x00"
     68 + 
     69 +# Flask instance
     70 +app = Flask(__name__)
     71 + 
     72 +parser = argparse.ArgumentParser(description="Visualizing and analyzing active directory Windows logon event logs.")
     73 +parser.add_argument("-r", "--run", action="store_true", default=False,
     74 + help="Start web application.")
     75 +parser.add_argument("-o", "--port", dest="port", action="store", type=str, metavar="PORT",
     76 + help="Port number to be started web application. (default: 8080).")
     77 +parser.add_argument("-s", "--server", dest="server", action="store", type=str, metavar="SERVER",
     78 + help="Neo4j server. (default: localhost)")
     79 +parser.add_argument("-u", "--user", dest="user", action="store", type=str, metavar="USERNAME",
     80 + help="Neo4j account name. (default: neo4j)")
     81 +parser.add_argument("-p", "--password", dest="password", action="store", type=str, metavar="PASSWORD",
     82 + help="Neo4j password. (default: password).")
     83 +parser.add_argument("-e", "--evtx", dest="evtx", action="store", type=str, metavar="EVTX",
     84 + help="Import to the AD EVTX file.")
     85 +parser.add_argument("-z", "--timezone", dest="timezone", action="store", type=int, metavar="UTC",
     86 + help="Event log time zone. (for example: +9) (default: GMT)")
     87 +parser.add_argument("-f", "--from", dest="fromdate", action="store", type=str, metavar="DATE",
     88 + help="Parse Security Event log from this time. (for example: 20170101000000)")
     89 +parser.add_argument("-t", "--to", dest="todate", action="store", type=str, metavar="DATE",
     90 + help="Parse Security Event log to this time. (for example: 20170228235959)")
     91 +parser.add_argument("--delete", action="store_true", default=False,
     92 + help="Delete all nodes and relationships from this Neo4j database. (default: False)")
     93 +args = parser.parse_args()
     94 + 
     95 +statement_user = """
     96 + MERGE (user:Username{ user:{user} }) set user.rights={rights}, user.sid={sid}, user.rank={rank}, user.counts={counts}, user.counts4624={counts4624}, user.counts4625={counts4625}, user.counts4768={counts4768}, user.counts4769={counts4769}, user.counts4776={counts4776}, user.detect={detect}
     97 + RETURN user
     98 + """
     99 + 
     100 +statement_ip = """
     101 + MERGE (ip:IPAddress{ IP:{IP} }) set ip.rank={rank}
     102 + RETURN ip
     103 + """
     104 + 
     105 +statement_r = """
     106 + MATCH (user:Username{ user:{user} })
     107 + MATCH (ip:IPAddress{ IP:{IP} })
     108 + CREATE (ip)-[event:Event]->(user) set event.id={id}, event.logintype={logintype}, event.status={status}, event.count={count}
     109 + 
     110 + RETURN user, ip
     111 + """
     112 + 
     113 +statement_date = """
     114 + MERGE (date:Date{ date:{Daterange} }) set date.start={start}, date.end={end}
     115 + RETURN date
     116 + """
     117 + 
     118 +if args.user:
     119 + NEO4J_USER = args.user
     120 + 
     121 +if args.password:
     122 + NEO4J_PASSWORD = args.password
     123 + 
     124 +if args.server:
     125 + NEO4J_SERVER = args.server
     126 + 
     127 +if args.port:
     128 + WEB_PORT = args.port
     129 + 
     130 +# Web application index.html
     131 +@app.route('/')
     132 +def index():
     133 + return render_template("index.html", server_ip=NEO4J_SERVER, neo4j_password=NEO4J_PASSWORD, neo4j_user=NEO4J_USER)
     134 + 
     135 + 
     136 +# Web application upload
     137 +@app.route("/upload", methods=["POST"])
     138 +def do_upload():
     139 + try:
     140 + timezone = request.form["timezone"]
     141 + file = request.files["file"]
     142 + if file and file.filename:
     143 + filename = file.filename
     144 + file.save(filename)
     145 + parse_command = "nohup python3 logontracer.py --delete -z " + timezone + " -e " + filename + " -u " + NEO4J_USER + " -p " + NEO4J_PASSWORD + " > static/logontracer.log 2>&1 &";
     146 + subprocess.call("rm -f static/logontracer.log > /dev/null", shell=True)
     147 + subprocess.call(parse_command, shell=True)
     148 + #parse_evtx(filename)
     149 + return "SUCCESS"
     150 + except:
     151 + return "FAIL"
     152 + 
     153 + 
     154 +# Calculate ChangeFinder
     155 +def adetection(counts, users, ranks, starttime, tohours):
     156 + count_array = np.zeros((5, len(users), tohours + 1))
     157 + count_all_array = []
     158 + result_array = []
     159 + for event, count in counts:
     160 + column = int((datetime.datetime.strptime(event[0], "%Y-%m-%d %H:%M:%S") - starttime).total_seconds() / 3600)
     161 + row = users.index(event[2])
     162 + #count_array[row, column, 0] = count_array[row, column, 0] + count
     163 + if event[1] == 4624:
     164 + count_array[0, row, column] = count
     165 + elif event[1] == 4625:
     166 + count_array[1, row, column] = count
     167 + elif event[1] == 4768:
     168 + count_array[2, row, column] = count
     169 + elif event[1] == 4769:
     170 + count_array[3, row, column] = count
     171 + elif event[1] == 4776:
     172 + count_array[4, row, column] = count
     173 + 
     174 + #count_average = count_array.mean(axis=0)
     175 + count_sum = np.sum(count_array, axis=0)
     176 + count_average = count_sum.mean(axis=0)
     177 + num = 0
     178 + for udata in count_sum:
     179 + cf = changefinder.ChangeFinder(r=0.04, order=1, smooth=5)
     180 + ret = []
     181 + u = ranks[users[num]]
     182 + for i in count_average:
     183 + cf.update(i * u)
     184 + 
     185 + for i in udata:
     186 + score = cf.update(i * u)
     187 + ret.append(round(score, 2))
     188 + result_array.append(ret)
     189 + 
     190 + count_all_array.append(udata.tolist())
     191 + for var in range(0, 5):
     192 + con = []
     193 + for i in range(0, tohours + 1):
     194 + con.append(count_array[var, num, i])
     195 + count_all_array.append(con)
     196 + num += 1
     197 + 
     198 + return count_all_array, result_array
     199 + 
     200 + 
     201 +# Calculate PageRank
     202 +def pagerank(event_set_uniq):
     203 + graph = {}
     204 + nodes = []
     205 + for events, count in event_set_uniq:
     206 + nodes.append(events[1])
     207 + nodes.append(events[2])
     208 + 
     209 + for node in list(set(nodes)):
     210 + links = []
     211 + for events, count in event_set_uniq:
     212 + if node in events[1]:
     213 + links.append(events[2])
     214 + if node in events[2]:
     215 + links.append(events[1])
     216 + graph[node] = links
     217 + 
     218 + d = 0.8
     219 + numloops = 10
     220 + ranks = {}
     221 + npages = len(graph)
     222 + for page in graph:
     223 + ranks[page] = 1.0 / npages
     224 + 
     225 + for i in range(0, numloops):
     226 + newranks = {}
     227 + for page in graph:
     228 + newrank = (1 - d) / npages
     229 + for node in graph:
     230 + if page in graph[node]:
     231 + newrank = newrank + d * ranks[node]/len(graph[node])
     232 + newranks[page] = newrank
     233 + ranks = newranks
     234 + 
     235 + return ranks
     236 + 
     237 + 
     238 +def to_lxml(record_xml):
     239 + record_xml = record_xml.encode("utf-8")
     240 + return etree.fromstring(record_xml)
     241 + 
     242 + 
     243 +def xml_records(filename):
     244 + with Evtx(filename) as evtx:
     245 + for xml, record in evtx_file_xml_view(evtx.get_file_header()):
     246 + try:
     247 + yield to_lxml(xml), None
     248 + except etree.XMLSyntaxError as e:
     249 + yield xml, e, fh
     250 + 
     251 + 
     252 +def get_child(node, tag, ns="{http://schemas.microsoft.com/win/2004/08/events/event}"):
     253 + return node.find("%s%s" % (ns, tag))
     254 + 
     255 + 
     256 +# Parse the EVTX file
     257 +def parse_evtx(evtx_file, GRAPH):
     258 + print("[*] Parse the EVTX file %s." % evtx_file)
     259 + with Evtx(evtx_file) as evtx:
     260 + fh = evtx.get_file_header()
     261 + nx_number = fh.next_record_number()
     262 + print("[*] Next recode number is %i." % int(nx_number))
     263 + 
     264 + if args.timezone:
     265 + try:
     266 + datetime.timezone(datetime.timedelta(hours=args.timezone))
     267 + tzone = args.timezone
     268 + print("[*] Time zone is %s." % args.timezone)
     269 + except:
     270 + sys.exit("[!] Can't load time zone '%s'." % args.timezone)
     271 + else:
     272 + tzone = 0
     273 + 
     274 + if args.fromdate:
     275 + try:
     276 + fdatetime = datetime.datetime.strptime(args.fromdate, "%Y%m%d%H%M%S")
     277 + print("[*] Parse the EVTX from %s." % fdatetime.strftime("%Y-%m-%d %H:%M:%S"))
     278 + except:
     279 + sys.exit("[!] From date does not match format '%Y%m%d%H%M%S'.")
     280 + 
     281 + if args.todate:
     282 + try:
     283 + tdatetime = datetime.datetime.strptime(args.todate, "%Y%m%d%H%M%S")
     284 + print("[*] Parse the EVTX from %s." % tdatetime.strftime("%Y-%m-%d %H:%M:%S"))
     285 + except:
     286 + sys.exit("[!] To date does not match format '%Y%m%d%H%M%S'.")
     287 + 
     288 + # Parse Event log
     289 + event_set = []
     290 + count_set = []
     291 + ipaddress_set = []
     292 + username_set = []
     293 + admins = []
     294 + sids = {}
     295 + count = 0
     296 + starttime = None
     297 + endtime = None
     298 + print("[*] Start parsing the EVTX file.")
     299 + for node, err in xml_records(evtx_file):
     300 + count += 1
     301 + if not count % 100:
     302 + sys.stdout.write("\r[*] Now loading %i records." % count)
     303 + sys.stdout.flush()
     304 + 
     305 + if err is not None:
     306 + continue
     307 + 
     308 + sysev = get_child(node, "System")
     309 + if int(get_child(sysev, "EventID").text) in EVENT_ID:
     310 + logtime = get_child(sysev, "TimeCreated").get("SystemTime")
     311 + etime = datetime.datetime.strptime(logtime.split(".")[0], "%Y-%m-%d %H:%M:%S") + datetime.timedelta(hours=tzone)
     312 + if args.fromdate or args.todate:
     313 + if args.fromdate and fdatetime > etime:
     314 + continue
     315 + if args.todate and tdatetime < etime:
     316 + endtime = datetime.datetime(*etime.timetuple()[:4])
     317 + break
     318 + 
     319 + if starttime is None:
     320 + starttime = datetime.datetime(*etime.timetuple()[:4])
     321 + elif starttime > etime:
     322 + starttime = datetime.datetime(*etime.timetuple()[:4])
     323 + 
     324 + if endtime is None:
     325 + endtime = datetime.datetime(*etime.timetuple()[:4])
     326 + elif endtime < etime:
     327 + endtime = datetime.datetime(*etime.timetuple()[:4])
     328 + 
     329 + event_data = get_child(node, "EventData")
     330 + logintype = "-"
     331 + username = "-"
     332 + ipaddress = "-"
     333 + status = "-"
     334 + sid = "-"
     335 + for data in event_data:
     336 + if data.get("Name") in ["IpAddress", "Workstation"] and data.text != None:
     337 + ipaddress = data.text.split("@")[0]
     338 + ipaddress = ipaddress.lower().replace("::ffff:", "")
     339 + ipaddress = ipaddress.replace("\\", "")
     340 + 
     341 + if data.get("Name") in "TargetUserName" and data.text != None:
     342 + username = data.text.split("@")[0]
     343 + if username[-1:] not in "$":
     344 + username = username.lower()
     345 + else:
     346 + username = "-"
     347 + 
     348 + if data.get("Name") in ["TargetUserSid", "TargetSid"] and data.text != None and data.text[0:2] in "S-1":
     349 + sid = data.text
     350 + 
     351 + if data.get("Name") in "LogonType":
     352 + logintype = int(data.text)
     353 + 
     354 + if data.get("Name") in "Status":
     355 + status = data.text
     356 + 
     357 + if username != "-" and ipaddress != "-" and ipaddress != "::1" and ipaddress != "127.0.0.1":
     358 + event_set.append([int(get_child(sysev, "EventID").text), ipaddress, username, logintype, status])
     359 + # print("%s,%i,%s,%s,%s,%s" % (int(get_child(sysev, "EventID").text), ipaddress, username, comment, logintype))
     360 + count_set.append([datetime.datetime(*etime.timetuple()[:4]).strftime("%Y-%m-%d %H:%M:%S"), int(get_child(sysev, "EventID").text), username])
     361 + # print("%s,%s" % (datetime.datetime(*etime.timetuple()[:4]).strftime("%Y-%m-%d %H:%M:%S"), username))
     362 + 
     363 + if ipaddress not in ipaddress_set:
     364 + ipaddress_set.append(ipaddress)
     365 + 
     366 + if username not in username_set:
     367 + username_set.append(username)
     368 + 
     369 + if sid not in "-":
     370 + sids[username] = sid
     371 + 
     372 + if int(get_child(sysev, "EventID").text) == 4672:
     373 + logtime = get_child(sysev, "TimeCreated").get("SystemTime")
     374 + if args.fromdate or args.todate:
     375 + etime = datetime.datetime.strptime(logtime.split(".")[0], "%Y-%m-%d %H:%M:%S") + datetime.timedelta(hours=tzone)
     376 + if args.fromdate and fdatetime > etime:
     377 + continue
     378 + if args.todate and tdatetime < etime:
     379 + break
     380 + 
     381 + event_data = get_child(node, "EventData")
     382 + username = "-"
     383 + for data in event_data:
     384 + if data.get("Name") in "SubjectUserName" and data.text != None:
     385 + username = data.text.split("@")[0]
     386 + if username[-1:] not in "$":
     387 + username = username.lower()
     388 + else:
     389 + username = "-"
     390 + 
     391 + if username not in admins and username != "-":
     392 + admins.append(username)
     393 + 
     394 + tohours = int((endtime - starttime).total_seconds() / 3600)
     395 + 
     396 + print("\n[*] Load finished.")
     397 + print("[*] Total Event log is %i." % count)
     398 + event_set.sort()
     399 + event_set_uniq = [(g[0], len(list(g[1]))) for g in itertools.groupby(event_set)]
     400 + count_set.sort()
     401 + count_set_uniq = [(g[0], len(list(g[1]))) for g in itertools.groupby(count_set)]
     402 + 
     403 + # Calculate PageRank
     404 + print("[*] Calculate PageRank.")
     405 + ranks = pagerank(event_set_uniq)
     406 + 
     407 + # Calculate ChangeFinder
     408 + print("[*] Calculate ChangeFinder.")
     409 + timelines, detects = adetection(count_set_uniq, username_set, ranks, starttime, tohours)
     410 + 
     411 + # Create node
     412 + print("[*] Creating a graph data.")
     413 + tx = GRAPH.begin()
     414 + for ipaddress in ipaddress_set:
     415 + tx.append(statement_ip, {"IP": ipaddress, "rank": ranks[ipaddress]})
     416 + 
     417 + i = 0
     418 + for username in username_set:
     419 + if username in sids:
     420 + sid = sids[username]
     421 + else:
     422 + sid = "-"
     423 + if username in admins:
     424 + rights = "system"
     425 + else:
     426 + rights = "user"
     427 + tx.append(statement_user, {"user": username, "rank": ranks[username],"rights": rights,"sid": sid,
     428 + "counts": ",".join(map(str, timelines[i*6])), "counts4624": ",".join(map(str, timelines[i*6+1])),
     429 + "counts4625": ",".join(map(str, timelines[i*6+2])), "counts4768": ",".join(map(str, timelines[i*6+3])),
     430 + "counts4769": ",".join(map(str, timelines[i*6+4])), "counts4776": ",".join(map(str, timelines[i*6+5])),
     431 + "detect": ",".join(map(str, detects[i]))})
     432 + i += 1
     433 + 
     434 + for events, count in event_set_uniq:
     435 + tx.append(statement_r, {"user": events[2], "IP": events[1], "id": events[0], "logintype": events[3],
     436 + "status": events[4], "count": count})
     437 + 
     438 + tx.append(statement_date, {"Daterange": "Daterange", "start": datetime.datetime(*starttime.timetuple()[:4]).strftime("%Y-%m-%d %H:%M:%S"),
     439 + "end": datetime.datetime(*endtime.timetuple()[:4]).strftime("%Y-%m-%d %H:%M:%S")})
     440 + 
     441 + tx.process()
     442 + tx.commit()
     443 + print("[*] Creation of a graph data finished.")
     444 + 
     445 + 
     446 +def main():
     447 + if not has_py2neo:
     448 + sys.exit("[!] py2neo must be installed for this script.")
     449 + 
     450 + if not has_evtx:
     451 + sys.exit("[!] python-evtx must be installed for this script.")
     452 + 
     453 + if not has_lxml:
     454 + sys.exit("[!] lxml must be installed for this script.")
     455 + 
     456 + if not has_lxml:
     457 + sys.exit("[!] numpy must be installed for this script.")
     458 + 
     459 + if not has_changefinder:
     460 + sys.exit("[!] changefinder must be installed for this script.")
     461 + 
     462 + if not has_flask:
     463 + sys.exit("[!] Flask must be installed for this script.")
     464 + 
     465 + try:
     466 + graph_http = "http://" + NEO4J_USER + ":" + NEO4J_PASSWORD +"@localhost:" + NEO4J_PORT + "/db/data/"
     467 + GRAPH = Graph(graph_http)
     468 + except:
     469 + sys.exit("[!] Can't connect Neo4j Database.")
     470 + 
     471 + print("[*] Script start. %s" % datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S"))
     472 + 
     473 + if args.run:
     474 + try:
     475 + app.run(host="0.0.0.0", port=WEB_PORT)
     476 + except:
     477 + sys.exit("[!] Can't runnning web application.")
     478 + 
     479 + # Delete database data
     480 + if args.delete:
     481 + GRAPH.delete_all()
     482 + print("[*] Delete all nodes and relationships from this Neo4j database.")
     483 + 
     484 + if args.evtx:
     485 + evtx_file = args.evtx
     486 + try:
     487 + os.path.exists(evtx_file)
     488 + except IOError:
     489 + sys.exit("[!] Can't open file {0}.".format(evtx_file))
     490 + 
     491 + fb = open(evtx_file, "rb")
     492 + fb_data = fb.read()[0:8]
     493 + if fb_data != EVTX_HEADER:
     494 + sys.exit("[!] This file is not EVTX format {0}.".format(evtx_file))
     495 + fb.close()
     496 + parse_evtx(evtx_file, GRAPH)
     497 + 
     498 + print("[*] Script end. %s" % datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S"))
     499 + 
     500 +if __name__ == "__main__":
     501 + main()
     502 + 
  • ■ ■ ■ ■ ■ ■
    requirements.txt
     1 +numpy
     2 +py2neo
     3 +python-evtx
     4 +lxml
     5 +changefinder
     6 +flask
     7 + 
  • ■ ■ ■ ■ ■ ■
    sample/README.md
     1 +# How to Use
     2 +## Security.evtx
     3 + AD security event sample
     4 + ```
     5 + $ python3 logontracer.py -e Security.evtx -z [TIMEZONE] -u [NEO4J_USER] -p [PASSWORD] -s [NEO4J_SERVER]
     6 + ```
     7 +## graph.db.tar.gz
     8 + neo4j database sample
     9 + ```
     10 + $ tar xvzf graph.db.tar.gz -C [neo4j]/data/databases/
     11 + ```
     12 + 
  • sample/Security.evtx
    Binary file.
  • sample/graph.db.tar.gz
    Binary file.
  • ■ ■ ■ ■ ■
    static/.gitkeep
     1 + 
  • ■ ■ ■ ■ ■ ■
    templates/index.html
     1 +<!DOCTYPE html>
     2 +<html lang="en">
     3 + 
     4 +<head>
     5 + <meta charset="utf-8">
     6 + <title>LogonTracer</title>
     7 + <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
     8 + <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script>
     9 + <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
     10 + <script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.1.1/cytoscape.min.js" integrity="sha256-lEMQOla3MoXVNUAEGAuu4n1XouEYLyZP3SRqe6BhpTY=" crossorigin="anonymous"></script>
     11 + <!-- Neo4j JavaScript Driver -->
     12 + <script src="/static/node_modules/neo4j-driver/lib/browser/neo4j-web.js"></script>
     13 +</head>
     14 + 
     15 +<body>
     16 + <nav class="navbar navbar-default">
     17 + <div class="container-fluid">
     18 + <a class="navbar-brand" href="#">LogonTracer</a>
     19 + <div class="collapse navbar-collapse">
     20 + <form class="navbar-form navbar-left" role="search">
     21 + <div class="form-group">
     22 + <label class="sr-only" for="InputSelect">select</label>
     23 + <select class="form-control" id="InputSelect">
     24 + <option>Username</option>
     25 + <option>Host</option>
     26 + </select>
     27 + <input class="form-control" type="text" value="administrator" id="query-input" size="10">
     28 + <div id="itemForm"></div>
     29 + </div>
     30 + <input type="button" value="+" onclick="ItemField.add();" />
     31 + <input type="button" value="-" onclick="ItemField.del();" />
     32 + <div class="checkbox">
     33 + <a>Event ID: </a>
     34 + <label>
     35 + <input type="checkbox" id="id4624" checked="checked"> 4624
     36 + </label>
     37 + <label>
     38 + <input type="checkbox" id="id4625" checked="checked"> 4625
     39 + </label>
     40 + <label>
     41 + <input type="checkbox" id="id4768" checked="checked"> 4768
     42 + </label>
     43 + <label>
     44 + <input type="checkbox" id="id4769" checked="checked"> 4769
     45 + </label>
     46 + <label>
     47 + <input type="checkbox" id="id4776" checked="checked"> 4776
     48 + </label>
     49 + </div>
     50 + <a>Count: </a>
     51 + <div class="form-group">
     52 + <input class="form-control" type="text" value=0 id="count-input" size="1">
     53 + </div>
     54 + <button type="button" class="btn btn-default" onclick="createQuery()">search</button>
     55 + <div class="btn-group">
     56 + <a href="#" class="btn btn-default dropdown-toggle" type="button" data-toggle="dropdown" aria-expanded="false">
     57 + Export <span class="caret"></span>
     58 + </a>
     59 + <ul class="dropdown-menu" role="menu">
     60 + <li role="presentation"><a href="#" download="image.csv" id="export-csv" onclick="exportCSV()">CSV</a></li>
     61 + <li role="presentation"><a href="#" download="image.json" id="export-json" onclick="exportJSON()">JSON</a></li>
     62 + <li role="presentation"><a href="#" download="image.png" id="export-png" onclick="exportPNG()">PNG</a></li>
     63 + <li role="presentation"><a href="#" download="image.jpeg" id="export-jpeg" onclick="exportJPEG()">JPEG</a></li>
     64 + </ul>
     65 + </div>
     66 + </form>
     67 + </div>
     68 + </div>
     69 + </nav>
     70 + 
     71 + <div class="container-fluid">
     72 + <div class="row">
     73 + <div class="col-sm-2 col-md-2 sidebar">
     74 + <div class="list-group">
     75 + <button type="button" class="list-group-item" onclick="createAllQuery()">All Users</button>
     76 + <button type="button" class="list-group-item" onclick="createSystemQuery()">SYSTEM privileges</button>
     77 + <button type="button" class="list-group-item" onclick="createRDPQuery()">RDP Logon</button>
     78 + <button type="button" class="list-group-item" onclick="createNetQuery()">Network Logon</button>
     79 + <button type="button" class="list-group-item" onclick="createBatchQuery()">Batch Logon</button>
     80 + <button type="button" class="list-group-item" onclick="createServiceQuery()">Service Logon</button>
     81 + <button type="button" class="list-group-item" onclick="create14068Query()">ms14-068 exploit failure</button>
     82 + <button type="button" class="list-group-item" onclick="createFailQuery()">Logon failure</button>
     83 + </div>
     84 + <hr>
     85 + <a>Add event value</a><br>
     86 + <div class="btn-group" data-toggle="buttons">
     87 + <label class="btn btn-default">
     88 + <input type="checkbox" name="options" id="label-count" autocomplete="off">Count</label>
     89 + <label class="btn btn-default">
     90 + <input type="checkbox" name="options" id="label-type" autocomplete="off">Type</label>
     91 + <label class="btn btn-default">
     92 + <input type="checkbox" name="options" id="label-status" autocomplete="off">Status</label>
     93 + </div>
     94 + <hr>
     95 + <a>Timeline</a><br>
     96 + <div class="list-group">
     97 + <button type="button" class="list-group-item" onclick="createAlltimeline()">Create All Users</button>
     98 + <button type="button" class="list-group-item" onclick="searchTimeline()">Search</button>
     99 + </div>
     100 + <div class="btn-group">
     101 + <button class="btn btn-default dropdown-toggle" type="button" data-toggle="dropdown" aria-expanded="false">
     102 + Download <span class="caret"></span>
     103 + </button>
     104 + <ul class="dropdown-menu" role="menu">
     105 + <li role="presentation"><a href="#" download="summary.csv" id="downloadsum-csv" onclick="downloadSummary()">Summary</a></li>
     106 + <li role="presentation"><a href="#" download="details.csv" id="downloaddet-csv" onclick="downloadDetail()">Details</a></li>
     107 + </ul>
     108 + </div>
     109 + <hr>
     110 + <a>Upload</a><br>
     111 + <button class="btn btn-default" data-toggle="modal" data-target="#UploadEVTX">Upload EVTX File</button>
     112 + </div>
     113 + <div class="col-sm-8 col-md-8 main">
     114 + <div id="error"></div>
     115 + <div id="cy" style="height:900px;"></div>
     116 + </div>
     117 + <div class="col-sm-2 col-md-2">
     118 + <div class="container" id="rankUser"></div>
     119 + <ul class="pager">
     120 + <li><a onclick="pruserBack()">Back</a></li>
     121 + <li><a onclick="pruserNext()">Next</a></li>
     122 + </ul>
     123 + <hr>
     124 + <div class="container" id="rankHost"></div>
     125 + <ul class="pager">
     126 + <li><a onclick="prhostBack()">Back</a></li>
     127 + <li><a onclick="prhostNext()">Next</a></li>
     128 + </ul>
     129 + </div>
     130 + </div>
     131 + <!-- Upload -->
     132 + <div class="modal fade" id="UploadEVTX" tabindex="-1">
     133 + <div class="modal-dialog">
     134 + <div class="modal-content">
     135 + <div class="modal-header">
     136 + <button type="button" class="close" data-dismiss="modal"><span>×</span></button>
     137 + <h4 class="modal-title">Upload EVTX File</h4>
     138 + </div>
     139 + <div class="modal-body">
     140 + <div id="zoneTime"></div>
     141 + <input id="lefile" type="file" style="display:none">
     142 + <div class="input-group">
     143 + <input type="text" id="evtx_name" class="form-control" placeholder="select file...">
     144 + <span class="input-group-btn"><button type="button" class="btn btn-info" onclick="$('input[id=lefile]').click();">Browse</button></span>
     145 + </div>
     146 + <div id="uploadBar"></div>
     147 + <div id="status"></div>
     148 + </div>
     149 + <div class="modal-footer">
     150 + <button type="submit" class="btn btn-primary" onclick="file_upload()">Upload</button>
     151 + <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
     152 + </div>
     153 + </div>
     154 + </div>
     155 + </div>
     156 + <script type="text/javascript">
     157 + var neo = neo4j.default;
     158 + //Neo4j access settings
     159 + var driver = neo.driver("bolt://{{ server_ip }}", neo.auth.basic("{{ neo4j_user }}", "{{ neo4j_password }}"));
     160 + var session = driver.session();
     161 + var cy = cytoscape();
     162 + var rankpageUser = 0
     163 + var rankpageHost = 0
     164 + 
     165 + var userqueryStr = 'MATCH (node:Username) RETURN node';
     166 + var ipqueryStr = 'MATCH (node:IPAddress) RETURN node';
     167 + pagerankQuery(userqueryStr, "User", rankpageUser);
     168 + pagerankQuery(ipqueryStr, "Host", rankpageHost);
     169 + exportCSV();
     170 + downloadSummary();
     171 + downloadDetail();
     172 + 
     173 + var currentNumber = 0;
     174 + var ItemField = {
     175 + currentNumber: 0,
     176 + itemTemplate: '<label class="sr-only" for="InputSelect">select</label>\
     177 + <select class="form-control" id="InputSelect_count_">\
     178 + <option>Username</option><option>Host</option></select>\
     179 + <input class="form-control" type="text" id="query-input_count_" size="10">\
     180 + <label class="sr-only" for="InputSelect">select</label>\
     181 + <select class="form-control" id="InputRule_count_">\
     182 + <option>OR</option><option>AND</option></select>',
     183 + add: function() {
     184 + currentNumber++;
     185 + if (currentNumber <= 10) {
     186 + var new_item = this.itemTemplate.replace(/_count_/mg, currentNumber);
     187 + var new_area = document.createElement("div");
     188 + new_area.setAttribute("id", "item" + currentNumber);
     189 + var field = document.getElementById('itemForm');
     190 + field.appendChild(new_area);
     191 + document.getElementById('item' + currentNumber).innerHTML = new_item;
     192 + }
     193 + },
     194 + del: function() {
     195 + if (currentNumber == 0) {
     196 + return;
     197 + }
     198 + var field = document.getElementById('itemForm');
     199 + field.removeChild(field.lastChild);
     200 + currentNumber--;
     201 + }
     202 + }
     203 + 
     204 + var downMenu = '<div class="col-xs-3"><select class="form-control" id="utcTime"><option>UTC</option>';
     205 + for (i = +14; i >= -12; i--) {
     206 + downMenu += '<option>' + i + '</option>';
     207 + }
     208 + downMenu += '</select></div>';
     209 + document.getElementById("zoneTime").innerHTML = downMenu;
     210 + 
     211 + function buildGraph(graph, path) {
     212 + for (idx in path) {
     213 + if (Object.keys(path[idx]).length == 3) {
     214 + objid = parseInt(path[idx].identity.low) + 100;
     215 + } else {
     216 + objid = parseInt(path[idx].identity.low) + 1000;
     217 + }
     218 + 
     219 + // Node
     220 + if (Object.keys(path[idx]).length == 3) {
     221 + var ndupflg = false;
     222 + for (nidx in graph.nodes) {
     223 + if (graph.nodes[nidx].data.objid == objid) {
     224 + ndupflg = true;
     225 + }
     226 + }
     227 + if (ndupflg) {
     228 + continue;
     229 + }
     230 + 
     231 + if (path[idx].labels[0] == "Username") {
     232 + nname = path[idx].properties.user
     233 + nfsize = "10"
     234 + nshape = "ellipse"
     235 + if (path[idx].properties.rights == "system") {
     236 + nwidth = "22"
     237 + nheight = "22"
     238 + ncolor = "#ff0000"
     239 + nbcolor = "#ffc0cb"
     240 + nfcolor = "#ff69b4"
     241 + } else {
     242 + nwidth = "15"
     243 + nheight = "15"
     244 + ncolor = "#0000cd"
     245 + nbcolor = "#cee1ff"
     246 + nfcolor = "#6da0f2"
     247 + }
     248 + }
     249 + if (path[idx].labels[0] == "IPAddress") {
     250 + nname = path[idx].properties.IP
     251 + nshape = "diamond"
     252 + nwidth = "15"
     253 + nheight = "15"
     254 + nfsize = "8"
     255 + ncolor = "#2e8b57"
     256 + nbcolor = "#98fb98"
     257 + nfcolor = "#3cb371"
     258 + }
     259 + graph.nodes.push({
     260 + "data": {
     261 + "id": objid,
     262 + "objid": objid,
     263 + "nlabel": nname,
     264 + "ncolor": ncolor,
     265 + "nbcolor": nbcolor,
     266 + "nfcolor": nfcolor,
     267 + "nwidth": nwidth,
     268 + "nheight": nheight,
     269 + "nfsize": nfsize,
     270 + "nshape": nshape,
     271 + "label": path[idx].labels[0],
     272 + }
     273 + });
     274 + } else {
     275 + // Relationship
     276 + var ldupflg = false;
     277 + var label_count = document.getElementById("label-count").checked;
     278 + var label_type = document.getElementById("label-type").checked;
     279 + var label_status = document.getElementById("label-status").checked;
     280 + var ename = path[idx].properties.id.low;
     281 + if (label_count) {
     282 + ename += " : " + path[idx].properties.count.low;
     283 + }
     284 + if (label_type) {
     285 + ename += " : " + path[idx].properties.logintype;
     286 + }
     287 + if (label_status) {
     288 + ename += " : " + path[idx].properties.status;
     289 + }
     290 + for (nidx in graph.edges) {
     291 + if (graph.edges[nidx].data.objid == objid) {
     292 + ldupflg = true;
     293 + }
     294 + }
     295 + if (ldupflg) {
     296 + continue;
     297 + }
     298 + graph.edges.push({
     299 + "data": {
     300 + "id": objid,
     301 + "source": parseInt(path[parseInt(idx) - 1].identity.low) + 100,
     302 + "target": parseInt(path[parseInt(idx) + 1].identity.low) + 100,
     303 + "objid": objid,
     304 + "elabel": ename,
     305 + "label": path[idx].type,
     306 + "distance": 5
     307 + }
     308 + });
     309 + }
     310 + }
     311 + 
     312 + return (graph);
     313 + }
     314 + 
     315 + function drawGraph(graph, rootNode) {
     316 + cy = cytoscape({
     317 + container: document.getElementById("cy"),
     318 + boxSelectionEnabled: false,
     319 + autounselectify: true,
     320 + style: cytoscape.stylesheet()
     321 + .selector('node').css({
     322 + "content": "data(nlabel)",
     323 + "width": "data(nwidth)",
     324 + "height": "data(nheight)",
     325 + "color": "data(ncolor)",
     326 + "font-size": "data(nfsize)",
     327 + "background-color": "data(nbcolor)",
     328 + "border-color": "data(nfcolor)",
     329 + "border-style": "solid",
     330 + "border-width": 3,
     331 + "shape": "data(nshape)"
     332 + })
     333 + .selector('edge').css({
     334 + "content": "data(elabel)",
     335 + "font-size": "8",
     336 + 'curve-style': 'bezier',
     337 + 'target-arrow-shape': 'triangle',
     338 + 'width': 2,
     339 + 'line-color': '#ddd',
     340 + 'target-arrow-color': '#ddd'
     341 + })
     342 + .selector('.highlighted').css({
     343 + 'background-color': '#61bffc',
     344 + 'line-color': '#61bfcc',
     345 + 'transition-property': 'background-color, line-color, target-arrow-color',
     346 + 'transition-duration': '0.5s'
     347 + }),
     348 + elements: graph,
     349 + layout: {
     350 + name: 'cose',
     351 + directed: true,
     352 + roots: rootNode,
     353 + animate: true,
     354 + padding: 10
     355 + }
     356 + });
     357 + 
     358 + cy.on('click', 'node', function(event) {
     359 + console.log("clicked", this, event, this.id());
     360 + });
     361 + }
     362 + 
     363 + function createAllQuery() {
     364 + eidStr = getQueryID();
     365 + eidStr = eidStr.slice(4);
     366 + queryStr = 'MATCH (user)-[event]-(ip) WHERE ' + eidStr + ' RETURN user, event, ip';
     367 + //console.log(queryStr);
     368 + executeQuery(queryStr);
     369 + }
     370 + 
     371 + function createSystemQuery() {
     372 + eidStr = getQueryID();
     373 + queryStr = 'MATCH (user)-[event]-(ip) WHERE user.rights = "system" ' + eidStr + ' RETURN user, event, ip';
     374 + //console.log(queryStr);
     375 + executeQuery(queryStr);
     376 + }
     377 + 
     378 + function createRDPQuery() {
     379 + eidStr = getQueryID();
     380 + queryStr = 'MATCH (user)-[event]-(ip) WHERE event.logintype = 10 ' + eidStr + ' RETURN user, event, ip';
     381 + //console.log(queryStr);
     382 + executeQuery(queryStr);
     383 + }
     384 + 
     385 + function createNetQuery() {
     386 + eidStr = getQueryID();
     387 + queryStr = 'MATCH (user)-[event]-(ip) WHERE event.logintype = 3 ' + eidStr + ' RETURN user, event, ip';
     388 + //console.log(queryStr);
     389 + executeQuery(queryStr);
     390 + }
     391 + 
     392 + function createBatchQuery() {
     393 + eidStr = getQueryID();
     394 + queryStr = 'MATCH (user)-[event]-(ip) WHERE event.logintype = 4 ' + eidStr + ' RETURN user, event, ip';
     395 + //console.log(queryStr);
     396 + executeQuery(queryStr);
     397 + }
     398 + 
     399 + function createServiceQuery() {
     400 + eidStr = getQueryID();
     401 + queryStr = 'MATCH (user)-[event]-(ip) WHERE event.logintype = 5 ' + eidStr + ' RETURN user, event, ip';
     402 + //console.log(queryStr);
     403 + executeQuery(queryStr);
     404 + }
     405 + 
     406 + function create14068Query() {
     407 + queryStr = 'MATCH (user)-[event]-(ip) WHERE event.status = "0xf" AND event.id = 4769 RETURN user, event, ip'
     408 + //console.log(queryStr);
     409 + executeQuery(queryStr);
     410 + }
     411 + 
     412 + function createFailQuery() {
     413 + queryStr = 'MATCH (user)-[event]-(ip) WHERE event.id = 4625 RETURN user, event, ip'
     414 + //console.log(queryStr);
     415 + executeQuery(queryStr);
     416 + }
     417 + 
     418 + function createRankQuery(setStr, qType) {
     419 + if (qType == "User") {
     420 + whereStr = 'user.user = "' + setStr + '" ';
     421 + }
     422 + if (qType == "Host") {
     423 + whereStr = 'ip.IP = "' + setStr + '" ';
     424 + }
     425 + 
     426 + eidStr = getQueryID()
     427 + 
     428 + queryStr = 'MATCH (user)-[event]-(ip) WHERE (' + whereStr + ') ' + eidStr + ' RETURN user, event, ip';
     429 + //console.log(queryStr);
     430 + executeQuery(queryStr);
     431 + }
     432 + 
     433 + function getQueryID() {
     434 + var id4624Ch = document.getElementById("id4624").checked;
     435 + var id4625Ch = document.getElementById("id4625").checked;
     436 + var id4768Ch = document.getElementById("id4768").checked;
     437 + var id4769Ch = document.getElementById("id4769").checked;
     438 + var id4776Ch = document.getElementById("id4776").checked;
     439 + var countInt = document.getElementById("count-input").value;
     440 + var eidStr = "AND ("
     441 + if (id4624Ch) {
     442 + eidStr = eidStr + "event.id = 4624 OR ";
     443 + }
     444 + if (id4625Ch) {
     445 + eidStr = eidStr + "event.id = 4625 OR ";
     446 + }
     447 + if (id4768Ch) {
     448 + eidStr = eidStr + "event.id = 4768 OR ";
     449 + }
     450 + if (id4769Ch) {
     451 + eidStr = eidStr + "event.id = 4769 OR ";
     452 + }
     453 + if (id4776Ch) {
     454 + eidStr = eidStr + "event.id = 4776 OR ";
     455 + }
     456 + eidStr = eidStr.slice(0, -4) + ")";
     457 + eidStr = eidStr + " AND event.count > " + countInt;
     458 + 
     459 + return eidStr;
     460 + }
     461 + 
     462 + function createQuery() {
     463 + var selectVal = document.getElementById("InputSelect").value;
     464 + var setStr = document.getElementById("query-input").value;
     465 + 
     466 + if (selectVal == "Username") {
     467 + whereStr = 'user.user =~ "' + setStr + '" ';
     468 + } else {
     469 + whereStr = 'ip.IP =~ "' + setStr + '" ';
     470 + }
     471 + 
     472 + for (i = 1; i <= currentNumber; i++) {
     473 + if (document.getElementById("query-input" + i).value) {
     474 + ruleStr = document.getElementById("InputRule" + i).value;
     475 + if (document.getElementById("InputSelect" + i).value == "Username") {
     476 + whereStr += ruleStr + ' user.user =~ "' + document.getElementById("query-input" + i).value + '" ';
     477 + } else {
     478 + whereStr += ruleStr + ' ip.IP =~ "' + document.getElementById("query-input" + i).value + '" ';
     479 + }
     480 + }
     481 + }
     482 + 
     483 + eidStr = getQueryID()
     484 + queryStr = 'MATCH (user)-[event]-(ip) WHERE (' + whereStr + ') ' + eidStr + ' RETURN user, event, ip';
     485 + //console.log(queryStr);
     486 + executeQuery(queryStr);
     487 + }
     488 + 
     489 + function executeQuery(queryStr) {
     490 + var graph = {
     491 + "nodes": [],
     492 + "edges": []
     493 + };
     494 + 
     495 + session.run(queryStr)
     496 + .subscribe({
     497 + onNext: function(record) {
     498 + //console.log(record.get('user'), record.get('event'), record.get('ip'));
     499 + graph = buildGraph(graph, [record.get("user"), record.get("event"), record.get("ip")])
     500 + },
     501 + onCompleted: function() {
     502 + session.close();
     503 + if (graph.nodes.length == 0) {
     504 + searchError();
     505 + } else {
     506 + //console.log(graph);
     507 + rootNode = graph.nodes[0].id;
     508 + drawGraph(graph, rootNode);
     509 + }
     510 + },
     511 + onError: function(error) {
     512 + console.log("Error: ", error);
     513 + }
     514 + });
     515 + }
     516 + 
     517 + function pruserBack() {
     518 + rankpageUser -= 1;
     519 + if (rankpageUser < 0) {
     520 + rankpageUser = 0;
     521 + }
     522 + pagerankQuery(userqueryStr, "User", rankpageUser);
     523 + }
     524 + 
     525 + function pruserNext() {
     526 + rankpageUser += 1;
     527 + if (rankpageUser < 0) {
     528 + rankpageUser = 0;
     529 + }
     530 + pagerankQuery(userqueryStr, "User", rankpageUser);
     531 + }
     532 + 
     533 + function prhostBack() {
     534 + rankpageHost -= 1;
     535 + if (rankpageHost < 0) {
     536 + rankpageHost = 0;
     537 + }
     538 + pagerankQuery(ipqueryStr, "Host", rankpageHost);
     539 + }
     540 + 
     541 + function prhostNext() {
     542 + rankpageHost += 1;
     543 + if (rankpageHost < 0) {
     544 + rankpageHost = 0;
     545 + }
     546 + pagerankQuery(ipqueryStr, "Host", rankpageHost);
     547 + }
     548 + 
     549 + function pagerankQuery(queryStr, dataType, currentPage) {
     550 + var nodes = new Array();
     551 + var html = '<div><table class="table table-striped"><thead><tr class="col-sm-2 col-md-2">\
     552 + <th class="col-sm-1 col-md-1">Rank</th><th class="col-sm-1 col-md-1">' + dataType +
     553 + '</th></tr></thead><tbody class="col-sm-2 col-md-2">';
     554 + session.run(queryStr)
     555 + .subscribe({
     556 + onNext: function(record) {
     557 + nodeData = record.get("node");
     558 + if (dataType == "User") {
     559 + nodes.push([nodeData.properties.user, nodeData.properties.rank]);
     560 + }
     561 + if (dataType == "Host") {
     562 + nodes.push([nodeData.properties.IP, nodeData.properties.rank]);
     563 + }
     564 + },
     565 + onCompleted: function() {
     566 + session.close();
     567 + nodes.sort(function(a, b) {
     568 + var aa = a[1];
     569 + var bb = b[1];
     570 + if (aa < bb) {
     571 + return 1;
     572 + }
     573 + if (aa > bb) {
     574 + return -1;
     575 + }
     576 + return 0;
     577 + });
     578 + for (i = 10 * currentPage; i < nodes.length; i++) {
     579 + if (i >= 10 * currentPage + 10) {
     580 + break;
     581 + }
     582 + html += '<tr><td>' + (i + 1) + '</td><td><a onclick="createRankQuery(\'' + nodes[i][0] + '\', \'' + dataType + '\')">' + nodes[i][0] + '</a></td></tr>';
     583 + //console.log(nodes[i][0]);
     584 + //console.log(hosts[i][0]);
     585 + }
     586 + html += '</tbody></table></div>';
     587 + 
     588 + if (dataType == "User") {
     589 + var rankElem = document.getElementById("rankUser");
     590 + }
     591 + if (dataType == "Host") {
     592 + var rankElem = document.getElementById("rankHost");
     593 + }
     594 + rankElem.innerHTML = html;
     595 + },
     596 + onError: function(error) {
     597 + console.log("Error: ", error);
     598 + }
     599 + });
     600 + }
     601 + 
     602 + function exportCSV() {
     603 + var queryStr = 'MATCH (user:Username)-[event]-(ip:IPAddress) RETURN user, ip, event';
     604 + var events = new Array();
     605 + 
     606 + session.run(queryStr)
     607 + .subscribe({
     608 + onNext: function(record) {
     609 + eventData = record.get("event");
     610 + userData = record.get("user");
     611 + ipData = record.get("ip");
     612 + events.push([userData.properties.user, ipData.properties.IP,
     613 + eventData.properties.id, eventData.properties.logintype,
     614 + eventData.properties.status, eventData.properties.count
     615 + ]);
     616 + },
     617 + onCompleted: function() {
     618 + session.close();
     619 + var rowData = "username,host,id,logontype,status,count\r\n";
     620 + for (i = 0; i < events.length; i++) {
     621 + rowData += events[i][0] + ",";
     622 + rowData += events[i][1] + ",";
     623 + rowData += events[i][2] + ",";
     624 + rowData += events[i][3] + ",";
     625 + rowData += events[i][4] + ",";
     626 + rowData += events[i][5] + "\r\n";
     627 + }
     628 + var csvData = "data:application/csv,";
     629 + csvData += encodeURIComponent(rowData);
     630 + var exptag = document.getElementById('export-csv');
     631 + exptag.href = csvData;
     632 + 
     633 + },
     634 + onError: function(error) {
     635 + console.log("Error: ", error);
     636 + }
     637 + });
     638 + }
     639 + 
     640 + function exportJSON() {
     641 + var jsonData = "data:application/json,";
     642 + jsonData += encodeURIComponent(JSON.stringify(cy.json()));
     643 + var exptag = document.getElementById('export-json');
     644 + exptag.href = jsonData;
     645 + }
     646 + 
     647 + function exportPNG() {
     648 + var png64 = cy.png();
     649 + var exptag = document.getElementById('export-png');
     650 + exptag.href = png64;
     651 + }
     652 + 
     653 + function exportJPEG() {
     654 + var jpg64 = cy.png();
     655 + var exptag = document.getElementById('export-jpeg');
     656 + exptag.href = jpg64;
     657 + }
     658 + 
     659 + function downloadCSV(csvType) {
     660 + var queryStr = 'MATCH (date:Date) MATCH (user:Username) RETURN date, user';
     661 + var users = new Array();
     662 + 
     663 + session.run(queryStr)
     664 + .subscribe({
     665 + onNext: function(record) {
     666 + nodeData = record.get("user");
     667 + dateData = record.get("date");
     668 + users.push([nodeData.properties.user, nodeData.properties.counts,
     669 + nodeData.properties.counts4624, nodeData.properties.counts4625,
     670 + nodeData.properties.counts4768, nodeData.properties.counts4769,
     671 + nodeData.properties.counts4776
     672 + ]);
     673 + starttime = dateData.properties.start;
     674 + endtime = dateData.properties.end;
     675 + },
     676 + onCompleted: function() {
     677 + session.close();
     678 + 
     679 + var startDate = new Date(starttime);
     680 + var rangeHours = Math.floor((Date.parse(endtime) - Date.parse(starttime)) / (1000 * 60 * 60)) + 1;
     681 + var rawDate = "username,";
     682 + if (csvType == "detail") {
     683 + rawDate += "id,";
     684 + }
     685 + var countData = "";
     686 + for (i = 0; i < rangeHours; i++) {
     687 + rawDate += startDate.toISOString() + ",";
     688 + startDate.setHours(startDate.getHours() + 1);
     689 + }
     690 + 
     691 + if (csvType == "summary") {
     692 + for (i = 0; i < users.length; i++) {
     693 + countData += users[i][0] + "," + users[i][1] + "\r\n";
     694 + }
     695 + } else if (csvType == "detail") {
     696 + for (i = 0; i < users.length; i++) {
     697 + countData += users[i][0];
     698 + for (j = 2; j <= 6; j++) {
     699 + if (j == 2) {
     700 + countData += ",4624,"
     701 + } else if (j == 3) {
     702 + countData += ",4625,"
     703 + } else if (j == 4) {
     704 + countData += ",4768,"
     705 + } else if (j == 5) {
     706 + countData += ",4769,"
     707 + } else if (j == 6) {
     708 + countData += ",4776,"
     709 + }
     710 + countData += users[i][j] + "\r\n";
     711 + }
     712 + }
     713 + }
     714 + 
     715 + var csvData = "data:application/csv,";
     716 + csvData += encodeURIComponent(rawDate + "\r\n");
     717 + csvData += encodeURIComponent(countData);
     718 + if (csvType == "summary") {
     719 + var exptag = document.getElementById('downloadsum-csv');
     720 + } else if (csvType == "detail") {
     721 + var exptag = document.getElementById('downloaddet-csv');
     722 + }
     723 + exptag.href = csvData;
     724 + },
     725 + onError: function(error) {
     726 + console.log("Error: ", error);
     727 + }
     728 + });
     729 + }
     730 + 
     731 + function downloadSummary() {
     732 + downloadCSV("summary")
     733 + }
     734 + 
     735 + function downloadDetail() {
     736 + downloadCSV("detail")
     737 + }
     738 + 
     739 + function createTimeline(queryStr, tableType) {
     740 + var users = new Array();
     741 + var starttime = "";
     742 + var endtime = "";
     743 + var weekTbl = new Array("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat");
     744 + var bgcolorTbl = new Array("#ff7f50", "#efefef", "#efefef", "#efefef", "#efefef", "#efefef", "#b0c4de");
     745 + 
     746 + if (tableType == "all") {
     747 + var span = 'rowspan = "4"';
     748 + }
     749 + if (tableType == "search") {
     750 + var span = 'rowspan = "4" colspan="2"';
     751 + }
     752 + var html = '<div class="table-responsive"><table class="table table-bordered table-condensed table-striped" style="background-color:#EEE;"><thead><tr>\
     753 + <th ' + span + '>Username</th>';
     754 + 
     755 + session.run(queryStr)
     756 + .subscribe({
     757 + onNext: function(record) {
     758 + dateData = record.get("date");
     759 + nodeData = record.get("user");
     760 + users.push([nodeData.properties.user, nodeData.properties.counts, nodeData.properties.detect, nodeData.properties.rights, nodeData.properties.counts4624,
     761 + nodeData.properties.counts4625, nodeData.properties.counts4768, nodeData.properties.counts4769, nodeData.properties.counts4776
     762 + ]);
     763 + starttime = dateData.properties.start;
     764 + endtime = dateData.properties.end;
     765 + },
     766 + onCompleted: function() {
     767 + session.close();
     768 + var startDate = new Date(starttime);
     769 + var rangeHours = Math.floor((Date.parse(endtime) - Date.parse(starttime)) / (1000 * 60 * 60)) + 1;
     770 + var thisyear = startDate.getFullYear();
     771 + var thismonth = startDate.getMonth();
     772 + var thisday = startDate.getDate();
     773 + var thishour = startDate.getHours();
     774 + var thisdow = startDate.getDay();
     775 + var nextyear = null;
     776 + var nrangeHours = 0;
     777 + var weekd = 0;
     778 + for (i = 1; i <= rangeHours; i++) {
     779 + startDate.setHours(startDate.getHours() + 1);
     780 + if (startDate.getFullYear() != thisyear) {
     781 + html += '<th colspan="' + (i - nrangeHours) + '">' + thisyear + '</th>';
     782 + thisyear = startDate.getFullYear();
     783 + nrangeHours = i;
     784 + }
     785 + }
     786 + html += '<th colspan="' + (rangeHours - nrangeHours) + '">' + thisyear + '</th></tr><tr>';
     787 + 
     788 + nrangeHours = 0;
     789 + startDate = new Date(starttime);
     790 + for (i = 1; i <= rangeHours; i++) {
     791 + startDate.setHours(startDate.getHours() + 1);
     792 + if (startDate.getMonth() != thismonth) {
     793 + html += '<th colspan="' + (i - nrangeHours) + '">' + (thismonth + 1) + '</th>';
     794 + thismonth = startDate.getMonth();
     795 + nrangeHours = i;
     796 + }
     797 + }
     798 + html += '<th colspan="' + (rangeHours - nrangeHours) + '">' + (thismonth + 1) + '</th></tr><tr>';
     799 + 
     800 + nrangeHours = 0;
     801 + startDate = new Date(starttime);
     802 + for (i = 1; i <= rangeHours; i++) {
     803 + startDate.setHours(startDate.getHours() + 1);
     804 + if (startDate.getDate() != thisday) {
     805 + html += '<th bgcolor="' + bgcolorTbl[thisdow + weekd] + '" colspan="' + (i - nrangeHours) + '">' + thisday + '(' + weekTbl[thisdow + weekd] + ')</th>';
     806 + if (thisdow + weekd >= 6) {
     807 + thisdow = 0 - (weekd + 1);
     808 + }
     809 + thisday = startDate.getDate();
     810 + nrangeHours = i;
     811 + weekd += 1;
     812 + }
     813 + }
     814 + html += '<th bgcolor="' + bgcolorTbl[thisdow + weekd] + '" colspan="' + (rangeHours - nrangeHours) + '">' + thisday + '(' + weekTbl[thisdow + weekd] + ')</th></tr><tr>';
     815 + 
     816 + for (i = 0; i < rangeHours; i++) {
     817 + html += '<th>' + thishour + '</th>';
     818 + thishour += 1;
     819 + if (thishour >= 24) {
     820 + thishour = 0;
     821 + }
     822 + }
     823 + 
     824 + html += '</tr></thead><tbody>';
     825 + 
     826 + if (tableType == "all") {
     827 + for (i = 0; i < users.length; i++) {
     828 + if (users[i][3] == "system") {
     829 + html += '<tr><td><font color="#ff7f50">' + users[i][0] + '</font></td>';
     830 + } else {
     831 + html += '<tr><td>' + users[i][0] + '</td>';
     832 + }
     833 + rowdata = users[i][1].split(",");
     834 + alerts = users[i][2].split(",");
     835 + for (j = 0; j < rowdata.length; j++) {
     836 + if (alerts[j] > 17) {
     837 + html += '<td bgcolor="#ff5aee">' + rowdata[j].split(".")[0] + '</td>';
     838 + } else if (alerts[j] > 16) {
     839 + html += '<td bgcolor="#ff8aee">' + rowdata[j].split(".")[0] + '</td>';
     840 + } else if (alerts[j] > 13) {
     841 + html += '<td bgcolor="#ffbaee">' + rowdata[j].split(".")[0] + '</td>';
     842 + } else if (alerts[j] > 10) {
     843 + html += '<td bgcolor="#ffeaee">' + rowdata[j].split(".")[0] + '</td>';
     844 + } else {
     845 + html += '<td>' + rowdata[j].split(".")[0] + '</td>';
     846 + }
     847 + }
     848 + html += '</tr>';
     849 + }
     850 + }
     851 + 
     852 + if (tableType == "search") {
     853 + for (i = 0; i < users.length; i++) {
     854 + if (users[i][3] == "system") {
     855 + html += '<tr><td rowspan = "5"><font color="#ff7f50">' + users[i][0] + '</font></td>';
     856 + } else {
     857 + html += '<tr><td rowspan = "5">' + users[i][0] + '</td>';
     858 + }
     859 + 
     860 + for (j = 4; j <= 8; j++) {
     861 + rowdata = users[i][j].split(",");
     862 + alerts = users[i][2].split(",");
     863 + if (j == 4) {
     864 + html += '<td>4624</td>';
     865 + } else if (j == 5) {
     866 + html += '<td>4625</td>';
     867 + } else if (j == 6) {
     868 + html += '<td>4768</td>';
     869 + } else if (j == 7) {
     870 + html += '<td>4769</td>';
     871 + } else if (j == 8) {
     872 + html += '<td>4776</td>';
     873 + }
     874 + for (k = 0; k < rowdata.length; k++) {
     875 + if (alerts[k] > 17) {
     876 + html += '<td bgcolor="#ff5aee">' + rowdata[k].split(".")[0] + '</td>';
     877 + } else if (alerts[k] > 16) {
     878 + html += '<td bgcolor="#ff8aee">' + rowdata[k].split(".")[0] + '</td>';
     879 + } else if (alerts[k] > 13) {
     880 + html += '<td bgcolor="#ffbaee">' + rowdata[k].split(".")[0] + '</td>';
     881 + } else if (alerts[k] > 10) {
     882 + html += '<td bgcolor="#ffeaee">' + rowdata[k].split(".")[0] + '</td>';
     883 + } else {
     884 + html += '<td>' + rowdata[k].split(".")[0] + '</td>';
     885 + }
     886 + }
     887 + html += '</tr>';
     888 + }
     889 + }
     890 + }
     891 + html += '</tbody></table></div>';
     892 + 
     893 + var timelineElem = document.getElementById("cy");
     894 + timelineElem.innerHTML = html;
     895 + },
     896 + onError: function(error) {
     897 + console.log("Error: ", error);
     898 + }
     899 + });
     900 + }
     901 + 
     902 + function createAlltimeline() {
     903 + var queryStr = 'MATCH (date:Date) MATCH (user:Username) RETURN date, user';
     904 + createTimeline(queryStr, "all");
     905 + }
     906 + 
     907 + function searchTimeline() {
     908 + var selectVal = document.getElementById("InputSelect").value;
     909 + var setStr = document.getElementById("query-input").value;
     910 + 
     911 + if (selectVal == "Username") {
     912 + whereStr = 'user.user =~ "' + setStr + '" ';
     913 + } else {
     914 + searchError();
     915 + }
     916 + 
     917 + for (i = 1; i <= currentNumber; i++) {
     918 + if (document.getElementById("query-input" + i).value) {
     919 + ruleStr = document.getElementById("InputRule" + i).value;
     920 + if (document.getElementById("InputSelect" + i).value == "Username") {
     921 + whereStr += ruleStr + ' user.user =~ "' + document.getElementById("query-input" + i).value + '" ';
     922 + } else {
     923 + searchError();
     924 + }
     925 + }
     926 + }
     927 + var queryStr = 'MATCH (date:Date) MATCH (user:Username) WHERE (' + whereStr + ') RETURN date, user';
     928 + createTimeline(queryStr, "search");
     929 + }
     930 + 
     931 + function searchError() {
     932 + var elemMsg = document.getElementById("error");
     933 + elemMsg.innerHTML =
     934 + '<div class="alert alert-warning alert-dismissible" role="alert"><button type="button" class="close" data-dismiss="alert" aria-label="close">\
     935 + <span aria-hidden="true">×</span></button><strong>WARNING</strong>: Search failed!</div>';
     936 + }
     937 + 
     938 + function file_upload() {
     939 + var upfile = document.getElementById("lefile");
     940 + var timezone = document.getElementById("utcTime").value;
     941 + document.getElementById("uploadBar").innerHTML = '';
     942 + document.getElementById("status").innerHTML = '';
     943 + 
     944 + var formData = new FormData();
     945 + formData.append("file", upfile.files[0]);
     946 + formData.append("timezone", timezone);
     947 + var xmlhttp = new XMLHttpRequest();
     948 + xmlhttp.upload.addEventListener("progress", progressHandler, false);
     949 + xmlhttp.addEventListener("load", completeHandler, false);
     950 + xmlhttp.addEventListener("error", errorHandler, false);
     951 + xmlhttp.addEventListener("abort", abortHandler, false);
     952 + xmlhttp.open("POST", "upload", true);
     953 + xmlhttp.send(formData);
     954 + }
     955 + 
     956 + function progressHandler(event) {
     957 + var percent = (event.loaded / event.total) * 100;
     958 + document.getElementById("uploadBar").innerHTML = '<h4>Upload ...</h4><div class="progress"><div class="progress-bar progress-bar-striped active" role="progressbar" style="width: ' + Math.round(percent) + '%;">' + Math.round(percent) + '%</div></div>';
     959 + }
     960 + 
     961 + var parse_status = false;
     962 + 
     963 + function completeHandler(event) {
     964 + if (event.target.responseText == "FAIL") {
     965 + document.getElementById("status").innerHTML = '<div class="alert alert-danger"><strong>ERROR</strong>: Upload Failed!</div>';
     966 + }
     967 + if (event.target.responseText == "SUCCESS") {
     968 + parse_status = false
     969 + document.getElementById("uploadBar").innerHTML = '<h4>Upload ...</h4><div class="progress"><div class="progress-bar progress-bar-success progress-bar-striped" role="progressbar" style="width: 100%;">SUCCESS</div></div>';
     970 + var loop = function() {
     971 + if (parse_status == false) {
     972 + setTimeout(loop, 2000);
     973 + }
     974 + parseEVTX();
     975 + }
     976 + loop();
     977 + }
     978 + }
     979 + 
     980 + function errorHandler(event) {
     981 + document.getElementById("status").innerHTML = '<div class="alert alert-danger"><strong>ERROR</strong>: Upload Failed!</div>';
     982 + }
     983 + 
     984 + function abortHandler(event) {
     985 + document.getElementById("status").innerHTML = '<div class="alert alert-info">Upload Aborted</div>';
     986 + }
     987 + 
     988 + $('input[id=lefile]').change(function() {
     989 + $('#evtx_name').val($(this).val().replace("C:\\fakepath\\", ""));
     990 + });
     991 + 
     992 + function parseEVTX() {
     993 + var xmlhttp2 = new XMLHttpRequest();
     994 + xmlhttp2.open("GET", "/static/logontracer.log");
     995 + xmlhttp2.send();
     996 + xmlhttp2.onreadystatechange = function() {
     997 + if (xmlhttp2.readyState == 4) {
     998 + if (xmlhttp2.status == 200) {
     999 + var logdata = xmlhttp2.responseText.split(/\r\n|\r|\n/);
     1000 + var allrecode = logdata[3].split(" ")[5].replace(".", "");
     1001 + var nowdata = logdata[logdata.length - 2];
     1002 + if (nowdata.indexOf("Now loading") != -1) {
     1003 + var recordnum = nowdata.split(" ")[3];
     1004 + var percent = (recordnum / allrecode) * 100;
     1005 + document.getElementById("uploadBar").innerHTML = '<h4>Parsing ...</h4><div class="progress"><div class="progress-bar progress-bar-striped active" role="progressbar" style="width: ' + Math.round(percent) + '%;">' + Math.round(percent) + '%</div></div>';
     1006 + } else if (nowdata.indexOf("Script end") != -1) {
     1007 + document.getElementById("uploadBar").innerHTML = '<h4>Parsing ...</h4><div class="progress"><div class="progress-bar progress-bar-success progress-bar-striped" role="progressbar" style="width: 100%;">SUCCESS</div></div>';
     1008 + parse_status = true;
     1009 + } else if (nowdata.indexOf("[!]") != -1) {
     1010 + document.getElementById("status").innerHTML = '<div class="alert alert-danger"><strong>ERROR</strong>: EVTX parse Failed!</div>';
     1011 + parse_status = true;
     1012 + }
     1013 + } else {
     1014 + document.getElementById("status").innerHTML = '<div class="alert alert-danger"><strong>ERROR</strong>: logontracer.log status = ' + xmlhttp2.status + '</div>';
     1015 + parse_status = true;
     1016 + }
     1017 + }
     1018 + }
     1019 + }
     1020 + </script>
     1021 +</body>
     1022 + 
     1023 +</html>
     1024 + 
Please wait...
Page is in error, reload to recover