ModSecurity Advanced Topic of the Week: Real-time Application Profiling

One of the key feature differentiators between ModSecurity and other commercial WAFs has long been automated learning capabilities which achieve a positive security model for input valiation.  ModSecurity users have struggled to achieve a positive security model ruleset to protect their web applications.  It is not that ModSecurity couldn't enforce a positive security ruleset, but rather, that this was largely a manual process of creating rules by hand.  

Some community efforts were created to try and fill this need such as Remo and Web Policies which added GUIs to the process.  This helped with the process however it was still manual.  Then Ivan Ristic and Ofer Shezaf presented ModProfiler, which was an automated method of creating ModSecurity rules based off of saved audit log data.  The concepts presented by ModProfiler were very good, however it too was not a real-time approach.

This past week, I participated in the OWASP Global Summit in Lisbon, Portugal.  While at the summit, I joined a dynamic working session for one of the projects I contribute to - AppSensor.  The concepts presented in AppSensor's Detection Points are very good at detecting malicious activity.  The problem for ModSecurity users was that in order to implement some of these detection points, you needed to have a positive security model.  While at the Summit, I was determined to find a way to achieve real-time application profiling using ModSecurity's capabilities today.  After much trial and error and testing, I came up with an approach that uses advanced features of the ModSecurity rules language.

Real-time Application Profiling

I am happy to announce that we have added a basic application profiling framework ruleset to the OWASP ModSecurity Core Rule Set which takes the first step towards automated application profiling.  

We have just released a ruleset to the OWASP CRS SVN repository called modsecurity_crs_40_profiler.conf.  This ruleset is currently available in the /experimental directory of the CRS and we are hoping that the community will help us with testing and feedback.

The ruleset leverages advanced ModSecurity capabilities such as:

  • Resource-based persistent storage
  • Variable incrementation
  • Macro expansion
  • @within operator checks
  • Operator negation

This initial version of the rules has the ability to profile and enforce the following on a per-resource basis:

  • Request Method(s)
  • Number of Parameters
  • Parameter Names
  • Parameter Length Ranges
  • Parameter Types - numeric or alpha

Profiling Methodology

Let's take a look at the profiling methodology of the new rules.

Resource-based Persistent Collections

The key capability that we are leveraging to achieve our profiling rules is that we need to have per-resource persistent storage so that we can correlate data both across requests and clients.  We do this by using initializing a Resource persistent storage mechanism in phase:1 and we use the macro expanded REQUEST_FILENAME variable as the collection key.

## --[ Step 1: Initiate the Resource Collection ]--## We are using the REQUEST_FILENAME as the key and then set 2 variables -## [resource.pattern_threshold]# Set the resource.pattern_threshold as the minimum number of times that a match should occur # in order to include it into the profile## [resource.confidence_counter_threshold] # Set the resource.confidence_counter_threshold as the minimum number of "clean" transactions# to profile/inspect before enforcement of the profile begins.#SecAction "phase:1,t:none,nolog,pass,initcol:resource=%{REQUEST_FILENAME},setvar:resource.pattern_threshold=50,setvar:resource.confidence_counter_threshold=100"

The two setvar actions are used to determine the number of transactions we want to profile and how many times our individual checks must match before we add them to the enforcement list.

Post-Process Profiling

In order to mimize the impact (latency) of the profiling rules, we chose to implement these checks in phase:5 (logging).  Before we decide whether or not to profile the transaction, we want to do a few security checks to ensure that we are only looking at "clean" transactions.

## --[ Begin Transaction Profiling ]--## There are a number of scenarios where we don't want to profile the transaction as# something is wrong.  In these cases, we will skip profiling.#SecMarker BEGIN_PROFILE_ANALYSISSecAction "phase:5,t:none,nolog,pass,ctl:ruleEngine=DetectionOnly"SecRule RESPONSE_STATUS "^404$" "phase:5,t:none,nolog,pass,setvar:!resource.KEY,skipAfter:END_PROFILE_ANALYSIS"SecRule RESPONSE_STATUS "^(5|4)" "phase:5,t:none,nolog,pass,skipAfter:END_PROFILE_ANALYSIS"SecRule TX:ANOMALY_SCORE "!@eq 0" "phase:5,t:none,nolog,pass,skipAfter:END_PROFILE_ANALYSIS"SecRule &RESOURCE:ENFORCE_PROFILE "@eq 1" "phase:2,t:none,nolog,pass,skipAfter:END_PROFILE_ANALYSIS"

There are basically four different scenarios where we don't want to profile:

  • We use the ctl action to change the ruleEngine to DetectionOnly as we don't want the operator matching to stop the rule processing.  This enables us to profile all ARGS.
  • If the HTTP response code is a 404, then the resource doesn't exist.  In this case, not only do we skip the profiling, but we also remove the resource key so we delete the persistent storage.
  • If the HTTP response code is a level 4xx or 5xx, then the application says something is wrong with the transaction so we won't profile it either.
  • If the CRS anomaly score is anything other than 0, then we won't profile as this means that one of the negative security rules have triggered.
  • Finally, if we have moved into Enforcement mode, then we will stop profiling.

So, if all of these pre-qualifier checks are passed, we then move to profiling the request attributes. 

Example Profiling Check

Let's take a look at one of the profiling checks - Parameter Names.  I have added line numbers to help with explanations.

## Check parameter names#1. SecRule ARGS_NAMES "!@within %{resource.args_names_enforce}" "phase:5,t:none,nolog,pass,setvar:resource.args_names_count_%{matched_var}=+1"2. SecRule RESOURCE:/ARGS_NAMES_COUNT_/ "@streq %{resource.pattern_threshold}" "chain,phase:5,t:none,nolog,pass,setvar:tx.args_names_count_%{matched_var_name}=%{matched_var_name}"3.         SecRule TX:/ARGS_NAMES_COUNT_/ "args_names_count_(.*)$" "capture,t:none,setvar:'resource.args_names_enforce=%{resource.args_names_enforce} %{tx.1}',setvar:!resource.args_names_count_%{tx.1}"4. SecMarker END_ARGS_NAMES_PROFILE_CHECK

Line #1 - this is another pre-qualifier check to see if the parameter name is already in our enforcement list.  If it is not, then we increment a variable counter for the parameter name.

Line #2 - We then check the increment counter to see if it has reached our pattern.threshold level.  If so, then we move to the 2nd part of this chained rule.

Line #3 - this rule will take the ARGS_NAME data and append it to the enforcement list. 

Example Resource Collection Data

To test out the profiling, I sent the following requests to my local webserver using the Apache "ab" tool -

$ ab -n 111 -c 10 "http://localhost/cgi-bin/printenv?param1=123456&param2=abcd"

Once the number of transactions profiled has reached the confidence counter threshold, we then add the enforcement variable to the resource collection.  This moves the rules from profiling to enforcement mode.

SecAction "phase:5,t:none,nolog,pass,setvar:resource.confidence_counter=+1"SecRule RESOURCE:CONFIDENCE_COUNTER "@streq %{resource.confidence_counter_threshold}" "phase:5,t:none,nolog,pass,setvar:resource.enforce_profile=1"

After the profiling phase has been completed, the data saved in the Resource collection will look something like this:

Retrieved collection (name "resource", key "/cgi-bin/printenv").Wrote variable: name "__expire_KEY", value "1297959402".Wrote variable: name "KEY", value "/cgi-bin/printenv".Wrote variable: name "TIMEOUT", value "3600".Wrote variable: name "__key", value "/cgi-bin/printenv".Wrote variable: name "__name", value "resource".Wrote variable: name "CREATE_TIME", value "1297954966".Wrote variable: name "UPDATE_COUNTER", value "111".Wrote variable: name "pattern_threshold", value "50".Wrote variable: name "confidence_counter_threshold", value "50".Wrote variable: name "ARGS:param1_digits", value "111".Wrote variable: name "ARGS:param2_alpha", value "111".Wrote variable: name "confidence_counter", value "111".Wrote variable: name "LAST_UPDATE_TIME", value "1297955802".Wrote variable: name "profile_confidence_counter", value "111".Wrote variable: name "request_method_enforce", value " GET".Wrote variable: name "#_args_enforce", value " 2".Wrote variable: name "args_names_enforce", value " param1 param2".Wrote variable: name "args_length_check1_enforce", value " ARGS:param2".Wrote variable: name "args_length_check2_enforce", value " ARGS:param1".Wrote variable: name "args_digits_enforce", value " ARGS:param1".Wrote variable: name "args_alpha_enforce", value " ARGS:param2".Wrote variable: name "enforce_profile", value "1".Persisted collection (name "resource", key "/cgi-bin/printenv").

As you can see, we now have a number of request attributes that we can now enforce.

Enforcement Examples

We can now send a series of requests to test out each aspect of the learned profile for the resource:

Request Method Enforcement

Test by sending a different request method such as PUT -

$ curl -X PUT http://localhost/cgi-bin/printenv

This results in the following alert message:

[Thu Feb 17 10:31:43 2011] [error] [client ::1] ModSecurity: Warning. Match of "within %{tx.allowed_methods}" against "REQUEST_METHOD" required. [file "/usr/local/apache/conf/modsec_current/base_rules/modsecurity_crs_30_http_policy.conf"] [line "30"] [id "960032"] [msg "Method is not allowed by policy"] [data "PUT"] [severity "CRITICAL"] [tag "POLICY/METHOD_NOT_ALLOWED"] [tag "WASCTC/WASC-15"] [tag "OWASP_TOP_10/A6"] [tag "OWASP_AppSensor/RE1"] [tag "PCI/12.1"] [hostname "localhost"] [uri "/cgi-bin/printenv"] [unique_id "TV0-XsCoAW0AAEawI0kAAAAB"]

Note that in the alert message we have the tag data mapped to the corresponding AppSensor Detection Point - RE1: Unexpected HTTP Command.

Number of Parameters

Test by adding a new parameter -

$ curl "http://localhost/cgi-bin/printenv?param1=123456&param2=abcd&param3=foo"

This results in the following alert message:

[Thu Feb 17 10:55:13 2011] [error] [client ::1] ModSecurity: Warning. Match of "within %{resource.#_args_enforce}" against "&ARGS" required. [file "/usr/local/apache/conf/modsec_current/base_rules/modsecurity_crs_40_profiler.conf"] [line "78"] [msg "Invalid Number of Parameters."] [data "3"] [tag "POLICY/PARAMETER_VIOLATION"] [tag "OWASP_AppSensor/RE5"] [hostname "localhost"] [uri "/cgi-bin/printenv"] [unique_id "TV1E4cCoAW0AAFoII@oAAAAB"]

Parameter Names

Test by changing a parameter name -

$ curl "http://localhost/cgi-bin/printenv?param1=123456&new_param=foo"

This results in the following alert message:

[Thu Feb 17 10:57:57 2011] [error] [client ::1] ModSecurity: Warning. Match of "within %{resource.args_names_enforce}" against "ARGS_NAMES:new_param" required. [file "/usr/local/apache/conf/modsec_current/base_rules/modsecurity_crs_40_profiler.conf"] [line "83"] [msg "Invalid Parameter Name(s)."] [data "new_param"] [tag "POLICY/PARAMETER_VIOLATION"] [tag "OWASP_AppSensor/RE5"] [hostname "localhost"] [uri "/cgi-bin/printenv"] [unique_id "TV1FhcCoAW0AAFoHI@gAAAAA"]

Parameter Length Checks

Test by changing a parameter length -

$ curl "http://localhost/cgi-bin/printenv?param1=123456789012345678&param2=foo"

This results in the following alert message:

[Thu Feb 17 11:32:12 2011] [error] [client ::1] ModSecurity: Warning. Pattern match "length_check2_(.*)$" at MATCHED_VAR_NAME. [file "/usr/local/apache/conf/modsec_current/base_rules/modsecurity_crs_40_profiler.conf"] [line "102"] [msg "Invalid Parameter Length."] [data "Parameter (ARGS:param1) Length: 18"] [tag "POLICY/PARAMETER_VIOLATION"] [tag "OWASP_AppSensor/RE7"] [hostname "localhost"] [uri "/cgi-bin/printenv"] [unique_id "TV1NjMCoAW0AAG51DUEAAAAA"] 

Parameter Type Checks

Test by changing the type for param1 from digits to include meta-characters (such as the single tick mark to test for SQL Injection flaws) -

$ curl "http://localhost/cgi-bin/printenv?param1=123456'&param2=foo"

This results in the following alert message:

[Thu Feb 17 11:57:26 2011] [error] [client ::1] ModSecurity: Warning. Pattern match "digits_check_(.*)$" at MATCHED_VAR_NAME. [file "/usr/local/apache/conf/modsec_current/base_rules/modsecurity_crs_40_profiler.conf"] [line "126"] [msg "Invalid Character(s) in Payload - Expecting Digits."] [data "Parameter (ARGS:param1): 123456'"] [tag "OWASP_AppSensor/RE8"] [hostname "localhost"] [uri "/cgi-bin/printenv"] [unique_id "TV1TdsCoAW0AAHu4GPQAAAAE"]

Current Limitations

While this ruleset is a great first step in the profiling direction, it is not perfect.  The main limitation with this ruleset is that here is no auto-relearning.  Once the rules have moved from profiling into enforcement, they are stay in that mode.  This means that if you have a legitimate code push, then you will probably have to remove the resource collections and then the profiling will restart.

Depending on the amount of data you need to profiler per resource, you may run into SDBM persistent storage size limits.  By default, you have ~800 bytes of usable storage.  If you run into this issue, you will see this error message:

[1] Failed to write to DBM file "/tmp/IP": Invalid argument

If you need more than that, you should download the separate APR and APR-Util packages then edit the "#define PAIRMAX" setting in the /dbm/sdbm/sdbm_private.h file.  You should then recompile both Apache and ModSecurity referencing the updated APR/APR-Util packages.

If you want to exclude certain URLs from profiling all together, we have some rules commented out that you can activate.  They will do an @pmFromFile check against an external file.  This will allow you to add URLs to be exluded to this list file.

Future Directions

There are a number of other profiling checks that will be adding soon including number of cookies, cookie names, referer checks and more parameter type checks.  Another item to consider would be to make sure that we factor in transactions from different clients to ensure that one client does not solely create the profile.  We also have plans to integrate this type of learning into ModSecurity core engines as well.  We chose to release this version using SecRules to the community so that we can have more testing before adding in new checks.

Please help us out by testing these rules and providing feedback on the OWASP ModSecurity Core Rule Set mail-list.

 

Trustwave reserves the right to review all comments in the discussion below. Please note that for security and other reasons, we may not approve comments containing links.