#!/bin/bash # # Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one # or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at # https://securityonion.net/license; you may not use this file except in compliance with the # Elastic License 2.0. {%- set MANAGER = salt['grains.get']('master') %} {%- set VERSION = salt['pillar.get']('global:soversion') %} {%- set IMAGEREPO = salt['pillar.get']('global:imagerepo') %} {%- set MANAGERIP = salt['pillar.get']('global:managerip') %} {%- set URLBASE = salt['pillar.get']('global:url_base') %} {%- set ES_USER = salt['pillar.get']('elasticsearch:auth:users:so_elastic_user:user', '') %} {%- set ES_PASS = salt['pillar.get']('elasticsearch:auth:users:so_elastic_user:pass', '') %} INDEX_DATE=$(date +'%Y.%m.%d') LOG_FILE=/nsm/import/evtx-import.log . /usr/sbin/so-common function usage { cat << EOF Usage: $0 [options] [evtx-file-2] [evtx-file-*] Imports one or more evtx files into Security Onion. The evtx files will be analyzed and made available for review in the Security Onion toolset. Options: --json Outputs summary in JSON format. Implies --quiet. --quiet Silences progress information to stdout. --shift Adds a time shift. Accepts a single argument that is intended to be the date of the last record, and shifts the dates of the previous records accordingly. Ex. sudo so-import-evtx --shift "2023-08-01 01:01:01" example.evtx EOF } quiet=0 json=0 INPUT_FILES= while [[ $# -gt 0 ]]; do param=$1 shift case "$param" in --json) json=1 quiet=1 ;; --quiet) quiet=1 ;; --shift) SHIFTDATE=$1 shift ;; -*) echo "Encountered unexpected parameter: $param" usage exit 1 ;; *) if [[ "$INPUT_FILES" != "" ]]; then INPUT_FILES="$INPUT_FILES $param" else INPUT_FILES="$param" fi ;; esac done function status { msg=$1 [[ $quiet -eq 1 ]] && return echo "$msg" } function evtx2es() { EVTX=$1 HASH=$2 SHIFTDATE=$3 docker run --rm \ -e "SHIFTTS=$SHIFTDATE" \ -v "$EVTX:/tmp/data.evtx" \ -v "/nsm/import/$HASH/evtx/:/tmp/evtx/" \ -v "/nsm/import/$HASH/evtx-end_newest:/tmp/newest" \ -v "/nsm/import/$HASH/evtx-start_oldest:/tmp/oldest" \ --entrypoint "/evtx_calc_timestamps.sh" \ {{ MANAGER }}:5000/{{ IMAGEREPO }}/so-pcaptools:{{ VERSION }} >> $LOG_FILE 2>&1 } # if no parameters supplied, display usage if [ "$INPUT_FILES" == "" ]; then usage exit 1 fi # ensure this is a Manager node require_manager @> /dev/null # verify that all parameters are files for i in $INPUT_FILES; do if ! [ -f "$i" ]; then echo "\"$i\" is not a valid file!" exit 2 fi done # track oldest start and newest end so that we can generate the Kibana search hyperlink at the end START_OLDEST="2050-12-31" END_NEWEST="1971-01-01" INVALID_EVTXS_COUNT=0 VALID_EVTXS_COUNT=0 SKIPPED_EVTXS_COUNT=0 # paths must be quoted in case they include spaces for EVTX in $INPUT_FILES; do EVTX=$(/usr/bin/realpath "$EVTX") status "Processing Import: ${EVTX}" if ! [ -z "$SHIFTDATE" ]; then status "- timeshifting logs to end date of $SHIFTDATE" fi # generate a unique hash to assist with dedupe checks HASH=$(md5sum "${EVTX}" | awk '{ print $1 }') HASH_DIR=/nsm/import/${HASH} status "- assigning unique identifier to import: $HASH" if [[ "$HASH_FILTERS" == "" ]]; then HASH_FILTERS="import.id:${HASH}" HASHES="${HASH}" else HASH_FILTERS="$HASH_FILTERS%20OR%20import.id:${HASH}" HASHES="${HASHES} ${HASH}" fi if [ -d $HASH_DIR ]; then status "- this EVTX has already been imported; skipping" SKIPPED_EVTXS_COUNT=$((SKIPPED_EVTXS_COUNT + 1)) else # create EVTX directory EVTX_DIR=$HASH_DIR/evtx mkdir -p $EVTX_DIR # create import timestamp files for i in evtx-start_oldest evtx-end_newest; do if ! [ -f "$i" ]; then touch /nsm/import/$HASH/$i fi done # import evtx and write them to import ingest pipeline status "- importing logs to Elasticsearch..." evtx2es "${EVTX}" $HASH "$SHIFTDATE" if [[ $? -ne 0 ]]; then INVALID_EVTXS_COUNT=$((INVALID_EVTXS_COUNT + 1)) status "- WARNING: This evtx file may not have fully imported successfully" else VALID_EVTXS_COUNT=$((VALID_EVTXS_COUNT + 1)) fi cp -f "${EVTX}" "${EVTX_DIR}"/data.evtx chmod 644 "${EVTX_DIR}"/data.evtx fi # end of valid evtx # determine start and end and make sure they aren't reversed START=$(cat /nsm/import/$HASH/evtx-start_oldest) END=$(cat /nsm/import/$HASH/evtx-end_newest) START_EPOCH=`date -d "$START" +"%s"` END_EPOCH=`date -d "$END" +"%s"` if [ "$START_EPOCH" -gt "$END_EPOCH" ]; then TEMP=$START START=$END END=$TEMP fi # compare $START to $START_OLDEST START_COMPARE=$(date -d $START +%s) START_OLDEST_COMPARE=$(date -d $START_OLDEST +%s) if [ $START_COMPARE -lt $START_OLDEST_COMPARE ]; then START_OLDEST=$START fi # compare $ENDNEXT to $END_NEWEST ENDNEXT=`date +%Y-%m-%d --date="$END 1 day"` ENDNEXT_COMPARE=$(date -d $ENDNEXT +%s) END_NEWEST_COMPARE=$(date -d $END_NEWEST +%s) if [ $ENDNEXT_COMPARE -gt $END_NEWEST_COMPARE ]; then END_NEWEST=$ENDNEXT fi status done # end of for-loop processing evtx files # output final messages if [[ $INVALID_EVTXS_COUNT -gt 0 ]]; then status status "Please note! One or more evtx was invalid! You can scroll up to see which ones were invalid." fi START_OLDEST_FORMATTED=`date +%Y-%m-%d --date="$START_OLDEST"` START_OLDEST_SLASH=$(echo $START_OLDEST_FORMATTED | sed -e 's/-/%2F/g') END_NEWEST_SLASH=$(echo $END_NEWEST | sed -e 's/-/%2F/g') if [[ $VALID_EVTXS_COUNT -gt 0 ]] || [[ $SKIPPED_EVTXS_COUNT -gt 0 ]]; then URL="https://{{ URLBASE }}/#/dashboards?q=$HASH_FILTERS%20%7C%20groupby%20-sankey%20event.dataset%20event.category%2a%20%7C%20groupby%20-pie%20event.category%20%7C%20groupby%20-bar%20event.module%20%7C%20groupby%20event.dataset%20%7C%20groupby%20event.module%20%7C%20groupby%20event.category%20%7C%20groupby%20observer.name%20%7C%20groupby%20source.ip%20%7C%20groupby%20destination.ip%20%7C%20groupby%20destination.port&t=${START_OLDEST_SLASH}%2000%3A00%3A00%20AM%20-%20${END_NEWEST_SLASH}%2000%3A00%3A00%20AM&z=UTC" status "Import complete!" status status "Use the following hyperlink to view the imported data. Triple-click to quickly highlight the entire hyperlink and then copy it into a browser:" status status "$URL" status status "or, manually set the Time Range to be (in UTC):" status status "From: $START_OLDEST_FORMATTED To: $END_NEWEST" status status "Note: It can take 30 seconds or more for events to appear in Security Onion Console." RESULT=0 else START_OLDEST= END_NEWEST= URL= RESULT=1 fi if [[ $json -eq 1 ]]; then jq -n \ --arg success_count "$VALID_EVTXS_COUNT" \ --arg fail_count "$INVALID_EVTXS_COUNT" \ --arg skipped_count "$SKIPPED_EVTXS_COUNT" \ --arg begin_date "$START_OLDEST" \ --arg end_date "$END_NEWEST" \ --arg url "$URL" \ --arg hashes "$HASHES" \ '''{ success_count: $success_count, fail_count: $fail_count, skipped_count: $skipped_count, begin_date: $begin_date, end_date: $end_date, url: $url, hash: ($hashes / " ") }''' fi exit $RESULT