Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .tests/adguardhome-dot-errors/adguardhome-dot-errors.log
Original file line number Diff line number Diff line change
@@ -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"
7 changes: 7 additions & 0 deletions .tests/adguardhome-dot-errors/config.yaml
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions .tests/adguardhome-dot-errors/parser.assert
Original file line number Diff line number Diff line change
@@ -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"
1 change: 1 addition & 0 deletions .tests/adguardhome-dot-errors/scenario.assert
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
len(results) == 0
6 changes: 6 additions & 0 deletions .tests/adguardhome-dot-scan/adguardhome-dot-scan.log
Original file line number Diff line number Diff line change
@@ -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"
7 changes: 7 additions & 0 deletions .tests/adguardhome-dot-scan/config.yaml
Original file line number Diff line number Diff line change
@@ -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
50 changes: 50 additions & 0 deletions .tests/adguardhome-dot-scan/parser.assert
Original file line number Diff line number Diff line change
@@ -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"
1 change: 1 addition & 0 deletions .tests/adguardhome-dot-scan/scenario.assert
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
len(results) == 0
6 changes: 6 additions & 0 deletions collections/PumbaLP/adguardhome-dot.yaml
Original file line number Diff line number Diff line change
@@ -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
32 changes: 32 additions & 0 deletions parsers/s01-parse/PumbaLP/adguardhome-dot-errors.md
Original file line number Diff line number Diff line change
@@ -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
36 changes: 36 additions & 0 deletions parsers/s01-parse/PumbaLP/adguardhome-dot-errors.yaml
Original file line number Diff line number Diff line change
@@ -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
23 changes: 23 additions & 0 deletions scenarios/PumbaLP/adguardhome-dot-scan.md
Original file line number Diff line number Diff line change
@@ -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
25 changes: 25 additions & 0 deletions scenarios/PumbaLP/adguardhome-dot-scan.yaml
Original file line number Diff line number Diff line change
@@ -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