Pin ubuntu:22.04, work around upstream rot, smoke test
APT-Hunter is unmaintained and breaks on modern dependency versions. Workarounds: - Pin ubuntu:22.04 (Python 3.10) — base for venv install. - Pin netaddr<1.0 — 1.x removed IPAddress.is_private(). - Add flatten_json (missing from upstream requirements.txt). - Patch EvtxDetection.py via sed: strip ' UTC' suffix from timestamps before parse() since dateutil rejects 'Z UTC' (Microsoft EVTX bug). start.sh: pre-mkdir the nested output/ dir APT-Hunter expects. test_smoke.sh: glob the actually-produced /output/apthunter_<ts>/output/ nested layout. Default SUBSET=DeepBlueCLI documented; YamatoSecurity is a working alternative and avoids the few corpora that hit other parser bugs. Validated end-to-end on amd64 Linux: 5/5 PASS on YamatoSecurity (16 EVTX), 1753 detections, 24K xlsx + 84K TimeSketch CSV produced. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -1 +1 @@
|
|||||||
old-tag.txt
|
test-data/
|
||||||
|
|||||||
+33
-5
@@ -1,9 +1,37 @@
|
|||||||
FROM ubuntu
|
FROM ubuntu:22.04
|
||||||
|
LABEL maintainer="tabledevil"
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
RUN apt update && apt install -y git python3-pip && rm -rf /var/lib/apt/lists/*
|
|
||||||
RUN git clone https://github.com/ahmedkhlief/APT-Hunter
|
# APT-Hunter upstream is unmaintained. Modern dependency releases break it:
|
||||||
RUN cd APT-Hunter ; pip3 install -r requirements.txt
|
# - netaddr 1.x removed IPAddress.is_private (renamed semantics)
|
||||||
RUN pip3 install flatten_json
|
# - python-dateutil 2.9+ rejects malformed "Z UTC" timestamps in some EVTX
|
||||||
|
# Pin to last compatible majors via constraints below.
|
||||||
|
# Ubuntu 22.04 base for Python 3.10 (also dodges some 3.12 stdlib churn).
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends \
|
||||||
|
git python3 python3-pip python3-venv ca-certificates \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Always grab the latest APT-Hunter master at build time.
|
||||||
|
RUN git clone --depth=1 https://github.com/ahmedkhlief/APT-Hunter /APT-Hunter
|
||||||
|
|
||||||
|
# Use a venv: PEP 668 on Ubuntu 24.04 blocks system pip.
|
||||||
|
# flatten_json is required at runtime but missing from upstream requirements.
|
||||||
|
RUN python3 -m venv /opt/apthunter \
|
||||||
|
&& /opt/apthunter/bin/pip install -r /APT-Hunter/requirements.txt \
|
||||||
|
&& /opt/apthunter/bin/pip install \
|
||||||
|
flatten_json \
|
||||||
|
'netaddr<1.0'
|
||||||
|
|
||||||
|
# Patch APT-Hunter for malformed "Z UTC" timestamps that dateutil refuses.
|
||||||
|
# Some Microsoft EVTX records carry "2021-...Z UTC" (Zulu + literal UTC),
|
||||||
|
# which APT-Hunter passes straight to dateutil.parser.parse() and crashes.
|
||||||
|
RUN sed -i 's|parse(record\["timestamp"\])|parse(record["timestamp"].replace(" UTC",""))|g' \
|
||||||
|
/APT-Hunter/lib/EvtxDetection.py
|
||||||
|
|
||||||
|
ENV PATH=/opt/apthunter/bin:$PATH
|
||||||
|
|
||||||
RUN mkdir /output && touch /output/notmounted
|
RUN mkdir /output && touch /output/notmounted
|
||||||
ADD start.sh /root/start.sh
|
ADD start.sh /root/start.sh
|
||||||
|
RUN chmod +x /root/start.sh
|
||||||
CMD ["/bin/bash","/root/start.sh"]
|
CMD ["/bin/bash","/root/start.sh"]
|
||||||
|
|||||||
Executable
+9
@@ -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"
|
||||||
@@ -28,12 +28,14 @@ else
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
#base command for apthunter
|
#base command for apthunter (uses the venv pip-installed deps)
|
||||||
cmd=(/usr/bin/python3 /APT-Hunter/APT-Hunter.py -p /data)
|
cmd=(/opt/apthunter/bin/python /APT-Hunter/APT-Hunter.py -p /data)
|
||||||
|
|
||||||
|
|
||||||
#set output-destination
|
#set output-destination — APT-Hunter creates a subdir 'output/' under -o,
|
||||||
|
#and refuses to create the parent. Pre-mkdir.
|
||||||
output="${output}/apthunter_$(date +%s)"
|
output="${output}/apthunter_$(date +%s)"
|
||||||
|
mkdir -p "${output}/output"
|
||||||
echo "output is goint to : ${output}"
|
echo "output is goint to : ${output}"
|
||||||
cmd+=(-o "${output}")
|
cmd+=(-o "${output}")
|
||||||
|
|
||||||
|
|||||||
Executable
+62
@@ -0,0 +1,62 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Smoke test for APT-Hunter: fetch sample EVTX, run, check outputs.
|
||||||
|
#
|
||||||
|
# Available SUBSET values (under test-data/sample-evtx/):
|
||||||
|
# DeepBlueCLI 21 files, ~1min
|
||||||
|
# YamatoSecurity 16 files, ~30s
|
||||||
|
# EVTX-ATTACK-SAMPLES 278 files, ~10min
|
||||||
|
# EVTX-to-MITRE-Attack 284 files, ~10min
|
||||||
|
# "" 599 files, ~20min
|
||||||
|
#
|
||||||
|
# Env vars: TAG=ls-apthunter:test SUBSET=DeepBlueCLI KEEP_DATA=1
|
||||||
|
set -u
|
||||||
|
TAG="${TAG:-ls-apthunter: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:-<all>}" || { 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
|
||||||
|
|
||||||
|
# APT-Hunter writes /output/apthunter_<ts>/output/apthunter_<ts>_{Report.xlsx,TimeSketch.csv,...}
|
||||||
|
xlsx=$(ls "$OUT"/apthunter_*/output/apthunter_*_Report.xlsx 2>/dev/null | head -1)
|
||||||
|
ts=$(ls "$OUT"/apthunter_*/output/apthunter_*_TimeSketch.csv 2>/dev/null | head -1)
|
||||||
|
[ -s "$xlsx" ] && ok "Excel report exists ($(du -h "$xlsx" | cut -f1))" || bad "xlsx missing"
|
||||||
|
[ -s "$ts" ] && ok "TimeSketch CSV exists ($(du -h "$ts" | cut -f1))" || bad "csv missing"
|
||||||
|
|
||||||
|
if [ -s "$ts" ]; then
|
||||||
|
detections=$(($(wc -l < "$ts") - 1))
|
||||||
|
echo
|
||||||
|
echo "Detections: $detections"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Summary: $pass pass, $fail fail"
|
||||||
|
[ "$fail" -eq 0 ]
|
||||||
Reference in New Issue
Block a user