Montag, 18. Juli 2011

Protect hidden fields with mod_security (HA Environment)

If you want to protect hidden fields in an HA/LB environment than the lua integration of mod_security is your salvation. I found a sample in the webgoat/GotRoot Rules (didn't found the download link anymore) which are using 2 lua scripts.The samples scan the RESPONSE_BODY for hidden fields and save them in a file. I modified them in order to speak with a memcached backend and store there the hidden fields. Of course you can use kyoto tycoon to get replication, see my first post ;).
I used the following rules to call the lua scripts.

#Store the sessionid in tx.sessionid
REQUEST_COOKIES:'/(j?sessionid|(php)?sessid|(asp|jserv|jw)?session[-_]?(id)?|cf(id|token)|sid)/' "(\d{4,4})?([^\s]+?)[\:\.\;\s]" "phase:1,id:'33000',t:none,capture,pass,nolog,setvar:tx.sessionid=%{TX.2}"
#if it's a post request check if the hidden fields are modified.
SecRule &ARGS_POST_NAMES "!@eq 0" \
"phase:2,t:none,t:lowercase,nolog,pass,id:'33001',exec:/test/read-hidden-values-memcached.lua"
SecRule TX:PARAMETER_TAMPERING ".*" "phase:2,t:none,log,deny,msg:'Parameter Tampering; Hidden field.',id:'3002',logdata:'Location: %{matched_var}',tag:'PARAMETER_TAMPERING'"
#Store the sessionid in tx.sessionid
SecRule RESPONSE_HEADERS:/Set-Cookie2?/ "(?i:(j?sessionid|(php)?sessid|(asp|jserv|jw)?session[-_]?(id)?|cf(id|token)|sid)=(\d{4,4})?([^\s]+?)[\:\.\;\s])" "phase:3,id:'33003',t:none,pass,nolog,capture,setvar:tx.sessionid=%{TX.7}"
#if a sessionid is there save the hidden fields into the backend.
SecRule TX:SESSIONID "(.*)" "chain,phase:4,id:'33004',t:none,pass,nolog"
    SecRule RESPONSE_BODY ".*" "t:none,nolog,exec:/test/write-hidden-values-memcached.lua"

The 2 Rules which extract the sessionid are from the CRS file modsecurity_crs_16_session_hijacking.conf.
I only modified the regex so it would save the real sessionid and not the sessionid with the server identifier.
Here the two lua scripts.
read-hidden-values-memcached.lua:
function main()
require ('Memcached')
    jsessionid = m.getvar("tx.sessionid", "none")
    if jsessionid then
      m.log(9, "luascript: SESSIONID=\"" .. jsessionid .. "\"")
    else
       return 'no sessionid'
    end
    argspost = m.getvars("ARGS_POST_NAMES", "none");
    memcache = Memcached.Connect({{'server1',9999},{'server2',9999}})
    if memcache then
   -- Examine all variables
     for i=1, #argspost do
      argname = argspost[i].value
      modsecargname = "ARGS_POST." .. argname
      -- use to make exception per ARG
      if modsecargname == "ARGS_POST.xxx" or modsecargname == "ARGS_POST.xxx1" or modsecargname == "ARGS_POST.xxx2" then
          m.log(9, "luascript: ARGNAME matched exception so no further processing")
      else
          memvalue = memcache:get(jsessionid .. argname)
          if memvalue then
             arg = m.getvars(modsecargname, "none")
             if memvalue == string.format("\"%s\"", arg[1].value) then
                m.log(9, "luascript: value matched, value = " .. arg[1].value .."")
             else
                m.setvar("tx.parameter_tampering", "Parameter tampered = "  .. modsecargname .. " old value = " .. memvalue .. " new value = " .. arg[1].value)
             end
          end
      end
     end
    else
     m.log(9, "luascript: error in connect to memcache")
    end
   -- Nothing wrong found.
    memcache:disconnect_all()
end

write-hidden-values-memcached.lua
function main()
require ('Memcached')
    jsessionid = m.getvar("tx.sessionid", "none")
    if jsessionid then
      m.log(9, "luascript: SESSIONID" .. jsessionid .. ".")
      fh = assert (io.tmpfile()) -- open temporary file
      responsebody = m.getvar("RESPONSE_BODY", "none")
      if responsebody then
          fh:write(responsebody)
          fh:flush()
      else
          fh:close()  -- close file
          return 'no Response Body'
      end
      fh:seek ("set", 0) -- back to start
      -- read whole file with '*a'
      local tbuff = fh:read ("*a")
      m.log(9, "luascript: read done from file")
      fh:close()  -- close file
      memcache = Memcached.Connect({{'server1',9999},{'server2',9999}})
      if memcache then
         for a in string.gmatch(tbuff, "<input .->") do
           t = {}
           m.log(9, "a = " .. a)
           for k, v in string.gmatch(a, "(%w+)=\"(.-)\"") do
             t[k]=v
  --           m.log(9, "var = " .. k .. " = " .. v .. "")
           end
           if t.type == nil then
                t.type = ''
           end
           if t.value == nil then
              t.value = ''
           end
           if t.name == nil then
                t.name = ''
           end
           if t.type:lower() == "hidden" and t.value ~= '' and t.name ~= '' then
                memname = t.name
                memvalue = string.format("\"%s\"", t.value)
                memcache:set(jsessionid .. memname, memvalue,600)
           end
         end
--    str1 = string.format('Response body is: %s\n', var1)
      else
          m.log(9, "luascript: error in connect to memcache")
      end
      memcache:disconnect_all()
    else
        m.log(9, "luascript: no sessionid")
    end
m.log(9, "luascript: script end")
end

Now your hidden fields should be protected.
You should use these Rules in combination with the session hijacking rules in order to protect your session too.

prerequisite: lua, luamemcached, luasocket

The next time i think i will post how you can modify the modsecurity_crs_16_session_hijacking.conf file in order to be HA aware and store the session data in memcached/kyoto tycoon for persistence.

Keine Kommentare:

Kommentar veröffentlichen