diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6350f0f --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +test-data/ diff --git a/Dockerfile b/Dockerfile index 843dab5..e84cf93 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,31 @@ -FROM alpine as builder +FROM alpine:3.23 AS builder +LABEL maintainer="tabledevil" RUN apk add --no-cache rust cargo python3 py3-pip alpine-sdk git bash ENV PATH=/root/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin -RUN git clone https://github.com/wagga40/Zircolite /opt/zircolite + +# Always grab the current Zircolite master + rules at build time. +RUN git clone --depth=1 https://github.com/wagga40/Zircolite /opt/zircolite + ENV PYTHONDONTWRITEBYTECODE=1 ADD pip.conf /etc/pip.conf -RUN cd /opt/zircolite && pip install -r requirements.txt + +# Use a venv: PEP 668 on modern Alpine blocks system-pip. +RUN python3 -m venv /opt/zircolite/venv \ + && /opt/zircolite/venv/bin/pip install -r /opt/zircolite/requirements.txt + WORKDIR /data RUN mkdir /output && touch /output/notmounted -RUN python3 /opt/zircolite/zircolite.py -U --rules /opt/zircolite/rules/ + +# Refresh sigma rules to latest at build time. +RUN /opt/zircolite/venv/bin/python /opt/zircolite/zircolite.py -U --rules /opt/zircolite/rules/ + ADD start.sh /root/start.sh +RUN chmod +x /root/start.sh + +FROM alpine:3.23 +RUN apk add --no-cache python3 bash +COPY --from=builder /opt/zircolite /opt/zircolite +COPY --from=builder /root/start.sh /root/start.sh +RUN mkdir -p /output && touch /output/notmounted +WORKDIR /data CMD ["/bin/bash","/root/start.sh"] diff --git a/fetch-test-data.sh b/fetch-test-data.sh new file mode 100755 index 0000000..87246af --- /dev/null +++ b/fetch-test-data.sh @@ -0,0 +1,9 @@ +#!/bin/bash +# Pull the upstream EVTX sample bundle (Yamato Security's curated bundle of +# DeepBlueCLI, EVTX-ATTACK-SAMPLES, EVTX-to-MITRE-Attack, plus their own). +set -e +cd "$(dirname "$0")" +mkdir -p test-data +[ -d test-data/sample-evtx ] || \ + git clone --depth=1 https://github.com/Yamato-Security/hayabusa-sample-evtx.git test-data/sample-evtx +echo "ready: test-data/sample-evtx" diff --git a/start.sh b/start.sh index d5f596f..d09dd16 100644 --- a/start.sh +++ b/start.sh @@ -32,4 +32,13 @@ fi outputf="${output}/zircolite_$(date +%s)" echo "output is goint to : ${outputf}" -python3 /opt/zircolite/zircolite.py --evtx /data --rules /opt/zircolite/rules/rules_windows_generic.json -c /opt/zircolite/config/fieldMappings.json -o "${outputf}.json" -t "${output}/tmp" -l "${outputf}.log" +# --evtx ; -o ; -l ; -c . +# Older start.sh passed -t , but in current zircolite -t means +# --template (Jinja2) which expects --templateOutput as well. Tmp is no +# longer user-controllable so we drop it. +/opt/zircolite/venv/bin/python /opt/zircolite/zircolite.py \ + --evtx /data \ + --rules /opt/zircolite/rules/rules_windows_generic.json \ + -c /opt/zircolite/config/fieldMappings.yaml \ + -o "${outputf}.json" \ + -l "${outputf}.log" diff --git a/test_smoke.sh b/test_smoke.sh new file mode 100755 index 0000000..a5079fd --- /dev/null +++ b/test_smoke.sh @@ -0,0 +1,61 @@ +#!/bin/bash +# Smoke test for zircolite: fetch sample EVTX, run, check outputs, print stats. +# +# Available SUBSET values (under test-data/sample-evtx/): +# DeepBlueCLI 21 files, ~30s — well-known PowerShell + auth attacks +# YamatoSecurity 16 files, ~20s — Yamato Security's own samples +# EVTX-ATTACK-SAMPLES 278 files, ~5min — sbousseaden's MITRE-mapped corpus +# EVTX-to-MITRE-Attack 284 files, ~5min — mdecrevoisier's MITRE-mapped corpus +# "" 599 files, ~10min — full bundle +# +# Env vars: TAG=ls-zircolite:test SUBSET=DeepBlueCLI KEEP_DATA=1 +set -u +TAG="${TAG:-ls-zircolite:test}" +SUBSET="${SUBSET:-DeepBlueCLI}" +KEEP_DATA="${KEEP_DATA:-0}" + +cd "$(dirname "$0")" +ROOT="$(pwd)" +DATA="$ROOT/test-data/sample-evtx" +OUT="$(mktemp -d)" +trap 'rm -rf "$OUT"; [ "$KEEP_DATA" = 0 ] && rm -rf "$ROOT/test-data"' EXIT + +pass=0; fail=0 +ok() { echo "PASS $1"; pass=$((pass+1)); } +bad() { echo "FAIL $1"; fail=$((fail+1)); } + +if docker image inspect "$TAG" >/dev/null 2>&1; then + ok "image $TAG present" +else + bad "image $TAG not present"; exit 1 +fi + +if [ ! -d "$DATA" ]; then + echo "Fetching sample EVTX..." + ./fetch-test-data.sh >/dev/null +fi +SCAN="$DATA/$SUBSET"; [ -z "$SUBSET" ] && SCAN="$DATA" +n=$(find "$SCAN" -name "*.evtx" | wc -l | tr -d ' ') +[ "$n" -gt 0 ] && ok "found $n EVTX in ${SUBSET:-}" || { bad "no EVTX"; exit 1; } + +echo "Running scan..." +if docker run --rm --network=none -v "$SCAN:/data:ro" -v "$OUT:/output" "$TAG" >"$OUT/.run.log" 2>&1; then + ok "container exited cleanly" +else + bad "container non-zero"; tail -20 "$OUT/.run.log" +fi + +json=$(ls "$OUT"/zircolite_*.json 2>/dev/null | head -1) +log=$(ls "$OUT"/zircolite_*.log 2>/dev/null | head -1) +[ -s "$json" ] && ok "JSON report exists ($(du -h "$json" | cut -f1))" || bad "JSON missing/empty" +[ -s "$log" ] && ok "scan log exists" || bad "log missing" + +if [ -s "$json" ]; then + hits=$(grep -oE '"title"' "$json" | wc -l | tr -d ' ') + echo + echo "Sigma rule hits: $hits" +fi + +echo +echo "Summary: $pass pass, $fail fail" +[ "$fail" -eq 0 ]