Monday, September 28, 2020

Converting custom Snort 2 rules for Snort 3 compatibility

By John Levy.

Snort 3 introduces many improvements to simplify rule-writing and increase rule syntax consistency, while at the same time increasing detection robustness and granularity. Converting Snort 2 rules to Snort 3 is a painless process, and this document, while not an exhaustive guide, walks users through some of the more fundamental and significant changes users will need to make to update their custom rules for Snort 3 compatibility.

Snort 3 makes rule writing a little less intimidating by introducing two new simplified rule headers, service rule headers and file rule headers. A traditional Snort rule header contains destination and source networks and ports, but these new optional header formats simplify detection creation and make rules network and port-agnostic. Specifically, they reduce the header to just two things: an action (e.g. alert) and either “file” or a service name (e.g. HTTP). 

Take file-based rules, for example. Cisco Talos analysts typically write a pair of rules for file detection: one rule for the file coming from HTTP, FTP, IMAP, and POP3 servers and a second rule for the file being delivered to an SMTP server. The new alert file rule header removes the need for two separate rules and tells Snort just to alert on matching files, regardless of the transport service, networks, or ports.

Consider the following two Snort 2 file rules:

alert tcp $EXTERNAL_NET $FILE_DATA_PORTS -> $HOME_NET any (msg:"MALWARE-OTHER Win.Ransomware.Agent payload download attempt"; flow:to_client,established; file_data; content:"secret_encryption_key"; metadata:service ftp-data, service http, service imap, service pop3; classtype:trojan-activity; sid:1;)

alert tcp $EXTERNAL_NET any -> $SMTP_SERVERS 25 (msg:"MALWARE-OTHER Win.Ransomware.Agent payload download attempt"; flow:to_server,established; file_data; content:"secret_encryption_key"; metadata:service smtp; classtype:trojan-activity; sid:2;)

This pair of rules is a perfect match for an alert file rule, and creating one is as simple as this:

alert file (msg:"MALWARE-OTHER Win.Ransomware.Agent payload download attempt"; file_data; content:"secret_encryption_key",fast_pattern,nocase; classtype:trojan-activity; sid:3;)

Service rule headers operate similarly and accomplish the same simplification. An alert http rule, for instance, tells Snort to match on all HTTP traffic regardless of destination and source networks and ports. When users are reviewing their own rulesets for updates, updating rule headers to use this new format offers greater simplicity and coverage capabilities.

Arguably one of the more fundamental changes coming to Snort 3 is the introduction of new http_* sticky buffers. Snort’s current HTTP buffers, including http_uri, http_header, http_client_body, require application after each match even if multiple matches appear consecutively in a single buffer. Snort 3 is changing this by making these buffers “sticky,” meaning once the buffer is specified, every subsequent content match is looked for in that particular buffer unless specified otherwise. This is exactly how Snort’s file_data buffer works in both Snort 2 and Snort 3 — once it’s declared, every match is checked for in that buffer. 

The following Snort 2 rule shows how http_* buffers currently work:

alert tcp $EXTERNAL_NET any -> $HOME_NET $HTTP_PORTS (msg:"SERVER-OTHER Admin password change attempt"; flow:to_server,established; content:"cmd=changepass"; fast_pattern:only; http_client_body; content:"user=admin"; http_client_body; content:"pwd="; http_client_body; metadata:service http; classtype:attempted-admin; sid:4;)

As previously stated, Snort 2 requires the corresponding buffer declaration after each match. Converting this rule to its Snort 3 counterpart is easy and simplifies detection by requiring the buffer declaration only once for multiple matches in a single buffer:

alert http (msg:"SERVER-OTHER Sensitive admin password change attempt"; http_client_body; content:"cmd=changepass",fast_pattern,nocase; content:"user=admin"; content:"pwd="; classtype:attempted-admin; sid:5;)

Once http_client_body is specified as a sticky buffer, every content match placed after it is checked for in that particular buffer, unless some other buffer is specified. 

Users may also have rules with content matches in multiple buffers, and updating those rules is just as easy. For example, a rule that looks for matches in both the http_uri and http_client_body buffers will need to be updated so that those two sticky buffers are each placed just once and before their respective matches:

alert http (msg:"SERVER-OTHER Sensitive admin password change attempt"; http_uri; content:"/control_panel.php",fast_pattern,nocase; http_client_body; content:"cmd=changepass"; content:"user=admin"; content:"pwd="; classtype:attempted-admin; sid:6;)

Snort’s pcre option is also affected by this change, meaning it also respects sticky buffer declarations. As a result, the following PCRE flags have been removed: B, U, P, H, M, C, I, D, K, S, and Y, and instead of specifying a PCRE flag for a specific buffer, users will need to place the correct sticky buffer somewhere before the pcre option. For instance, consider the following Snort 2 rule that looks for a particular RegEx present in the http_uri buffer:

alert tcp $HOME_NET any -> $EXTERNAL_NET $HTTP_PORTS (msg:"MALWARE-CNC Win.Malware.Agent outbound cnc connection attempt"; flow:to_server,established; content:"/pizza_order.php?"; fast_pattern:only; http_uri; pcre:"/\x2fpizza_order\x2ephp\x3f[a-z0-9]\x3d\d{45}$/Ui"; metadata:service http; classtype:trojan-activity; sid:7;)

Converting this rule to Snort 3 is easy. Simply remove http_uri and the “U” flag, and specify the http_uri sticky buffer before the first content match like so:

alert http (msg:"MALWARE-CNC Win.Malware.Agent outbound cnc connection attempt"; http_uri; content:"/pizza_order.php?",fast_pattern,nocase; pcre:"/\x2fpizza_order\x2ephp\x3f[a-z0-9]\x3d\d{45}$/i"; classtype:trojan-activity; sid:8;)

One thing you might notice about the above Snort 3 rules is the lack of fast_pattern:only. This is because Snort 3 removes fast_pattern:only and will now only match a given fast_pattern once if it can. As a result, users with custom rules that explicitly define fast_pattern:only will want to replace it with fast_pattern,nocase (more on the syntax below). On a related note, Snort 3 also changes how fast_pattern:<offset>,<length> definitions are declared. For those that need a refresher, this allows users to specify only a portion of a content match as the fast_pattern. The new syntax for it is: 

fast_pattern,fast_pattern_offset <offset>,fast_pattern_length <length>.

Snort 3 also introduces optional selectors to the http_header and http_uri buffers that give users more granularity when looking for content matches. This is especially useful for tightening a rule for FP-prevention purposes. Specifically, http_header and http_raw_header can now look at individual headers, and http_uri, and http_raw_uri can look for matches in either the HTTP URI’s path or query. These aren’t the only optional selectors, and the full list of optional selectors can be found in the Snort 3 manual

Utilizing one of these optional selectors is done by specifying the selector after the corresponding http_* buffer like <http_*>:<optional_selector>. The http_header field selector requires the field keyword before the field name, but the query and path selectors can be specified directly after the http_uri: or http_raw_uri: declarations. So, for example, to look for a particular HTTP header field, say user-agent, one will want to specify the following sticky buffer before the match: http_header:field user-agent;. The below rule does just that and looks for “vuln finder v12345” only in the specified header field.

alert http (msg:"MISC-ACTIVITY Suspicious web scanner detected"; http_header:field user-agent; content:"vuln finder v12345",fast_pattern,nocase; classtype:trojan-activity; sid:9;)

This added granularity is optional but is still useful to know when converting old rules, as it is one way Snort 3 users can make their current rules just a bit more targeted.

Cleaner rule syntax is another new and welcome change coming to Snort 3. For starters, Snort 3 now allows arbitrary whitespace, meaning there’s no longer a need to escape new lines if creating a Snort rule that spans multiple lines:

alert http (

    msg:"POLICY-OTHER Suspicious web scanner detected";

    http_header:field user-agent; 

    content:"vuln finder v12345",fast_pattern,nocase;




Another stylistic change is that content matches suboptions, such as nocase, fast_pattern, within, distance, depth, and offset, are now applied to their content match separated by commas instead of semicolons, and integer values specified after the location-based suboptions are separated now by spaces instead of colons (note that semicolons should still be placed after the final suboption). For example, if we had a Snort 2 rule that was looking for content:"suspicious string two"; within:50; nocase;, we would need to update it accordingly with the new cleaner syntax: content:"suspicious string two",within 50,nocase;.

Specifying multiple services for a given rule has also been simplified, and in fact, service is now its own rule option and no longer specified in the metadata rule option. Rather than writing “service” before each service name, it only needs to be written once (as its own rule option), and multiple service declarations are now also separated by commas. So for example, to convert a rule looking for both HTTP and IMAP traffic, the service option should be written like so:

alert tcp $EXTERNAL_NET [$HTTP_PORTS,143] -> $HOME_NET any (

    msg:"MALWARE-OTHER Win.Malware.Agent payload download attempt"; 



    content:"suspicious string one",fast_pattern,nocase;

    content:"suspicious string two",within 50,nocase; 





Snort 3 is also dropping the urilen rule option in lieu of a new option, bufferlen, which is to be used in tandem with a sticky buffer. So for instance, one would check for a URI length like http_raw_uri; bufferlen:<length>;. This is a pretty simple change and allows users to also check the length of other buffers, as opposed to just being able to check the URI length. For example, users can combine bufferlen with http_raw_uri:query to check for just the length of the query part of an HTTP URI:

alert http (

       msg:"MALWARE-OTHER Win.Ransomware.Agent cnc communication attempt";








Lastly, for users with many custom rules, Snort 3 provides a binary that can handle most rule-conversion needs: snort2lua. This binary will attempt to convert Snort 2 rules in a .rules file to valid Snort 3 rules files. If the Snort 2 rule contains an option unsupported in Snort 3, information about it will be included as a comment in the output file. Running this tool is very simple. If a user has a rules file containing custom Snort 2 rules named old.rules, they can convert that to a Snort 3 rules file with the following command:

$ snort2lua -c old.rules -r updated.rules

It might be wise to first try a blanket conversion of all custom rules before individually converting/updating ones that require manual attention. Snort 2 rules, for instance, do not allow for the optional selectors mentioned above, so users will need to add this extra specificity. Rule conversion is only a small part of what snort2lua has to offer, and we will cover the tool in greater detail in a future blog post.

We hope this post helps kickstart users’ ability to convert their rules to be compatible with and take advantage of all that is offered in Snort 3. Snort users will feel right at home when updating their rules for Snort 3 compatibility, and the above changes, while only a handful of them all, simplify rule creation and improve its already outstanding detection capabilities. Combining these changes with Snort 3’s new multithreading, hyperscan support, full plugin-system, and many more, provides users with even better usability, detection and performance.