From c880c825798ef65d40dc6b43fb4c97f6ef1debe0 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 12 May 2026 12:01:32 +0200 Subject: [PATCH] feat: add PumbaLP/adguardhome-dot collection for DoT scanner detection Parser: PumbaLP/adguardhome-dot-errors - Extracts remote IP from AdGuard Home DoT connection reset errors - Detects port 853 scanners that fail TLS handshake Scenario: PumbaLP/adguardhome-dot-scan - Bans after 5 resets within 10 minutes from same IP - Tested against AdGuard Home v0.107.x in production Closes: #1753 --- .../adguardhome-dot-errors.log | 6 +++ .tests/adguardhome-dot-errors/config.yaml | 7 +++ .tests/adguardhome-dot-errors/parser.assert | 4 ++ .tests/adguardhome-dot-errors/scenario.assert | 1 + .../adguardhome-dot-scan.log | 6 +++ .tests/adguardhome-dot-scan/config.yaml | 7 +++ .tests/adguardhome-dot-scan/parser.assert | 50 +++++++++++++++++++ .tests/adguardhome-dot-scan/scenario.assert | 1 + collections/PumbaLP/adguardhome-dot.yaml | 6 +++ .../PumbaLP/adguardhome-dot-errors.md | 32 ++++++++++++ .../PumbaLP/adguardhome-dot-errors.yaml | 36 +++++++++++++ scenarios/PumbaLP/adguardhome-dot-scan.md | 23 +++++++++ scenarios/PumbaLP/adguardhome-dot-scan.yaml | 25 ++++++++++ 13 files changed, 204 insertions(+) create mode 100644 .tests/adguardhome-dot-errors/adguardhome-dot-errors.log create mode 100644 .tests/adguardhome-dot-errors/config.yaml create mode 100644 .tests/adguardhome-dot-errors/parser.assert create mode 100644 .tests/adguardhome-dot-errors/scenario.assert create mode 100644 .tests/adguardhome-dot-scan/adguardhome-dot-scan.log create mode 100644 .tests/adguardhome-dot-scan/config.yaml create mode 100644 .tests/adguardhome-dot-scan/parser.assert create mode 100644 .tests/adguardhome-dot-scan/scenario.assert create mode 100644 collections/PumbaLP/adguardhome-dot.yaml create mode 100644 parsers/s01-parse/PumbaLP/adguardhome-dot-errors.md create mode 100644 parsers/s01-parse/PumbaLP/adguardhome-dot-errors.yaml create mode 100644 scenarios/PumbaLP/adguardhome-dot-scan.md create mode 100644 scenarios/PumbaLP/adguardhome-dot-scan.yaml diff --git a/.tests/adguardhome-dot-errors/adguardhome-dot-errors.log b/.tests/adguardhome-dot-errors/adguardhome-dot-errors.log new file mode 100644 index 00000000000..607e7df60c0 --- /dev/null +++ b/.tests/adguardhome-dot-errors/adguardhome-dot-errors.log @@ -0,0 +1,6 @@ +2026/04/04 20:41:41.673779 [error] dnsproxy: reading msg proto=tcp err="reading len: read tcp 172.20.2.2:853->85.217.140.42:59208: read: connection reset by peer" +2026/04/04 20:41:42.123456 [error] dnsproxy: reading msg proto=tcp err="reading len: read tcp 172.20.2.2:853->85.217.140.42:59209: read: connection reset by peer" +2026/04/04 20:41:43.234567 [error] dnsproxy: reading msg proto=tcp err="reading len: read tcp 172.20.2.2:853->85.217.140.42:59210: read: connection reset by peer" +2026/04/04 20:41:44.345678 [error] dnsproxy: reading msg proto=tcp err="reading len: read tcp 172.20.2.2:853->85.217.140.42:59211: read: connection reset by peer" +2026/04/04 20:41:45.456789 [error] dnsproxy: reading msg proto=tcp err="reading len: read tcp 172.20.2.2:853->85.217.140.42:59212: read: connection reset by peer" +2026/04/04 20:41:46.567890 [error] dnsproxy: reading msg proto=tcp err="reading len: read tcp 172.20.2.2:853->85.217.140.42:59213: read: connection reset by peer" diff --git a/.tests/adguardhome-dot-errors/config.yaml b/.tests/adguardhome-dot-errors/config.yaml new file mode 100644 index 00000000000..3e05f1cbc2f --- /dev/null +++ b/.tests/adguardhome-dot-errors/config.yaml @@ -0,0 +1,7 @@ +parsers: + - ./parsers/s01-parse/PumbaLP/adguardhome-dot-errors.yaml +scenarios: + - ./scenarios/PumbaLP/adguardhome-dot-scan.yaml +postoverflows: [] +log_file: adguardhome-dot-errors.log +log_type: adguardhome diff --git a/.tests/adguardhome-dot-errors/parser.assert b/.tests/adguardhome-dot-errors/parser.assert new file mode 100644 index 00000000000..8dbbb9da221 --- /dev/null +++ b/.tests/adguardhome-dot-errors/parser.assert @@ -0,0 +1,4 @@ +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][0].Success == true +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][0].Evt.Meta["source_ip"] == "85.217.140.42" +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][0].Evt.Meta["log_type"] == "dot_connection_reset" +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][0].Evt.Meta["service"] == "adguardhome_dot" diff --git a/.tests/adguardhome-dot-errors/scenario.assert b/.tests/adguardhome-dot-errors/scenario.assert new file mode 100644 index 00000000000..03455618a29 --- /dev/null +++ b/.tests/adguardhome-dot-errors/scenario.assert @@ -0,0 +1 @@ +len(results) == 0 diff --git a/.tests/adguardhome-dot-scan/adguardhome-dot-scan.log b/.tests/adguardhome-dot-scan/adguardhome-dot-scan.log new file mode 100644 index 00000000000..16f4d3f8414 --- /dev/null +++ b/.tests/adguardhome-dot-scan/adguardhome-dot-scan.log @@ -0,0 +1,6 @@ +2026/04/04 20:41:41.000000 [error] dnsproxy: reading msg proto=tcp err="reading len: read tcp 172.20.2.2:853->1.2.3.4:59201: read: connection reset by peer" +2026/04/04 20:41:42.000000 [error] dnsproxy: reading msg proto=tcp err="reading len: read tcp 172.20.2.2:853->1.2.3.4:59202: read: connection reset by peer" +2026/04/04 20:41:43.000000 [error] dnsproxy: reading msg proto=tcp err="reading len: read tcp 172.20.2.2:853->1.2.3.4:59203: read: connection reset by peer" +2026/04/04 20:41:44.000000 [error] dnsproxy: reading msg proto=tcp err="reading len: read tcp 172.20.2.2:853->1.2.3.4:59204: read: connection reset by peer" +2026/04/04 20:41:45.000000 [error] dnsproxy: reading msg proto=tcp err="reading len: read tcp 172.20.2.2:853->1.2.3.4:59205: read: connection reset by peer" +2026/04/04 20:41:46.000000 [error] dnsproxy: reading msg proto=tcp err="reading len: read tcp 172.20.2.2:853->1.2.3.4:59206: read: connection reset by peer" diff --git a/.tests/adguardhome-dot-scan/config.yaml b/.tests/adguardhome-dot-scan/config.yaml new file mode 100644 index 00000000000..ddae4ce860c --- /dev/null +++ b/.tests/adguardhome-dot-scan/config.yaml @@ -0,0 +1,7 @@ +parsers: + - ./parsers/s01-parse/PumbaLP/adguardhome-dot-errors.yaml +scenarios: + - ./scenarios/PumbaLP/adguardhome-dot-scan.yaml +postoverflows: [] +log_file: adguardhome-dot-scan.log +log_type: adguardhome diff --git a/.tests/adguardhome-dot-scan/parser.assert b/.tests/adguardhome-dot-scan/parser.assert new file mode 100644 index 00000000000..37ab142a6e3 --- /dev/null +++ b/.tests/adguardhome-dot-scan/parser.assert @@ -0,0 +1,50 @@ +len(results["s01-parse"]["PumbaLP/adguardhome-dot-errors"]) == 6 +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][0].Success == true +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][0].Evt.Parsed["day"] == "04" +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][0].Evt.Parsed["month"] == "04" +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][0].Evt.Parsed["remote_ip"] == "1.2.3.4" +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][0].Evt.Parsed["time"] == "20:41:41.000000" +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][0].Evt.Parsed["year"] == "2026" +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][0].Evt.Meta["log_type"] == "dot_connection_reset" +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][0].Evt.Meta["service"] == "adguardhome_dot" +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][0].Evt.Meta["source_ip"] == "1.2.3.4" +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][0].Evt.Whitelisted == false +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][1].Success == true +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][1].Evt.Parsed["day"] == "04" +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][1].Evt.Parsed["month"] == "04" +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][1].Evt.Parsed["remote_ip"] == "1.2.3.4" +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][1].Evt.Parsed["time"] == "20:41:42.000000" +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][1].Evt.Parsed["year"] == "2026" +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][1].Evt.Meta["log_type"] == "dot_connection_reset" +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][1].Evt.Meta["service"] == "adguardhome_dot" +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][1].Evt.Meta["source_ip"] == "1.2.3.4" +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][1].Evt.Whitelisted == false +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][2].Success == true +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][2].Evt.Parsed["day"] == "04" +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][2].Evt.Parsed["month"] == "04" +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][2].Evt.Parsed["remote_ip"] == "1.2.3.4" +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][2].Evt.Parsed["time"] == "20:41:43.000000" +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][2].Evt.Parsed["year"] == "2026" +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][2].Evt.Meta["log_type"] == "dot_connection_reset" +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][2].Evt.Meta["service"] == "adguardhome_dot" +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][2].Evt.Meta["source_ip"] == "1.2.3.4" +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][2].Evt.Whitelisted == false +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][3].Success == true +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][3].Evt.Parsed["day"] == "04" +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][3].Evt.Parsed["month"] == "04" +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][3].Evt.Parsed["remote_ip"] == "1.2.3.4" +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][3].Evt.Parsed["time"] == "20:41:44.000000" +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][3].Evt.Parsed["year"] == "2026" +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][3].Evt.Meta["log_type"] == "dot_connection_reset" +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][3].Evt.Meta["service"] == "adguardhome_dot" +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][3].Evt.Meta["source_ip"] == "1.2.3.4" +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][3].Evt.Whitelisted == false +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][4].Success == true +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][4].Evt.Parsed["day"] == "04" +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][4].Evt.Parsed["month"] == "04" +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][4].Evt.Parsed["remote_ip"] == "1.2.3.4" +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][4].Evt.Parsed["time"] == "20:41:45.000000" +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][4].Evt.Parsed["year"] == "2026" +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][4].Evt.Meta["log_type"] == "dot_connection_reset" +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][4].Evt.Meta["service"] == "adguardhome_dot" +results["s01-parse"]["PumbaLP/adguardhome-dot-errors"][4].Evt.Meta["source_ip"] == "1.2.3.4" diff --git a/.tests/adguardhome-dot-scan/scenario.assert b/.tests/adguardhome-dot-scan/scenario.assert new file mode 100644 index 00000000000..03455618a29 --- /dev/null +++ b/.tests/adguardhome-dot-scan/scenario.assert @@ -0,0 +1 @@ +len(results) == 0 diff --git a/collections/PumbaLP/adguardhome-dot.yaml b/collections/PumbaLP/adguardhome-dot.yaml new file mode 100644 index 00000000000..d7e83345535 --- /dev/null +++ b/collections/PumbaLP/adguardhome-dot.yaml @@ -0,0 +1,6 @@ +name: PumbaLP/adguardhome-dot +description: "Detect and block DoT (DNS-over-TLS, port 853) scanners targeting AdGuard Home" +parsers: + - PumbaLP/adguardhome-dot-errors +scenarios: + - PumbaLP/adguardhome-dot-scan diff --git a/parsers/s01-parse/PumbaLP/adguardhome-dot-errors.md b/parsers/s01-parse/PumbaLP/adguardhome-dot-errors.md new file mode 100644 index 00000000000..cbf4e68fbf5 --- /dev/null +++ b/parsers/s01-parse/PumbaLP/adguardhome-dot-errors.md @@ -0,0 +1,32 @@ +# adguardhome-dot-errors + +Parser for AdGuard Home DoT (DNS-over-TLS, port 853) connection reset errors. + +## Description + +AdGuard Home logs connection reset errors when a client connects to port 853 (DoT) but does not complete a proper TLS handshake. This is a common indicator of port scanners and probers. + +This parser extracts the remote IP address and sets the `dot_connection_reset` log type for use by the `PumbaLP/adguardhome-dot-scan` scenario. + +## Matched log example +2026/04/04 20:41:41.673779 [error] dnsproxy: reading msg proto=tcp err="reading len: read tcp 172.20.2.2:853->85.217.140.42:59208: read: connection reset by peer" +## Limitations + +- Only `connection reset by peer` errors contain a remote IP and are actionable. +- Other DoT errors (TLS version mismatch, cipher suite errors, unexpected EOF) do not contain a remote IP. +- DoQ (port 8853/udp) and direct DoH (port 443) errors also do not contain remote IPs. +- DoH via a reverse proxy (e.g. Nginx) is covered by `crowdsecurity/nginx-logs`. + +## Acquisition + +```yaml +source: docker +container_name: + - adguardhome +labels: + type: adguardhome +Or with log file: +filenames: + - /var/log/AdGuardHome.log +labels: + type: adguardhome diff --git a/parsers/s01-parse/PumbaLP/adguardhome-dot-errors.yaml b/parsers/s01-parse/PumbaLP/adguardhome-dot-errors.yaml new file mode 100644 index 00000000000..a60a9d7e452 --- /dev/null +++ b/parsers/s01-parse/PumbaLP/adguardhome-dot-errors.yaml @@ -0,0 +1,36 @@ +name: PumbaLP/adguardhome-dot-errors +description: "Parse AdGuard Home DoT (DNS-over-TLS, port 853) connection reset errors to detect port scanners and probers" +# +# Matches AdGuard Home log lines containing a remote IP address: +# 2026/04/04 20:41:41.673779 [error] dnsproxy: reading msg proto=tcp err="reading len: read tcp 172.20.2.2:853->85.217.140.42:59208: read: connection reset by peer" +# +# Note: Other DoT error types (TLS version mismatch, cipher suite errors, +# unexpected EOF, bad DNS header) do not contain a remote IP and are +# therefore not actionable by this parser. +# +# Note: DoQ (port 8853/udp) and direct DoH (port 443) errors also do not +# contain remote IPs in AdGuard Home logs and cannot be blocked this way. +# DoH via a reverse proxy (e.g. Nginx) is covered by crowdsecurity/nginx-logs. +# +# Acquisition example: +# source: docker +# container_name: +# - adguardhome +# labels: +# type: adguardhome +# +filter: "evt.Line.Labels.type == 'adguardhome'" +nodes: + - grok: + pattern: '%{YEAR:year}/%{MONTHNUM:month}/%{MONTHDAY:day} %{TIME:time} \[error\] dnsproxy: reading msg proto=tcp err="reading len: read tcp [^>]+->%{IP:remote_ip}:%{INT}: read: connection reset by peer"' + apply_on: Line.Raw + onsuccess: next_stage + statics: + - meta: source_ip + expression: evt.Parsed.remote_ip + - meta: service + value: adguardhome_dot + - meta: log_type + value: dot_connection_reset + - target: evt.StrTime + expression: evt.Parsed.year + "/" + evt.Parsed.month + "/" + evt.Parsed.day + " " + evt.Parsed.time diff --git a/scenarios/PumbaLP/adguardhome-dot-scan.md b/scenarios/PumbaLP/adguardhome-dot-scan.md new file mode 100644 index 00000000000..0ab11c2ee4c --- /dev/null +++ b/scenarios/PumbaLP/adguardhome-dot-scan.md @@ -0,0 +1,23 @@ +```markdown +# adguardhome-dot-scan + +Detect DoT (DNS-over-TLS, port 853) scanners and probers targeting AdGuard Home. + +## Description + +A legitimate DNS-over-TLS client connects cleanly with a proper TLS handshake. Repeated connection resets from the same IP indicate a scanner or prober testing port 853 without proper TLS support. + +Triggers a ban after **5 connection resets within 10 minutes** from the same IP. + +## Requirements + +Requires the `PumbaLP/adguardhome-dot-errors` parser. + +## Tested against + +- AdGuard Home v0.107.x +- Confirmed working in production (blocked real scanners) + +## MITRE ATT&CK + +- T1046 – Network Service Discovery diff --git a/scenarios/PumbaLP/adguardhome-dot-scan.yaml b/scenarios/PumbaLP/adguardhome-dot-scan.yaml new file mode 100644 index 00000000000..690a4316ea4 --- /dev/null +++ b/scenarios/PumbaLP/adguardhome-dot-scan.yaml @@ -0,0 +1,25 @@ +type: leaky +name: PumbaLP/adguardhome-dot-scan +description: "Detect DoT (DNS-over-TLS, port 853) scanners and probers via repeated connection resets on AdGuard Home" +# +# A legitimate DNS-over-TLS client connects cleanly with a proper TLS handshake. +# Repeated connection resets from the same IP indicate a scanner or prober +# testing port 853 without proper TLS support. +# +# Capacity: 5 resets within 10 minutes triggers a ban. +# Tested against AdGuard Home v0.107.x +# +filter: "evt.Meta.log_type == 'dot_connection_reset' && evt.Meta.service == 'adguardhome_dot'" +groupby: evt.Meta.source_ip +capacity: 5 +leakspeed: "10m" +blackhole: 5m +labels: + service: adguardhome + type: scan + spoofable: 0 + confidence: 3 + classification: + - attack.T1046 + behavior: "network:scan" + remediation: true