You are here

Building the cheapest application firewall

Today we'll learn how to build the cheapest and smallest - but efficient - layer 7 firewall. Then you don't need anymore to buy very expensive appliances for URL filtering (F5 BigIP, Netscaler,..) and you don't have to spend hours to make Apache mod_security work. This security can be added to the Tomcat Security Manager if you are running a reverse-proxy, and provides in-depth security as advised by many including official government services such as ANSSI (Agence nationale de la sécurité des systèmes d’information).

With Apache 2.4 we get the opportunity to try LUA through the experimental mod_lua module. This module provides LUA scripting as well as being very compatible with Apache. It's a good way to interfere with HTTP requests/responses because it can go into full request inspection (mod_rewrite cannot), and without breaking the others modules. So you still can write proxy configs or anything else, which is very usable. Now, let's start saving money :

  1. Installing Apache
    You can install Apache from binaries, otherwise compile it with --enable-lua (assuming you have both lua and lua-devel packages on your Linux).
  2. Enabling mod_lua
    In your httpd.conf :
    LoadModule lua_module modules/mod_lua.so
  3. Add a layer 7 firewall based on keywords blacklisting

    <LuaQuickHandler firewall>
    require "apache2"

    function firewall(r)

    -- contains all the forbidden words raising a 403
    forbiddenWords = { 'INSERT',
    'SELECT',
    'UPDATE',
    'DELETE',
    'DISTINCT',
    'HAVING',
    'TRUNCATE',
    'REPLACE',
    'handler',
    'like',
    'procedure',
    'limit',
    'order by',
    'group by',
    'script',
    'document',
    'java.io.',
    '%.%.'
    }

    -- contains the result
    foundForbidden = false

    -- is debug enabled ?
    isDebug = true

    local f = io.open("/tmp/debug-lua.log", "a")
    if f then
    if r.method == 'GET' then
    for i, v in ipairs(forbiddenWords) do
    if string.find( string.lower(url_decode(r.the_request)), string.lower(v) ) then
    foundForbidden = true
    end
    if isDebug then
    f:write( string.lower(url_decode(r.the_request)).." , "..string.lower(v).." , " .. tostring(foundForbidden) .." \n")
    end
    end

    elseif r.method == 'POST' then
    for k, w in pairs( r:parsebody() ) do
    for i, v in ipairs(forbiddenWords) do
    if string.find( url_decode(string.lower(w)), string.lower(v) ) then
    foundForbidden = true
    end
    if isDebug then
    f:write( string.lower(url_decode(string.lower(w))).." , "..string.lower(v).." , " .. tostring(foundForbidden) .." \n")
    end
    end
    end
    end

    f:close()
    end

    if foundForbidden then
    return 403
    else
    return apache2.DECLINED
    end
    end

    -- url decoding as anywhere in the web
    function url_decode(str)
    str = string.gsub (str, "+", " ")
    str = string.gsub (str, "%%(%x%x)",
    function(h) return string.char(tonumber(h,16)) end)
    str = string.gsub (str, "\r\n", "\n")
    return str
    end

    </LuaQuickHandler>

  4. Final word
    We now have the cheapest application firewall running on Apache. It doesn't provide all the features of a "professional" product (parsing of headers, checking cookies length,..) but we could easily add some features to make it more complete, but also heavier. Of course, you cannot really compare it to a F5 or similar because it's not a load-balancer by itself.
    This solution might introduce some latency under heavy load or when dealing with huge POST requests, and I will do some benchmark on this some day.

Theme by Danetsoft and Danang Probo Sayekti inspired by Maksimer