Project

General

Profile

Actions

Feature #8408

open
VJ OD

Task #8388: firewall: support protocol hooks for all app-layer protocols

firewall: support FTP-data hook states for firewall rule evaluation

Feature #8408: firewall: support FTP-data hook states for firewall rule evaluation

Added by Victor Julien about 1 month ago. Updated 1 day ago.

Status:
New
Priority:
High
Assignee:
Target version:
Effort:
Difficulty:
Label:

Description

FTP is a critical protocol for network firewall deployments. In Suricata 8.0.4, FTP app-layer hook states are not registered for firewall mode. Attempting to use any ftp:* hook in a firewall rule (e.g., accept:hook ftp:request_command ) fails with the error: protocol ftp does not support hook.

This means FTP traffic cannot be inspected or controlled at the application layer in firewall mode. Even when packet-layer rules accept the TCP handshake on port 21, the FTP control channel is dropped by default_app_policy as soon as the app-layer parser engages, because no FTP hooks exist for the firewall engine to evaluate.

FTP-DATA has its own lifecycle:
  • Connection requested
  • Connection established
  • Transfer initiated
  • Data transfer in progress
  • Transfer complete
  • Transfer aborted
  • Connection closed

These states should be mapped to firewall hook points that allow rules to make accept/drop decisions at meaningful protocol transitions — for example, during data transfer, etc.

The expectation engine should also be integrated with firewall mode so that PASV/PORT-negotiated data channel ports are automatically allowed when the control channel is accepted. (Optional)


Related issues 1 (1 open0 closed)

Copied from Suricata - Feature #8392: firewall: support FTP hook states for firewall rule evaluationNewOISF DevActions

VJ Updated by Victor Julien about 1 month ago Actions #1

  • Copied from Feature #8392: firewall: support FTP hook states for firewall rule evaluation added

VJ Updated by Victor Julien 16 days ago Actions #2

The FTP data state machine is very simple. There are 2 states: in progress and finished. There is no protocol data to parse, just data. I'm not sure how to distinguish between transfer complete and aborted, and what would be different between finished and connection closed. The transfer is still in progress as long as the tcp connection is active.

There is currently one dedicated ftp data command: ftpdata_command. This is set by the control channel to either RETR or STOR. Additionally the file.* keywords work, but these haven't been tested with firewall mode yet. They have their own state machine that needs some review. Perhaps a dedicated ftpdata_filename keyword could be added as this info is set up in the connection state by the control channel.

@yashda@amazon.com do you have a ruleset example we can use to reason about this?

YD Updated by Yash Datre 1 day ago Actions #3

Hi Victor,

Here are the ruleset examples. Three scenarios, then thoughts on hook design.

Scenario 1: Read-only FTP (block uploads)

Access control happens on the control channel via ftp:request_command_complete. The data channel is handled with broad TCP port-range rules since the passive mode port is dynamically negotiated.


# --- Control channel (port 21) ---
accept:hook tcp:all $HOME_NET any -> 10.0.5.100/32 21 (flow:not_established; sid:1;)
accept:hook tcp:all $HOME_NET any <> 10.0.5.100/32 21 (flow:established; sid:2;)

accept:hook ftp:request_started $HOME_NET any -> 10.0.5.100/32 any (sid:3;)

# Allow read-only commands — STOR, STOU, APPE, DELE not listed → dropped by default
accept:hook ftp:request_command_complete $HOME_NET any -> 10.0.5.100/32 any (ftp.command; content:"RETR"; sid:4;)
accept:hook ftp:request_command_complete $HOME_NET any -> 10.0.5.100/32 any (ftp.command; content:"LIST"; sid:5;)
accept:hook ftp:request_command_complete $HOME_NET any -> 10.0.5.100/32 any (ftp.command; content:"PASV"; sid:6;)
accept:hook ftp:request_command_complete $HOME_NET any -> 10.0.5.100/32 any (ftp.command; content:"EPSV"; sid:7;)
accept:hook ftp:request_command_complete $HOME_NET any -> 10.0.5.100/32 any (ftp.command; content:"USER"; sid:8;)
accept:hook ftp:request_command_complete $HOME_NET any -> 10.0.5.100/32 any (ftp.command; content:"PASS"; sid:9;)
accept:hook ftp:request_command_complete $HOME_NET any -> 10.0.5.100/32 any (ftp.command; content:"TYPE"; sid:10;)
accept:hook ftp:request_command_complete $HOME_NET any -> 10.0.5.100/32 any (ftp.command; content:"PWD"; sid:11;)
accept:hook ftp:request_command_complete $HOME_NET any -> 10.0.5.100/32 any (ftp.command; content:"CWD"; sid:12;)
accept:hook ftp:request_command_complete $HOME_NET any -> 10.0.5.100/32 any (ftp.command; content:"QUIT"; sid:13;)

accept:hook ftp:response_started 10.0.5.100/32 any -> $HOME_NET any (sid:14;)
accept:hook ftp:response_complete 10.0.5.100/32 any -> $HOME_NET any (sid:15;)

# --- Data channel (passive mode) ---
accept:hook tcp:all 10.0.5.100/32 1024:65535 -> $HOME_NET any (flow:not_established; sid:16;)
accept:hook tcp:all 10.0.5.100/32 1024:65535 <> $HOME_NET any (flow:established; sid:17;)

Scenario 2: Bounce attack prevention

Same control channel rules as above, but PORT and EPRT are simply not in the accept list → dropped by default. Only PASV/EPSV are accepted. No rules for the server connecting outbound (active mode), so bounce attacks are blocked by default.

Scenario 3: Directory-scoped uploads

Allow STOR only to /incoming/ — this is where request_command_complete earns its keep, since both the command and its argument are available:

accept:hook ftp:request_command_complete $HOME_NET any -> 10.0.5.51/32 any \
    (ftp.command; content:"STOR"; ftp.command_data; content:"/incoming/"; startswith; sid:20;)

Thoughts on FTP-data hooks

You're right that the data state machine is trivially simple. The control channel hooks do the heavy lifting — every accept/drop decision about the transfer is made at request_command_complete where we know the command and target file. By the time data flows, the policy decision is already made.

For the data channel, a simple two-state model ( request_started / request_complete ) is fine. The main value would be defense-in-depth using keywords set by the control channel:

  • ftpdata_command — e.g. accept:hook ftp-data:request_started ... (ftpdata_command:retr; sid:X;) to confirm only downloads on the data channel
  • file.name — e.g. accept:hook ftp-data:request_started ... (file.name; content:".pdf"; endswith; sid:X;) to restrict by file type

No need to distinguish transfer complete vs. aborted for firewall purposes.

Priority from our side:
  1. FTP control channel hooksrequest_started , request_command_complete , response_started , response_complete . This is where all policy decisions happen.
  2. FTP-data hooksrequest_started and request_complete with ftpdata_command and file.name validated for firewall mode. Defense-in-depth.

Let me know if you'd like me to flesh out any specific scenario further.

Actions

Also available in: PDF Atom