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 | 44,265 Hits
About a month ago I was chatting on skype to a colleague about a payload for...
28/06/2011 | Written By Sandeep Nain | 6,716 Hits
Coming from a family of civil engineers, I always knew that it is a rigorous...
17/02/2011 | Written By Josh Zlatin | 5,999 Hits
Recently a floating point DoS vulnerability surfaced in both PHP and Java. Th...
15/10/2011 | Written By Ty Miller | 4,317 Hits
Lets say that at some point you decided to adhere to security best practices...

Most Recent Posts List

03/01/2012 | Written By Josh Zlatin | 1,349 Hits
Someone asked me about the Hash DoS attack recently disclosed at CCC, so...
02/12/2011 | Written By Josh Zlatin | 596 Hits
On a recent engagement we gained unrestricted administrative access to...
16/11/2011 | Written By Josh Zlatin | 1,246 Hits
Often when implementing customised ModSecurity solutions we need to...
15/10/2011 | Written By Ty Miller | 4,317 Hits
Lets say that at some point you decided to adhere to security best practices...