## ProFTPD module `mod_delay`

The `mod_delay` module is designed to make a certain type of information leak, known as a "timing attack", harder.

When `proftpd` processes the `USER` and `PASS` FTP commands from a client, it has to perform checks against configured ACLs, look up user and group information, etc. These checks are not done if the given username is known to not exist for the server, in order to not tie up system resources needlessly. However, this does mean that more work is done when handling "good" users than when handling "bad" users. This difference can be detected in the time it takes for `proftpd` to send a response to the `USER` and `PASS` commands. This means it is possible for an attacker to look for these statistical timing differences, and determine which users are "good" and which are "bad". From there, a determined attacker can focus their attention on the known good usernames. Note that the timings will vary depending on server load, number of users in the user base, type of storage of user data (e.g. LDAP directories, SQL tables, RADIUS servers, flat files, etc).

The `mod_delay` module attempts to prevent such timing differences by keeping track of the time taken to process the `USER` and `PASS` commands. It does this for the most recent `USER` and `PASS` commands. The timing data are stored in the module's `DelayTable`. If the module detects that `proftpd` has not taken enough time to handle one of these commands, compared to its past response times, a small delay will be added to the response cycle. The amount of delay is determined by the difference between the current time spent handling the command and the median time spent handling the same command in the past.

The most current version of `mod_delay` can be found in the ProFTPD source distribution:

      http://www.proftpd.org/

## Thanks

2004-10-18: Thanks to Michael Renner for testing out various versions of the module as it was developed.

## Directives

- DelayControlsACLs
- DelayEngine
- DelayOnEvent
- DelayTable

## Control Actions

- `delay info`
- `delay reset`

### DelayControlsACLs

Syntax: DelayControlsACLs actions|"all" "allow"|"deny" "user"|"group" list
Default: None
Context: server config
Module: mod_delay
Compatibility: 1.3.1rc1 and later

The `DelayControlsACLs` directive configures access lists of users or groups who are allowed (or denied) the ability to use the actions implemented by `mod_delay`. The default behavior is to deny everyone unless an ACL allowing access has been explicitly configured.

If "allow" is used, then list, a comma-delimited list of users or groups, can use the given actions; all others are denied. If "deny" is used, then the list of users or groups cannot use actions all others are allowed. Multiple `DelayControlsACLs` directives may be used to configure ACLs for different control actions, and for both users and groups.

### DelayEngine

Syntax: DelayEngine on|off
Default: DelayEngine on
Context: server config
Module: mod_delay
Compatibility: 1.2.10rc1 and later

The `DelayEngine` directive enables or disables the module's runtime delaying calculations. If it is set to off this module does no delaying. Use this directive to disable the module.

Example:

      <IfModule mod_delay.c>
        DelayEngine off
      </IfModule>

### DelayOnEvent

Syntax: DelayOnEvent event delay-ms
Default: None
Context: server config
Module: mod_delay
Compatibility: 1.3.6rc1 and later

The `DelayOnEvent` directive configures an explicit delay-ms, in milliseconds, for the given event. The supported values for the event parameter are:

- USER
- PASS
- FailedLogin
- Connect

The delay-ms parameter can be a number (in which case the units are assumed to be milliseconds), or using a suffix. In addition, the delay-ms parameter can provide a range, from minium to maximum (but only for `Connect` events currently).

To help illustrate the usage of `DelayOnEvent`, here are some examples:

      DelayOnEvent PASS 2000ms

This configures `mod_delay` to always add a delay of 2000 milliseconds for every PASS command. Alternatively, to add a delay only on failed logins, you might use:

      DelayOnEvent FailedLogin 5s

which adds a delay of 5 seconds after the login has failed.

And to inject delays at connect time, to "spread out" the processing of many connections that occur with a short span of time, you can use:

      DelayOnEvent Connect 100-3000ms

to inject a minimum 100ms delay to the start of a session, with a randomly selected additional jitter delay for a maximum possible delay of 3000ms (3 sec).

### DelayTable

Syntax: DelayTable path|"none"
Default: DelayTable /var/proftpd/proftpd.delay
Context: server config
Module: mod_delay
Compatibility: 1.2.10rc1 and later

The `DelayTable` directive configures a path to a file that `mod_delay` uses for storing its timing data. The given path must be an absolute path. It is recommended that this file not be on an NFS mounted partition.

Note that timing data is kept across daemon stop/starts. When new `<VirtualHost>`s are added to the configuration, though, `mod_delay` will detect that it does not have a suitable `DelayTable` for the new configuration, and will clear all stored data.

If the `DelayTable` parameter is "none", then the `mod_delay` module will not store timing data. This configuration is used, in conjunction with `DelayOnEvent`, for setting explicit delays, rather than learning/adapting the delays dynamically.

## Control Actions

### `delay info`

Syntax: ftpdctl delay info
Purpose: Display data from the DelayTable

The `delay info` control action can be used to view the timing data currently stored in the `DelayTable` file. This can help to determine why the `mod_delay` module might be adding longer-than-expected delays to the login sequence for FTP sessions.

### `delay reset`

Syntax: ftpdctl delay reset
Purpose: Reset the DelayTable, clearing all data

The `delay reset` control action can be used to clear the `DelayTable` of all timing information, resetting the table. This can be used if the table starts to collect delays that are too long to be useful.

## Usage

The `mod_delay` module provides protection against a very specific problem. However, the module may not be appropriate for all situations.

For some sites, the delay added by `mod_delay` is not helpful, and in fact can annoy users. To disable `mod_delay`, simply add the following to your `proftpd.conf` file:

      <IfModule mod_delay.c>
        DelayEngine off
      </IfModule>

For other sites, malicious users may notice that they can add arbitrarily long delays to all users' sessions by "poisoning" `mod_delay`'s cache of timing information. For example, a malicious client might connect, send a `USER` command, and then not disconnect for long time. After a few such clients have connected, `mod_delay` will add a delay for all clients, such that some legitimate clients may time out. In effect, this may look like a sort of denial of service (DoS).

To guard against the above situation, there are two possible solutions. First, you can disable the `mod_delay` module entirely (see above); if the timing information leak is not of concern for your FTP site, this is the recommended approach. Second, you can use the `mod_ifsession` module and its `<IfClass>` sections so that the `mod_delay` module applies only to certain DNS names and IP address ranges. For example:

      # Define a class which identifies trusted clients
      <Class trusted>
        From a.b.c.d
        From e.f.g.h
      </Class>

      <IfModule mod_delay.c>
        <IfClass trusted>
          DelayEngine off
        </IfClass>

        <IfClass !trusted>
          DelayEngine on
        </IfClass>
      </IfModule>

More information on defining classes can be found here.

Logging
The `mod_delay` module supports trace logging, via the module-specific log channels:

- delay

Thus for trace logging, to aid in debugging, you would use the following in your `proftpd.conf`:

      TraceLog /path/to/ftpd/trace.log
      Trace delay:20

This trace logging can generate large files; it is intended for debugging use only, and should be removed from any production configuration.

Frequently Asked Questions

Question: My client times out, after a couple of minutes, after sending the `USER` command. After disabling `mod_delay`, the login succeeds quickly. Isn't this a bug in `mod_delay`?
Answer: No.

The `DelayTable` file can, over time, build up a store of long delay values. A series of logins which take a while (e.g. SSL/TLS handshakes over slow network connections) can cause this sort of behavior. You can delete the `DelayTable` file or use the `delay reset` ftpdctl action to clear the stored data.

Question: Shouldn't `mod_delay` have some sort of maximum delay value, so that it doesn't cause timeouts?
Answer: No. There is no single value, hardcoded or configurable, that will work well for all sites and achieve `mod_delay`'s purpose at the same time. What seems like a normal delay time for one site will be unacceptably long for another site.

Question: Is it OK to configure my `DelayTable` on some ephemeral storage like `tmpfs`?
Answer: Yes, this is fine.

The `mod_delay` module is designed to create and populate a new `DelayTable` when one is not present. So if your `DelayTable` is stored on something like `tmpfs` such that it goes away when your host is restarted, `mod_delay` will create a new one. This does mean that the previously stored timing data will be lost, and that `mod_delay` will start calculating and storing new timing data; the functional impact should be negligible.

## Installation

The `mod_delay` module is compiled by default.

© Copyright 2004-2023 The ProFTPD Project
All Rights Reserved