Skip to content

firewallservices/pf-logs does not parse TCP fields from OPNsense filterlog #1850

Description

@Camellotsgo

firewallservices/pf-logs silently drops TCP fields for sequence ranges, causing pf-scan-multi_ports false positives

Description

The firewallservices/pf-logs parser does not parse the TCP-specific portion of some valid OPNsense filterlog records when the TCP sequence number is represented as a range (start:end).

Because the TCP portion of PF_PROTOCOL is optional, the parser still reports success after parsing only:

source port,
destination port,
data length.

As a result, fields such as tcp_flags, sequence_number, ack_number, and tcp_window remain empty.

This can cause firewallservices/pf-scan-multi_ports to interpret legitimate late TCP packets (PA, FA, FPA) from HTTPS servers as a multi-port scan and issue a ban against the server.

Environment
OPNsense: 26.1.10
Architecture: amd64
OPNsense commit: fb4ee60c3
CrowdSec parser: firewallservices/pf-logs
Scenario: firewallservices/pf-scan-multi_ports
Scenario version: 0.5
Parser location:
/usr/local/etc/crowdsec/parsers/s01-parse/pf-logs.yaml
Reproduction log
<134>1 2026-07-22T11:27:15+00:00 OPNsense.internal filterlog 14455 - [meta sequenceId="2026501"] 5,,,02f4bab031b57d1e30553ce08e0ec131,bce0,match,block,in,4,0x0,,58,959,0,DF,6,tcp,64,160.79.104.10,195.85.230.1,443,62392,24,PA,2601190499:2601190523,2850001525,16,,

Save the line and run:

cscli explain --file /tmp/pf-test.log --type syslog -v
Actual result

The parser reports success and correctly extracts:

evt.Parsed.src_port = 443
evt.Parsed.dst_port = 62392
evt.Parsed.data_length = 24

However, it leaves the TCP-specific fields empty:

evt.Parsed.tcp_flags = ""
evt.Parsed.sequence_number = ""
evt.Parsed.ack_number = ""
evt.Parsed.tcp_window = ""
evt.Parsed.tcp_options = ""

The scenario is still instantiated:

firewallservices/pf-scan-multi_ports
Expected result

The parser should extract:

evt.Parsed.tcp_flags = PA
evt.Parsed.sequence_number = 2601190499:2601190523
evt.Parsed.ack_number = 2850001525
evt.Parsed.tcp_window = 16

Alternatively, the parser may expose the sequence range as two separate values:

evt.Parsed.sequence_start = 2601190499
evt.Parsed.sequence_end = 2601190523
Root cause

The current parser contains:

PF_TCP_DATA: '%{WORD:tcp_flags},%{INT:sequence_number},(?:%{INT:ack_number})?,%{INT:tcp_window},(%{DATA:urg_data})?,%{GREEDYDATA:tcp_options}'

However, OPNsense may log the TCP sequence field as a range:

2601190499:2601190523

This does not match:

%{INT:sequence_number}

Additionally, the parser defines:

PF_PROTOCOL: '%{PF_UDP_DATA}(,%{PF_TCP_DATA})?'

Because the TCP section is optional, Grok successfully matches only the three common protocol fields and silently ignores the remaining TCP data.

Operational impact

In this case, the dropped packets were sent by a legitimate HTTPS service:

160.79.104.10:443 -> my_IP:

The packets had flags such as:

PA
FA
FPA

They were late packets or session-closing packets that no longer matched a PF state.

The firewallservices/pf-scan-multi_ports scenario groups all dropped TCP packets by source IP and counts distinct destination ports. The ephemeral destination ports were therefore interpreted as scanned ports.

This generated a decision against the legitimate HTTPS server and added it to the CrowdSec PF blocklist, which also prevented new outbound connections to that address.

As a mitigation, the scenario has been placed in simulation mode:

cscli simulation enable firewallservices/pf-scan-multi_ports
service crowdsec reload
Suggested parser fix

One possible approach:

PF_TCP_SEQUENCE: '%{INT:sequence_start}(?::%{INT:sequence_end})?'

PF_TCP_DATA: '%{WORD:tcp_flags},%{PF_TCP_SEQUENCE},(?:%{INT:ack_number})?,%{INT:tcp_window},(%{DATA:urg_data})?,%{GREEDYDATA:tcp_options}'

The parser test suite should include at least:

Sequence range
PA,2601190499:2601190523,2850001525,16,,
Single sequence number
S,193297189,,64240,,mss;sackOK;TS;nop;wscale
FIN/ACK
FA,785276263,1896178730,46,,
Additional scenario consideration

Even after fixing the parser, firewallservices/pf-scan-multi_ports currently appears to classify all pf_drop TCP packets based on distinct destination ports without requiring an initial SYN packet.

It may be worth limiting the scenario to connection-initiation packets, for example SYN without ACK, once tcp_flags is reliably populated.

Related issue

This appears related to the older OPNsense PF parsing issue:

#677

That report already contains PF log examples where the sequence field uses the start:end format, but the current behavior additionally demonstrates a production false-positive ban caused by the partial parser match.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions