ARTEX host triage
English · 한국어
한국어:
artex_host_triage.py는 ARTEX 가 돌았다고 의심되는 호스트 한 대에서 직접 돌리는 읽기 전용 분류(triage) 스크립트입니다. SIEM(Sigma)·네트워크 센서(Suricata)·위협 인텔리전스 플랫폼(지표 CSV·MISP)을 쓰는 방어자 말고, SIEM 없이 의심 호스트의 셸 앞에 선 대응자를 위한 것입니다. 리슨 포트·기록 프록시 아티팩트·로그 마커·PostgreSQL 스키마를 저장소 소스에 근거해 점검하고, 각 발견에 같은 한계(포트는 바꿀 수 있음, CA 파일명은 단독 mitmproxy 와 공유됨 등)를 함께 적습니다. 자신이 소유하거나 서면 허가를 받은 호스트에만 사용하십시오. 한국어 전체 문서는 README.ko.md 를 보십시오.
A read-only triage helper you run on a single suspected host to answer "did ARTEX run here?" from local state. The rest of this directory serves defenders who run a SIEM (Sigma), a network sensor (Suricata), or a threat-intelligence platform (the indicators). This script serves the other responder: the one at a host's shell, with no SIEM, who needs a quick, defensible answer from what is on the box.
It operationalizes the same fingerprints the rest of the directory ships, plus the three host/DB
indicators the indicator list deliberately carries without a Sigma
rule because they are not log- or network-observable and can only be checked on the host itself
(server-listen-port, recording-proxy-endpoint, postgres-exploration-schema).
What it checks
Every check is grounded in a string or path verified in this repository's source, and every finding carries the same honest caveat as the matching Sigma rule or indicator row.
- Listening ports —
:8787(admin UI) and127.0.0.1:8788(recording proxy), the defaults of the--addr/--proxyflags incmd/artex/main.go. Parsed fromss/netstat/lsofon the live host, or from a file you pass with--ports-from. - Recording-proxy artifacts — the MITM CA the recorder writes on first start,
<data-dir>/traffic/_ca/mitmproxy-ca-cert.pem, and the sibling_index/index.sqliteand_blobs/(traffic/traffic.go; the data directory default isdata/next to the binary). The CA is the trust anchor of an adversary-in-the-middle traffic recorder (ATT&CK T1557). - Log markers — the enrichment prober UA
artex-enrich/1.0(enrich/enrich.go), the self-update egress UAartex-selfupdate(selfupdate/github.go), and the platform-guard audit marker (guard/guard.go; kept verbatim, including the non-ASCII framing, so the grep matches) in the log file(s) you point it at. Rotated logs compressed as.gz/.bz2/.xzare decompressed and scanned too, so the host's log history is covered; a format with no standard-library codec (.zst/.lz4) is reported as skipped rather than silently treated as clean — decompress it first orgrepit by hand. - PostgreSQL exploration schema — the dual-graph tables (
exploration_nodes/_edges/_anchorswithassets/companies/activityand theagent_promptsseed) in the ARTEX store (db/schema.sql). Run against a DSN withpsqlif available; otherwise the script prints the exact read-only query for you to run by hand. - Process env injection — a running process whose environment carries a proxy var (
HTTP_PROXY/HTTPS_PROXY/ALL_PROXY) together with a toolchain CA-trust var (SSL_CERT_FILE/CURL_CA_BUNDLE/REQUESTS_CA_BUNDLE/GIT_SSL_CAINFO/NODE_EXTRA_CA_CERTS) pointing at amitmproxy-ca-cert.pem. ARTEX injects exactly these into every worker tool it spawns (agent/worker.goproxyEnv, asserted byagent/proxyenv_test.go). The variable names are hard-coded in the source (only the values are configurable), so this tell survives an operator renaming the binary or changing the ports — a stronger signal than the bare listen port. Read from/procon the live Linux host, or from a captured dump with--proc-from. A proxy and a mitmproxy CA together are reported high; a mitmproxy CA alone, or the ARTEX default proxy endpoint (127.0.0.1:8788) alone, is medium; a corporate proxy with no mitmproxy CA is deliberately not flagged.
A hit is a triage lead, not an attribution, and the absence of every finding is not a clean bill of health: an operator can rename the binary, move the data directory, or change the ports.
Platform support
The script is pure Python 3 (standard library only), so it runs wherever Python 3 does — verified on Linux (the CI self-test) and macOS. Two checks are OS-specific, and both degrade cleanly rather than failing:
- Live port scan — tries
ss, thennetstat, thenlsof, and uses the first that produces output. On Linux that isss/netstat; on macOS/BSD, wheressis absent andnetstatdoes not take the Linux-ltnpflags (it exits with empty output), it falls through tolsof -nP -iTCP -sTCP:LISTEN, parsed the same way. Pass--ports-fromto read a saved listing instead of scanning live. - Live process-env scan — reads
/proc, so it runs only on Linux. On a host without/proc(macOS/BSD) it is reported as skipped, not clean; capture a dump on the Linux host and pass it with--proc-from(see Usage).
The remaining checks — recording-proxy artifacts, log markers, and the PostgreSQL schema — read the
filesystem, log files, and (with a DSN) psql, so they are OS-independent.
Usage
# check a host end to end
detections/triage/artex_host_triage.py \
--data-dir /opt/artex/data \
--log /var/log/syslog --log-dir /var/log/artex \
--pg-dsn "$ARTEX_PG_DSN"
# machine-readable findings, and exit non-zero if anything fired
detections/triage/artex_host_triage.py --data-dir /opt/artex/data --json --exit-code
# offline / forensic image: read a captured process-environment dump
# make the dump on the host with:
# for p in /proc/[0-9]*; do echo "# $p"; tr '\0' '\n' < "$p/environ"; echo; done > proc_env_dump.txt
detections/triage/artex_host_triage.py --proc-from proc_env_dump.txt
# reproducible fixture test (no host state touched)
detections/triage/artex_host_triage.py --self-test
The script is pure Python 3 standard library: no install, no network, and it writes nothing anywhere
except the --self-test's own temporary directory. It reads host state (open ports, a data directory,
log files, and — only if you pass a DSN — the database) and prints what it found. Exit code is 0 by
default (triage, not a gate); pass --exit-code to make it 1 when any indicator fired.
How this stays honest
The --self-test builds a synthetic host — a data directory with a planted CA, index, and blob store; a
log containing each marker; a port listing; and a captured process-environment dump — and asserts every
check fires on it, then asserts a clean host, a benign log, and a corporate-proxy process produce zero
findings (no false positives). It is wired into the
detections CI workflow and re-run by
detections/tests/run-all.sh, so a change that breaks a check, or that drifts an
indicator away from the source string it greps for, fails the merge gate. A detection you cannot run is
only a claim.