Project

General

Profile

Actions

Bug #9181

open
RH

Modbus: a midstream flow picked up from a server response is oriented backwards and is never swapped

Bug #9181: Modbus: a midstream flow picked up from a server response is oriented backwards and is never swapped

Added by Roger Hill 6 days ago.

Status:
New
Priority:
Normal
Assignee:
-
Target version:
Affected Versions:
Effort:
Difficulty:
Label:

Description

Affected version: 8.0.4 (the jasonish/suricata:8.0.4 image)

Setting: stream.midstream: true

Summary

When Suricata picks up a Modbus/TCP session midstream and the first packet it sees is a response from the server (source port 502), it takes the server as the client. The flow stays oriented that way for its whole life. The Modbus parser then reads every real request as a response and every response as a request. It raises unsolicited_response events, and modbus keywords with flow:to_server match the server's responses. APPLAYER_WRONG_DIRECTION_FIRST_DATA is never raised, and the flow is never swapped. A session the sensor happens to pick up from a client request is oriented correctly. So with midstream pickup on, the result depends on which packet of a polling cycle the sensor sees first.

Steps to reproduce

The attached make_pcaps.py writes two synthetic captures. Both use RFC 5737 documentation addresses (client 192.0.2.10:49152, server 192.0.2.20:502) and have no three-way handshake. Each holds four Read Holding Registers (FC 3) request and response pairs.

  • modbus-midstream-server-first.pcap starts with the server's response to a request the capture did not see.
  • modbus-midstream-client-first.pcap starts with a client request. This is the control.

repro.rules:

alert tcp any any -> any any (msg:"modbus unsolicited response"; app-layer-event:modbus.unsolicited_response; sid:1000001; rev:1;)
alert tcp any any -> any any (msg:"modbus FC3 request (to_server)"; flow:to_server; modbus:function 3; sid:1000002; rev:1;)
alert tcp any any -> any any (msg:"applayer wrong direction first data"; app-layer-event:applayer_wrong_direction_first_data; sid:1000003; rev:1;)

Run, with the image's default suricata.yaml:

suricata -c /etc/suricata/suricata.yaml -S repro.rules -r modbus-midstream-server-first.pcap \
  --runmode=single -k none -l out \
  --set stream.midstream=true --set app-layer.protocols.modbus.enabled=yes

Observed

Capture Flow record 1000002 (FC 3, to_server) 1000001 (unsolicited_response) 1000003 (wrong direction)
server first 192.0.2.20:502 -> 192.0.2.10:49152, app_proto: modbus 5, all on server-to-client packets 8 0
client first (control) 192.0.2.10:49152 -> 192.0.2.20:502, app_proto: modbus 4, on the 4 real requests 0 0

In the server-first capture, the five server responses are matched as FC 3 requests, and the four real requests are not. The parser reports every transaction as an unsolicited response in both directions.

Expected

When the first data the engine sees on a midstream Modbus flow is server-to-client and shaped like a response, the Modbus probing parser should signal the reverse direction. The flow is then swapped, as happens for protocols whose probe does this, so that to_server is the client's direction and the parser sees requests as requests. SMB has a midstream-specific probe for this purpose.

Code pointers

Checked against the suricata-8.0.4 tag.

  • rust/src/modbus/modbus.rs, modbus_probe: it takes _direction and _rdir but uses neither. It probes the payload with Direction::Unknown and returns ALPROTO_MODBUS, ALPROTO_UNKNOWN or ALPROTO_FAILED, so it can never report that the data arrived in the reverse direction.
  • rust/src/smb/smb.rs, smb_probe_tcp_midstream: called from smb_probe_tcp when STREAM_MIDSTREAM is set. It classifies the record as a request or a response and sets *rdir when that disagrees with the direction it arrived in.
  • src/app-layer-detect-proto.c, AppLayerProtoDetectPPGetProto: it sets *reverse_flow = true when a probing parser returns a valid protocol with an rdir different from the direction probed.
  • src/app-layer.c, TCPProtoDetect: on reverse_flow, for a session with STREAMTCP_FLAG_MIDSTREAM set and STREAMTCP_FLAG_MIDSTREAM_SYNACK not set, it calls PacketSwap and FlowSwap and swaps the direction flags.

So the swap exists for midstream TCP sessions. It needs only the protocol's probe to set rdir, and modbus_probe does not.

A response from a Modbus server and a request from a client share the MBAP header layout. They can still be told apart in common cases: the source port is 502 or another configured detection port and the destination port is not, and some function codes have different body lengths in a request and a response (for example, FC 3's response carries a byte count). A probe that uses the port asymmetry alone, as the detection ports already define, would fix the reported case.

Workaround

None found in configuration. The only midstream option in 8.0.4, stream.midstream-policy, takes pass-flow or ignore, and neither changes direction. Adding sp to app-layer.protocols.modbus.detection-ports made no difference. A rule can report the condition once per flow, for example alert tcp any 502 -> any any (flow:to_server,established; app-layer-protocol:modbus; flowbits:isnotset,mb.rev; flowbits:set,mb.rev; ...), but it cannot correct it.

Attachments

  • make_pcaps.py, which writes both captures, using the Python standard library only;
  • modbus-midstream-server-first.pcap;
  • modbus-midstream-client-first.pcap;
  • repro.rules.

Files

make_pcaps.py (2.94 KB) make_pcaps.py Roger Hill, 10/08/2026 12:49 AM
modbus-midstream-server-first.pcap (767 Bytes) modbus-midstream-server-first.pcap Roger Hill, 10/08/2026 12:49 AM
modbus-midstream-client-first.pcap (684 Bytes) modbus-midstream-client-first.pcap Roger Hill, 10/08/2026 12:49 AM
repro.rules (402 Bytes) repro.rules Roger Hill, 10/08/2026 12:49 AM

No data to display

Actions

Also available in: PDF Atom