Skip to content
Alexander Krizhanovsky edited this page Mar 13, 2024 · 61 revisions

Frang security limits enforcing module

Frang is an internal Tempesta module for enforcing security limits to prevent Web attacks and mitigate HTTP(S) (D)DoS attacks. It uses static limiting and checking of ingress HTTP requests.

Learn how to configure limits when migrating from NGINX.

Frang limits are split into two parts: connection level limits and message level limits. The rules how the limits can be defined and the ways how malicious traffic can be blocked differ for both of the types.

The main portion of it's logic is on the HTTP layer, so it's recommended that ip_block option (enabled by default) is used to block malicious users at IP layer.

The limits are described inside section frang_limits. The section may appear:

  • at top level - to configure connection level limits and to override default values for message level limits for vhosts below this directive section. May appear multiple times. Implicit default vhost will use the last effective set of the message level limits.

  • inside vhost section - to override defaults for locations of the current vhost below this directive section. May appear multiple times. Implicit default location of the vhost will use the last effective set of the limits.

  • inside location section - to configure message level limits for the current location.

Even if section frang_limits is not stated in the configuration file, the whole Frang subsystem is enabled with defaults.

The global Frang limits must be specified before all vhosts, which must inherit the global limits.

See examples below how the limits can be configured and overridden.

Connection level limits

Connection level limits are applied at very early processing stages, when there is not enough data to determine target vhost. E.g. when a client establishes connections or until full set of request headers is received. Thus connection level limits are configured only globally.

Limits of this type can not work with block_action, which sends HTTP error messages, since a connection under inspection of a limit from this section is either not yet established or is going to be terminated, i.e. there is nowhere to send an error message.

If the limit is reached, then Tempesta FW resets a connection (sends TCP RST segment) - this is true for all connection level limits. A client can try to reestablish a connection and it can be serviced if it satisfies all the limits this time and unless the ip_block on is specified.


ip_block [off|on] If disabled, client connection(-s) are closed of security events (the way of closing depends on block_action attack section). If enabled, all client connections with this IP are aborted (with RST packet) and new clients will be blocked by it's IP address. Defaults: off - disabled.


request_rate [NUM] Maximum number of requests per second from a single client across all it's connections. This directive can be used against DDoS HTTP/2 Rapid Reset. In HTTP/2 counts the number of HEADERS frames (with and without END_STREAM, END_HEADERS flags). The option can be used independently from request_burst limit. 0 to disable the limit. Defaults: 0 (disabled).


request_burst [NUM] Maximum burst of request rate on short periods of time (125 ms). This directive can be used against DDoS HTTP/2 Rapid Reset. In HTTP/2 counts the number of HEADERS frames (with and without END_STREAM, END_HEADERS flags). The option can be used independently from request_rate limit. 0 to disable the limit. Defaults: 0 (disabled).


tcp_connection_rate [NUM] Maximum number of new connection openings (both TLS and non TLS) per second from a single client. 0 to disable the limit. Defaults: 0 (disabled).


tcp_connection_burst [NUM] Maximum burst of new connection openings rate (both TLS and non TLS) on short periods of time (125 ms) from a single client. Having that browsers may issue about 6 connections simultaneously to a host, a reasonable value for the limit is 10-20. The option can be used independently from tcp_connection_rate limit. 0 to disable the limit. Defaults: 0 (disabled).


concurrent_tcp_connections [NUM] Maximum number of new concurrent connections (both TLS and non TLS) from a single client. 0 to disable the limit. Defaults: 0 (disabled).


client_header_timeout [TIME] Maximum time in seconds to receive the whole HTTP headers set of incoming request. When we receive the first block of the request header, we remember the time when it was received. When the next block arrives, we check the time of its arrival and if the waiting timeout exceeds the specified one, we drop the request. 0 to disable the limit. Defaults: 0 (disabled).


client_body_timeout [TIME] Maximum time in seconds to receive the whole HTTP body of incoming request. The behavior is same as for client_header_timeout. 0 to disable the limit. Defaults: 0 (disabled).


http_header_chunk_cnt [NUM] HTTP messages can be sent by parts. The option controls maximum number of parts (chunks) in the request header part. 0 to disable the limit. Defaults: 0 (disabled).


http_body_chunk_cnt [NUM] HTTP messages can be sent by parts. The option controls maximum number of parts (chunks) in the request body part. 0 to disable the limit. Defaults: 0 (disabled).


tls_connection_rate [NUM] Maximum number of new TLS sessions established with a single client per second. Establishing a new TLS connection (handshake processing) requires much more computations on server side than on resuming a previous one. Malicious clients can DDoS server constantly establishing thousands new TLS connection. With the option Tempesta blocks new connections with a client, if it establishes new TLS connections too often. 0 to disable the limit. Defaults: 0 (disabled).


tls_connection_burst [NUM] Maximum burst of new TLS sessions rate on short periods of time (125 ms) from a single client. The option can be used independently from tls_connection_rate limit. 0 to disable the limit. Defaults: 0 (disabled).


tls_incomplete_connection_rate [NUM] Block client if it fails more than NUM TLS handshakes per second. 0 to disable the limit. Defaults: 0 (disabled).

Accounting history

Some of the limits (all *_burst and *_rate limits) work using a history ring buffer of 8 slots each of 125ms, so the tracking history is 1 second. This implies the property of amnesia: if a client has issued say 1000 requests immediately with only 10 requests per second in a rate limit, then after 1 second it still can be normally serviced unless ip_block on was specified.

Minor bursts also can actually exceed the specified limit, but not more than 2 times. This may happen if the actual burst is tracked in 2 consequent slots in the history buffer instead of one. Keep this in mind during configuration and troubleshooting.

Message level limits

Message level limits are accounted on later processing stages when target vhost and location are already known. Such request can be configured globally as defaults, per vhost and per location.

These limits can trigger both the blocking on the IP layer (with ip_block option) or sending an HTTP error message (with block_action). However, bear in mind how both of the options interact with each other, i.e. if both of them are enabled like in the wrong configuration:

frang_limits {
    ip_block on;
    http_ct_required true;
}
block_action attack reply;

If the http_ct_required rule is triggered, then Frang immediately calls the IP filter, which

  1. resets the client TCP connection with RST segment and
  2. blocks the client IP on the Netfilter layer. This means that we can not send any data, including an error HTTP response, to the client. In other words, in this configuration block_action statement for triggered http_ct_required rule is just ignored.

http_uri_len [NUM] Maximum length of URI part in a request.0 to disable the limit. Defaults: 0 (disabled).


http_body_len [NUM] Maximum length of HTTP message body of incoming request. 0 to disable the limit. Defaults: 1073741824 (1 Gb).


http_hdr_len [NUM] Maximum length of the HTTP message whole header, including the header name and value. Defaults: 0 (disabled)


http_header_cnt [NUM] Maximum number of HTTP headers in a HTTP message. 0 to disable the limit. Defaults: 0 (disabled).


http_strict_host_checking [true|false] Apply additional "SHOULD" conditions to authority information from RFC 9112/9113:

  • Enforce Host and :authority equality (HTTP/2)
  • Enforce equality host=... part of Forwarded header and authority information (HTTP/1.1, HTTP/2)
  • Enforce equality of absoluteURI host part and Host header (HTTP/1.1)

Defaults: true.

Notice While mismatching of SNI and virtual host names is the common source of vhost confusion Tempesta FW does not validate SNI against virtual host name at run time. Instead, it validates Subject Alternative Names (SAN) on certificate loading for the vhost and later, in run time, validates SNI against the available SANs.


http_ct_required [true|false] Require presence of Content-Type header in a request. Ignored when http_ct_vals is present. Defaults: false.


http_trailer_split_allowed [true|false] Allow the same header appear in both request header part and chunked trailer part. Defaults: false.


http_methods METHOD [METHOD]... The list of accepted HTTP methods (see OWASP recommendations). There is also one special method (unknown) which makes any method accepted. Defaults: limit disabled (all methods are allowed). Example: http_methods get post head;


http_ct_vals ["CONTENT_TYPE"]... The list of accepted values for Content-Type header. Note that the full types must be specified, i.e. if you need to allow text/html and text/plain, then you should specify http_ct_vals "text/plain" "text/html", http_ct_vals "text/*" won't match the required values. Sets the value to true and ignore false for http_ct_required. Defaults: limit disabled (all values are allowed). Example: http_ct_vals "text/plain" "text/html";


http_resp_code_block RESPONSE_CODE [RESPONSE_CODE]... LIMIT TIME_FRAME_IN_SECONDS Block client if it cause to many errors on backend server in the selected time frame (responses from the Tempesta cache is not counted for this limit). See Password crackers section. RESPONSE_CODE - status code in the response from backend server. LIMIT - responses count. TIME_FRAME_IN_SECONDS - size of sliding window to count the limit. Defaults: limit disabled. Example: http_resp_code_block 403 404 502 20 5;


http_method_override_allowed [true|false] The option controls how the requests with method override headers (X-Http-Method, X-Method-Override, X-Http-Method-Override) are processed. Defaults: false. If the option is disabled (defaults) such requests are blocked. If the option is enabled, the overridden method is used as primary request method inside request processing logic. The overridden method must be allowed by http_methods directive, otherwise it will be blocked. Keep in mind that in all cases unsafe method cannot override safe method (GET, HEAD, OPTIONS, TRACE, PROPFIND).

Combinations with block_action and ip_block

To clarify possible combinations of block_action and ip_block let's consider all the combinations:

  • ip_block off, block_action attack reply: this configuration doesn't impact connection level limits, so if such a limit is triggered, then just TCP RST is sent to a malicious client. If a message level limit is triggered, then a TCP connection closed gracefully (FIN) and HTTP error response is sent.

  • ip_block off, block_action attack drop: triggering a limit on any level leads to a TCP connection reset (RST) and no HTTP responses.

  • ip_block on, block_action attack drop: triggering a limit on any level leads to a TCP connection reset (RST), client IP blocking and no HTTP responses.

  • ip_block on, block_action attack reply: this is equivalent to the previous one for connection level limits (TCP RST, IP blocking and no HTTP responses), but for the message level limits this is a misconfiguration since reply doesn't work together with blocking IP (TCP can not operate with a blocked peer on the IP layer).

Configuration examples

Example 1. Configure all Frang limits globally for all vhosts.

frang_limits {
    request_rate 20;
    request_burst 15;
    tcp_connection_rate 8;
    tcp_connection_burst 6;
    concurrent_tcp_connections 8;
    client_header_timeout 20;
    client_body_timeout 10;
    http_uri_len 1024;
    http_hdr_len 256;
    http_ct_required false;
    http_methods get post head;
    http_ct_vals "text/plain" "text/html";
    http_header_chunk_cnt 10;
    http_body_chunk_cnt 0;
    http_resp_code_block 403 404 502 20 5;
}

# The `frang_limits` section is listed before vhosts definitions, thus the
# vhosts and all their locations will be using the limits defined above.
vhost crm.example.com {
    ...
}
vhost example.com {
    ...
}
...

Example 2. Global/connection-level vs per-vhost/message limits.

listen 192.168.100.4:443 proto=https;

srv_group default {
	server 127.0.0.1:8080 conns_n=4;
}

vhost default {
	tls_certificate /root/tempesta/etc/tfw-root.crt;
	tls_certificate_key /root/tempesta/etc/tfw-root.key;

	resp_hdr_set Strict-Transport-Security "max-age=31536000; includeSubDomains";

	frang_limits {
		http_methods GET;
		http_uri_len 512;
		http_resp_code_block 400 403 404 3 10;
	}

	proxy_pass default;
}

cache 0;

frang_limits {
	client_header_timeout 20;
	client_body_timeout 10;
	http_header_chunk_cnt 10;
	http_body_chunk_cnt 0;
}

block_action attack reply;

http_chain {
	-> default;
}

Example 3. Overriding Frang limits default values.

# Any limits can be overridden, update just one to keep the example short.
frang_limits {
    http_uri_len 1024;
}

vhost crm.example.com {
    # No `frang_limits` section in this vhost, effective Frang configuration is:
    # http_uri_len 1024;
    ...
}

frang_limits {
    http_uri_len 2048;
}
# Frang configuration was updated, current defaults is:
# http_uri_len 2048;

vhost example.com {
    location prefix "/img/" {
        frang_limits {
            http_uri_len 3096;
        }
        # Frang configuration was updated inside location, current defaults is:
        # http_uri_len 3096;
    }
    location prefix "/video/" {
        # Frang configuration was not updated inside location, keep using
        # global defaults. Effective configuration is:
        # http_uri_len 2048;
    }

    frang_limits {
        http_uri_len 1024;
    }
    # Frang configuration was updated, current defaults for other locations is:
    # http_uri_len 1024;

    location prefix "/docs/" {
        # Frang configuration was not updated inside location, keep using
        # global defaults. Effective configuration is:
        # http_uri_len 1024;
    }

    # Frang limits for implicit default location is:
    # http_uri_len 1024;
}

vhost test-net.com {
    # Frang settings was updated multiple times in vhost `example.com` section,
    # but that changes doesn't affect global defaults. Effective configuration
    # is:
    # http_uri_len 2048;
}
...

Custom character sets

Following configuration options define allowed character sets in various HTTP fields:

  • http_uri_brange - URI path. Note that Referer headers are also verified by the characters set. Disallowed characters - 0x00-0x20

  • http_token_brange - each header field value defined with 'token' by RFC 7230, e.g. non-standard values for HTTP methods and HTTP headers: Connection, Transfer-Encoding, Cookie name (note that value is processed with its own alphabet http_cookie_brange). For example: Connection: extension - TempestaFW checks all characters. Disallowed characters - 0x00-0x20, 0x2c, 0x3b. WARNING: please use this directive with caution. It checks a lot of headers and may have an unexpected result.

  • http_qetoken_brange - 'token' with DQUOTE and '=', e.g. non-standard values for headers - Cache-Control, Pragma, and Keep-Alive. For example: Cache-Control: extension=100 - TempestaFW checks all characters. Disallowed characters - 0x00-0x20, 0x2c,

  • http_nctl_brange - 'non-control characters' for generic filed values defined by RFC 7230 Apendix B and RFC 5234 Apendix B.1. Currently used for HTTP date headers, such as Expires, Date, Last-Modified, and If-Modified-Since, extension values only. Disallowed characters - 0x00-0x1f.

  • http_xff_brange - X-Forwarded-For Node ID defined by RFC 7239. Disallowed characters - 0x00-0x20, 0x2c

  • http_etag_brange - ETag accepted characters set (RFC 7232 2.3). This directive checks if-none-match header and value in quotes. It does not check W/ and *. Disallowed characters - 0x00-0x20, 0x22

  • http_cookie_brange - Cookie header values. Disallowed characters - 0x00-0x20, 0x3b, 0x3d

  • http_ctext_vchar_brange - 'ctext | VCHAR' headers, e.g. User-Agent. Disallowed characters - 0x00-0x1f. WARNING: please use this directive with caution. It checks a lot of headers and may have an unexpected result.

Receiving a forbidden character is considered an attack and this connection will be closed.

Syntax:         <directive> <ranges>;
Default:        disabled
Context:        global
Reconfig:       false
Repeat:         false

ranges is a list of space separated ranges (integers in range [0-255]) or integers for single characters. Hex and decimal encodings are accepted. Example:

http_uri_brange 0x2f 0x61-122 48 0x2A;

HTTP Strict Transport Security (HSTS)

HTTP Strict Transport Security (HSTS) is defined in (RFC 6797) and can be implemented just by adding Strict-Transport-Security header to a required vhost or location:

resp_hdr_set Strict-Transport-Security "max-age=31536000; includeSubDomains"

Filtering

Let's see a simple example to understand Tempesta filtering.

Run Tempesta FW with Frang configured and put some load onto the system to make Frang generate a blocking rule:

$ dmesg | grep frang
[tempesta] Warning: frang: connections max num. exceeded for 192.168.0.1: 9 (lim=8)

Frang's rate limiting calls the filter module that stores the blocked IPs in Tempesta DB, so now we can run some queries on the database (you can read more about tdbq):

# ./tdbq -a info

Tempesta DB version: 0.1.14
Open tables: filter

INFO: records=1 status=OK zero-copy

The table filter contains all blocked IP addresses.

Clone this wiki locally