ModSecurity Resource Profiling

No matter how good a black list is there will always be a way to circumvent it. JavaScript is especially good at letting attackers hide their payloads using various characters. Billy Hoffman demonstrated this very clearly in his book Ajax Security where he shows how to encode payloads using whitespace and tabs (p.115-116). A better approach, such as a whitelist, is needed to provide holistic protection for web applications.

Creating a list of acceptable parameter values for your web app sounds like a daunting task for any decent size app. The good news is that we can automate much of the process using ModSecurity rules and Lua. This post will demonstrate one technique that can be used to allow ModSecurity to learn what a valid server response for a given resource looks like. By fingerprinting the server response, we can better detect web site defacements or injected data that may be part of a cross site scripting attack. After some discussions on the matter, Ryan Barnett (@ryancbarnett) and I came up with the following rules.

The first step is a Lua script that simply grabs the the server response from the ModSecurity RESPONSE_BODY parameter, then counts the number of iframes, scripts, links and images in the response. The script then sets a transactional variable with the number of iframes, scripts, links and images found

function main()
  local response_body = m.getvar("RESPONSE_BODY", "none");
  if response_body ~= "" then
    local  _, nscripts = string.gsub(response_body, "<script", "");
    local  _, niframes = string.gsub(response_body, "<iframe", "");
    local  _, nlinks = string.gsub(response_body, "a href", "");
    local  _, nimages = string.gsub(response_body, "<img", "");
    m.setvar("tx.niframes", niframes);
    m.setvar("tx.nscripts", nscripts);
    m.setvar("tx.nlinks", nlinks);
    m.setvar("tx.nimages", nimages);
    m.log(3, "niframes[" .. niframes .. "]");
    m.log(3, "nscripts[" .. nscripts .. "]");
    m.log(3, "nlinks[" .. nlinks .. "]");
    m.log(3, "nimages[" .. nimages .. "]");
    return nil;
  end
  return nil;
end

The following ModSecurity rules:

  1. Execute the Lua script.
  2. Inserts the value of the given transactional variable (as set by the Lua script) into the RESOURCE collection, if the RESOURCE collection for the given variable is empty (i.e. this is the first time its called) and skips to the end of the page profile rules. These values are going to be used as the baseline for future comparisons.
  3. Increments the confidence counter that what we learned is correct if the transactional variable is equal to our baseline value in the RESOURCE collection for the particular variable.

SecRuleScript profile_page_scripts.lua "phase:4,t:none,nolog,pass"

SecRule &RESOURCE:'/(niframes|nscripts|nlinks|nimages)/' "@eq 0"
"skipAfter:END_PAGE_PROFILE,phase:4,t:none,nolog,pass, \
  setvar:resource.niframes=%{tx.niframes}, \
  setvar:resource.nscripts=%{tx.nscripts}, \
  setvar:resource.nlinks=%{tx.nlinks}, \
  setvar:resource.nimages=%{tx.nimages}"

SecRule TX:NIFRAMES "@eq %{resource.niframes}" "phase:4,t:none, \
  nolog,pass,setvar:resource.profile_confidence_counter=+1"
SecRule TX:NSCRIPTS "@eq %{resource.nscripts}" "phase:4,t:none, \
  nolog,pass,setvar:resource.profile_confidence_counter=+1"
SecRule TX:NLINKS "@eq %{resource.nlinks}" "phase:4,t:none, \
  nolog,pass,setvar:resource.profile_confidence_counter=+1"
SecRule TX:NIMAGES "@eq %{resource.nimages}" "phase:4,t:none, \
  nolog,pass,setvar:resource.profile_confidence_counter=+1"

Finally, we enforce the rules by:

  1. Checking that the value of the confidence counter is at 40 or higher (FWIW, 40 is a completely arbitrary number)
  2. Blocking, logging and setting various transactional meta-data when a change in the number of iframes, scripts, links and images in the server response is detected

SecRule RESOURCE:PROFILE_CONFIDENCE_COUNTER "@lt 40" "phase:4, \
  t:none,nolog,pass,skipAfter:END_PAGE_PROFILE"

SecRule TX:NIFRAMES "!@eq %{resource.niframes}" "phase:4, \
  t:none,block,msg:'Number of IFrames in Page Have Changed.', \
  logdata:'Previous #: %{resource.niframes} and Current #: %{tx.niframes}',
  severity:'3',setvar:'tx.msg=%{rule.msg}', \
  setvar:tx.outbound_anomaly_score=+%{tx.error_anomaly_score}, \
  setvar:tx.anomaly_score=+{tx.error_anomaly_score},\
  setvar:tx.%{rule.id}-PROFILE/ANOMALY-%{matched_var_name}=%{tx.0}"
SecRule TX:NSCRIPTS "!@eq %{resource.nscripts}" "phase:4, \
  t:none,block,msg:'Number of Scripts in Page Have Changed.', \
  logdata:'Previous #: %{resource.nscripts} and Current #: %{tx.nscripts}',
  severity:'3',setvar:'tx.msg=%{rule.msg}', \
  setvar:tx.outbound_anomaly_score=+%{tx.error_anomaly_score}, \
  setvar:tx.anomaly_score=+{tx.error_anomaly_score}, \
  setvar:tx.%{rule.id}-PROFILE/ANOMALY-%{matched_var_name}=%{tx.0}"
SecRule TX:NLINKS "!@eq %{resource.nlinks}" "phase:4, \
  t:none,block,msg:'Number of Links in PageHave Changed.', \
  logdata:'Previous #: %{resource.nlinks} and Current #: %{tx.nlinks}', 
  severity:'3',setvar:'tx.msg=%{rule.msg}', \
  setvar:tx.outbound_anomaly_score=+%{tx.error_anomaly_score}, \
  setvar:tx.anomaly_score=+{tx.error_anomaly_score}, \
  setvar:tx.%{rule.id}-PROFILE/ANOMALY-%{matched_var_name}=%{tx.0}"
SecRule TX:NIMAGES "!@eq %{resource.nimages}" "phase:4, \
  t:none,block,msg:'Number of Images in Page Have Changed.', \
  logdata:'Previous #: %{resource.nimages} and Current #: %{tx.nimages}', 
  severity:'3',setvar:'tx.msg=%{rule.msg}', \
  setvar:tx.outbound_anomaly_score=+%{tx.error_anomaly_score}, \
  setvar:tx.anomaly_score=+{tx.error_anomaly_score}, \
  setvar:tx.%{rule.id}-PROFILE/ANOMALY-%{matched_var_name}=%{tx.0}"
SecMarker END_PAGE_PROFILE

Post new comment

The content of this field is kept private and will not be shown publicly.

Most Popular List

06/05/2011 | Written By Gordon Maddern | 63,686 Hits
About a month ago I was chatting on skype to a colleague about a payload for...
15/10/2011 | Written By Ty Miller | 18,870 Hits
Lets say that at some point you decided to adhere to security best practices...
28/06/2011 | Written By Sandeep Nain | 15,634 Hits
Coming from a family of civil engineers, I always knew that it is a rigorous...
24/05/2011 | Written By Gordon Maddern | 8,838 Hits
Skype has patched and released the fix for the Skype bug we found so we can d...

Most Recent Posts List

03/06/2013 | Written By Josh Zlatin | 1,265 Hits
I am happy to announce the ModSecurit...
19/05/2013 | Written By Josh Zlatin | 3,718 Hits
Often when implementing customised ModSecurity solutions we need to...
07/05/2013 | Written By Richard Brown | 627 Hits
The term ‘ethical hacker’ is often misrepresented as the keywords...
05/04/2013 | Written By Gordon Maddern | 609 Hits
I recently had to go in to bat for a client who was told by their PCI auditor...