#!/bin/bash # # Copyright 2014,2015,2016,2017,2018,2019,2020 Security Onion Solutions, LLC # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . {%- 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') %} . /usr/sbin/so-common function usage { cat << EOF Usage: $0 [pcap-file-2] [pcap-file-N] Imports one or more PCAP files onto a sensor node. The PCAP traffic will be analyzed and made available for review in the Security Onion toolset. EOF } function pcapinfo() { PCAP=$1 ARGS=$2 docker run --rm -v "$PCAP:/input.pcap" --entrypoint capinfos {{ MANAGER }}:5000/{{ IMAGEREPO }}/so-pcaptools:{{ VERSION }} /input.pcap $ARGS } function pcapfix() { PCAP=$1 PCAP_OUT=$2 docker run --rm -v "$PCAP:/input.pcap" -v $PCAP_OUT:$PCAP_OUT --entrypoint pcapfix {{ MANAGER }}:5000/{{ IMAGEREPO }}/so-pcaptools:{{ VERSION }} /input.pcap -o $PCAP_OUT > /dev/null 2>&1 } function suricata() { PCAP=$1 HASH=$2 NSM_PATH=/nsm/import/${HASH}/suricata mkdir -p $NSM_PATH chown suricata:socore $NSM_PATH LOG_PATH=/opt/so/log/suricata/import/${HASH} mkdir -p $LOG_PATH chown suricata:suricata $LOG_PATH docker run --rm \ -v /opt/so/conf/suricata/suricata.yaml:/etc/suricata/suricata.yaml:ro \ -v /opt/so/conf/suricata/threshold.conf:/etc/suricata/threshold.conf:ro \ -v /opt/so/conf/suricata/rules:/etc/suricata/rules:ro \ -v ${LOG_PATH}:/var/log/suricata/:rw \ -v ${NSM_PATH}/:/nsm/:rw \ -v "$PCAP:/input.pcap:ro" \ -v /opt/so/conf/suricata/bpf:/etc/suricata/bpf:ro \ {{ MANAGER }}:5000/{{ IMAGEREPO }}/so-suricata:{{ VERSION }} \ --runmode single -k none -r /input.pcap > $LOG_PATH/console.log 2>&1 } function zeek() { PCAP=$1 HASH=$2 NSM_PATH=/nsm/import/${HASH}/zeek mkdir -p $NSM_PATH/logs mkdir -p $NSM_PATH/extracted mkdir -p $NSM_PATH/spool chown -R zeek:socore $NSM_PATH docker run --rm \ -v $NSM_PATH/logs:/nsm/zeek/logs:rw \ -v $NSM_PATH/spool:/nsm/zeek/spool:rw \ -v $NSM_PATH/extracted:/nsm/zeek/extracted:rw \ -v "$PCAP:/input.pcap:ro" \ -v /opt/so/conf/zeek/local.zeek:/opt/zeek/share/zeek/site/local.zeek:ro \ -v /opt/so/conf/zeek/node.cfg:/opt/zeek/etc/node.cfg:ro \ -v /opt/so/conf/zeek/zeekctl.cfg:/opt/zeek/etc/zeekctl.cfg:ro \ -v /opt/so/conf/zeek/policy/securityonion:/opt/zeek/share/zeek/policy/securityonion:ro \ -v /opt/so/conf/zeek/policy/custom:/opt/zeek/share/zeek/policy/custom:ro \ -v /opt/so/conf/zeek/policy/cve-2020-0601:/opt/zeek/share/zeek/policy/cve-2020-0601:ro \ -v /opt/so/conf/zeek/policy/intel:/opt/zeek/share/zeek/policy/intel:rw \ -v /opt/so/conf/zeek/bpf:/opt/zeek/etc/bpf:ro \ --entrypoint /opt/zeek/bin/zeek \ -w /nsm/zeek/logs \ {{ MANAGER }}:5000/{{ IMAGEREPO }}/so-zeek:{{ VERSION }} \ -C -r /input.pcap local > $NSM_PATH/logs/console.log 2>&1 } # if no parameters supplied, display usage if [ $# -eq 0 ]; then usage exit 1 fi # ensure this is a sensor node if [ ! -d /opt/so/conf/suricata ]; then echo "This command must be run on a sensor node." exit 3 fi # verify that all parameters are files for i in "$@"; do if ! [ -f "$i" ]; then usage echo "\"$i\" is not a valid file!" exit 2 fi done # track if we have any valid or invalid pcaps INVALID_PCAPS="no" VALID_PCAPS="no" # 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" # paths must be quoted in case they include spaces for PCAP in "$@"; do PCAP=$(/usr/bin/realpath "$PCAP") echo "Processing Import: ${PCAP}" echo "- verifying file" if ! pcapinfo "${PCAP}" > /dev/null 2>&1; then # try to fix pcap and then process the fixed pcap directly PCAP_FIXED=`mktemp /tmp/so-import-pcap-XXXXXXXXXX.pcap` echo "- attempting to recover corrupted PCAP file" pcapfix "${PCAP}" "${PCAP_FIXED}" PCAP="${PCAP_FIXED}" TEMP_PCAPS+=(${PCAP_FIXED}) fi # generate a unique hash to assist with dedupe checks HASH=$(md5sum "${PCAP}" | awk '{ print $1 }') HASH_DIR=/nsm/import/${HASH} echo "- assigning unique identifier to import: $HASH" if [ -d $HASH_DIR ]; then echo "- this PCAP has already been imported; skipping" INVALID_PCAPS="yes" elif pcapinfo "${PCAP}" |egrep -q "Last packet time: 1970-01-01|Last packet time: n/a"; then echo "- this PCAP file is invalid; skipping" INVALID_PCAPS="yes" else VALID_PCAPS="yes" PCAP_DIR=$HASH_DIR/pcap mkdir -p $PCAP_DIR # generate IDS alerts and write them to standard pipeline echo "- analyzing traffic with Suricata" suricata "${PCAP}" $HASH # generate Zeek logs and write them to a unique subdirectory in /nsm/import/bro/ # since each run writes to a unique subdirectory, there is no need for a lock file echo "- analyzing traffic with Zeek" zeek "${PCAP}" $HASH START=$(pcapinfo "${PCAP}" -a |grep "First packet time:" | awk '{print $4}') END=$(pcapinfo "${PCAP}" -e |grep "Last packet time:" | awk '{print $4}') echo "- saving PCAP data spanning dates $START through $END" # 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 cp -f "${PCAP}" "${PCAP_DIR}"/data.pcap chmod 644 "${PCAP_DIR}"/data.pcap fi # end of valid pcap echo done # end of for-loop processing pcap files # remove temp files echo "Cleaning up:" for TEMP_PCAP in ${TEMP_PCAPS[@]}; do echo "- removing temporary pcap $TEMP_PCAP" rm -f $TEMP_PCAP done # output final messages if [ "$INVALID_PCAPS" = "yes" ]; then echo echo "Please note! One or more pcaps was invalid! You can scroll up to see which ones were invalid." fi START_OLDEST_SLASH=$(echo $START_OLDEST | sed -e 's/-/%2F/g') END_NEWEST_SLASH=$(echo $END_NEWEST | sed -e 's/-/%2F/g') if [ "$VALID_PCAPS" = "yes" ]; then cat << EOF Import complete! You can use the following hyperlink to view data in the time range of your import. You can triple-click to quickly highlight the entire hyperlink and you can then copy it into your browser: https://{{ URLBASE }}/#/hunt?q=import.id:${HASH}%20%7C%20groupby%20event.module%20event.dataset&t=${START_OLDEST_SLASH}%2000%3A00%3A00%20AM%20-%20${END_NEWEST_SLASH}%2000%3A00%3A00%20AM&z=UTC or you can manually set your Time Range to be (in UTC): From: $START_OLDEST To: $END_NEWEST Please note that it may take 30 seconds or more for events to appear in Hunt. EOF fi