Skip to content

7g Firewall for Nginx

Converting the 7G Firewall to Nginx

The 7G firewall was created by Jeff Starr and is available on his website at https://perishablepress.com/7g-firewall/. The firewall is basically a set of Apache rewrite rules designed to be inserted into an htaccess file.

I personally like all my site security done at the reverse proxy level which, for me, means Nginx. I have therefore attempted to convert the 7G firewall so that it works with Nginx. I am in no way a regex guru, nor am I an Nginx expert for that matter, so I can’t say for sure that I’ve done this without error. From what I can tell though with the testing I’ve performed, it does appear to be working OK.

In the Nginx conf.d folder, I created a file named 7g.conf and filled it as follows:

sudo nano /etc/nginx/conf.d/7g.conf
# Converted from 7G Firewall

map $request_uri $request_uri_path {
  "~^(?P<path>[^?]*)(\?.*)?$"  $path;
}

map $query_string $bad_querystring {
    default '[OK]';
    "~*([a-z0-9]{2000,})" '[bad_querystring_rule_1]';
    "~*(/|%2f)(:|%3a)(/|%2f)" '[bad_querystring_rule_2]';
    "~*(/|%2f)(\*|%2a)(\*|%2a)(/|%2f)" '[bad_querystring_rule_3]';
    "~*(~|`|<|>|%3c|%3e|<|>|\^|\|\\\|0x00|%00|%0d%0a)" '[bad_querystring_rule_4]';
    "~*(cmd|command)(=|%3d)(chdir|mkdir)(.*)(x20)" '[bad_querystring_rule_5]';
    "~*(fck|ckfinder|fullclick|ckfinder|fckeditor)" '[bad_querystring_rule_6]';
    "~*(/|%2f)((wp-)?config)((\.|%2e)inc)?((\.|%2e)php)" '[bad_querystring_rule_7]';
    "~*(thumbs?(_editor|open)?|tim(thumbs?)?)((\.|%2e)php)" '[bad_querystring_rule_8]';
    "~*(absolute_|base|root_)(dir|path)(=|%3d)(ftp|https?)" '[bad_querystring_rule_9]';
    "~*(localhost|loopback|127(\.|%2e)0(\.|%2e)0(\.|%2e)1)" '[bad_querystring_rule_10]';
    "~*(\.|20)(get|the)(_|%5f)(permalink|posts_page_url)(\(|%28)" '[bad_querystring_rule_11]';
    "~*(s)?(ftp|http|inurl|php)(s)?(:(/|%2f|%u2215)(/|%2f|%u2215))" '[bad_querystring_rule_12]';
    "~*(globals|mosconfig([a-z_]{1,22})|request)(=|\[|%[a-z0-9]{0,2})" '[bad_querystring_rule_13]';
    "~*((boot|win)((\.|%2e)ini)|etc(/|%2f)passwd|self(/|%2f)environ)" '[bad_querystring_rule_14]';
    "~*(((/|%2f){3,3})|((\.|%2e){3,3})|((\.|%2e){2,2})(/|%2f|%u2215))" '[bad_querystring_rule_15]';
    "~*(benchmark|char|exec|fopen|function|html)(.*)(\(|%28)(.*)(\)|%29)" '[bad_querystring_rule_16]';
    "~*(php)([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})" '[bad_querystring_rule_17]';
    "~*(e|%65|%45)(v|%76|%56)(a|%61|%31)(l|%6c|%4c)(.*)(\(|%28)(.*)(\)|%29)" '[bad_querystring_rule_18]';
    "~*(/|%2f)(=|%3d|$&|_mm|cgi(\.|-)|inurl(:|%3a)(/|%2f)|(mod|path)(=|%3d)(\.|%2e))" '[bad_querystring_rule_19]';
    "~*(<|%3c)(.*)(e|%65|%45)(m|%6d|%4d)(b|%62|%42)(e|%65|%45)(d|%64|%44)(.*)(>|%3e)" '[bad_querystring_rule_20]';
    "~*(<|%3c)(.*)(i|%69|%49)(f|%66|%46)(r|%72|%52)(a|%61|%41)(m|%6d|%4d)(e|%65|%45)(.*)(>|%3e)" '[bad_querystring_rule_21]';
    "~*(<|%3c)(.*)(o|%4f|%6f)(b|%62|%42)(j|%4a|%6a)(e|%65|%45)(c|%63|%43)(t|%74|%54)(.*)(>|%3e)" '[bad_querystring_rule_22]';
    "~*(<|%3c)(.*)(s|%73|%53)(c|%63|%43)(r|%72|%52)(i|%69|%49)(p|%70|%50)(t|%74|%54)(.*)(>|%3e)" '[bad_querystring_rule_23]';
    "~*(\+|%2b|%20)(d|%64|%44)(e|%65|%45)(l|%6c|%4c)(e|%65|%45)(t|%74|%54)(e|%65|%45)(\+|%2b|%20)" '[bad_querystring_rule_24]';
    "~*(\+|%2b|%20)(i|%69|%49)(n|%6e|%4e)(s|%73|%53)(e|%65|%45)(r|%72|%52)(t|%74|%54)(\+|%2b|%20)" '[bad_querystring_rule_25]';
    "~*(\+|%2b|%20)(s|%73|%53)(e|%65|%45)(l|%6c|%4c)(e|%65|%45)(c|%63|%43)(t|%74|%54)(\+|%2b|%20)" '[bad_querystring_rule_26]';
    "~*(\+|%2b|%20)(u|%75|%55)(p|%70|%50)(d|%64|%44)(a|%61|%41)(t|%74|%54)(e|%65|%45)(\+|%2b|%20)" '[bad_querystring_rule_27]';
    "~*(\\\x00|(\"|%22|\'|%27)?0(\"|%22|\'|%27)?(=|%3d)(\"|%22|\'|%27)?0|cast(\(|%28)0x|or%201(=|%3d)1)" '[bad_querystring_rule_28]';
    "~*(g|%67|%47)(l|%6c|%4c)(o|%6f|%4f)(b|%62|%42)(a|%61|%41)(l|%6c|%4c)(s|%73|%53)(=|[|%[0-9A-Z]{0,2})" '[bad_querystring_rule_29]';
    "~*(_|%5f)(r|%72|%52)(e|%65|%45)(q|%71|%51)(u|%75|%55)(e|%65|%45)(s|%73|%53)(t|%74|%54)(=|[|%[0-9A-Z]{0,2})" '[bad_querystring_rule_30]';
    "~*(j|%6a|%4a)(a|%61|%41)(v|%76|%56)(a|%61|%31)(s|%73|%53)(c|%63|%43)(r|%72|%52)(i|%69|%49)(p|%70|%50)(t|%74|%54)(:|%3a)(.*)(;|%3b|\)|%29)" '[bad_querystring_rule_31]';
    "~*(b|%62|%42)(a|%61|%41)(s|%73|%53)(e|%65|%45)(6|%36)(4|%34)(_|%5f)(e|%65|%45|d|%64|%44)(e|%65|%45|n|%6e|%4e)(c|%63|%43)(o|%6f|%4f)(d|%64|%44)(e|%65|%45)(.*)(\()(.*)(\))" '[bad_querystring_rule_32]';
    "~*(allow_url_(fopen|include)|auto_prepend_file|blexbot|browsersploit|(c99|php)shell|curltest|disable_functions?|document_root|elastix|encodeuricom|exec|exploit|fclose|fgets|fputs|fsbuff|fsockopen|gethostbyname|grablogin|hmei7|input_file|load_file|null|open_basedir|outfile|passthru|popen|proc_open|quickbrute|remoteview|root_path|safe_mode|shell_exec|site((.){0,2})copier|sux0r|trojan|wget|xertive)" '[bad_querystring_rule_33]';
    "~*(;|<|>|\'|\"|\)|%0a|%0d|%22|%27|%3c|%3e|%00)(.*)(/\*|alter|base64|benchmark|cast|char|concat|convert|create|encode|declare|delete|drop|insert|md5|order|request|script|select|set|union|update)" '[bad_querystring_rule_34]';
    "~*((\+|%2b)(concat|delete|get|select|union)(\+|%2b))" '[bad_querystring_rule_35]';
    "~*(union)(.*)(select)(.*)(\(|%28)" '[bad_querystring_rule_36]';
    "~*(concat)(.*)(\(|%28)" '[bad_querystring_rule_37]';
    }

# Converted from 7G Firewall
map $request_uri_path $bad_request {
    default '[OK]';
    "~*([a-z0-9]{2000,})" '[bad_request_rule_1]';
    "~*((=?\\\(\'|%27)/?)(\.))" '[bad_request_rule_2]';
    "~*(/)(\*|\"|\'|\.|,|&|&amp;?)/?$" '[bad_request_rule_3]';
    "~*(\.)(php)(\()?([0-9]+)(\))?(/)?$" '[bad_request_rule_4]';
    "~*(/)(vbulletin|boards|vbforum)(/)?" '[bad_request_rule_5]';
    "~*(\^|~|`|<|>|,|%|\\\|\{|\}|\[|\]|\|)" '[bad_request_rule_6]';
    "~*(\.(s?ftp-?)config|(s?ftp-?)config\.)" '[bad_request_rule_7]';
    "~*(\{0\}|\"?0\"?=\"?0|\(/\(|\.\.\.|\+\+\+|\\\")" '[bad_request_rule_8]';
    "~*(thumbs?(_editor|open)?|tim(thumbs?)?)(\.php)" '[bad_request_rule_9]';
    "~*(/)(fck|ckfinder|fullclick|ckfinder|fckeditor)" '[bad_request_rule_10]';
    "~*(\.|20)(get|the)(_)(permalink|posts_page_url)(\()" '[bad_request_rule_11]';
    "~*(///|\?\?|/&&|/\*(.*)\*/|/:/|\\\\\\|0x00|%00|%0d%0a)" '[bad_request_rule_12]';
    "~*(/%7e)(root|ftp|bin|nobody|named|guest|logs|sshd)(/)" '[bad_request_rule_13]';
    "~*(/)(etc|var)(/)(hidden|secret|shadow|ninja|passwd|tmp)(/)?$" '[bad_request_rule_14]';
    "~*(s)?(ftp|http|inurl|php)(s)?(:(/|%2f|%u2215)(/|%2f|%u2215))" '[bad_request_rule_15]';
    "~*(/)(=|\$&?|&?(pws|rk)=0|_mm|_vti_|cgi(\.|-)?|(=|/|;|,)nt\.)" '[bad_request_rule_16]';
    "~*(\.)(conf(ig)?|ds_store|htaccess|htpasswd|init?|mysql-select-db)(/)?$" '[bad_request_rule_17]';
    "~*(/)(bin)(/)(cc|chmod|chsh|cpp|echo|id|kill|mail|nasm|perl|ping|ps|python|tclsh)(/)?$" '[bad_request_rule_18]';
    "~*(/)(::[0-9999]|%3a%3a[0-9999]|127\.0\.0\.1|localhost|loopback|makefile|pingserver|wwwroot)(/)?" '[bad_request_rule_19]';
    "~*(\(null\)|\{\$itemURL\}|cAsT\(0x|echo(.*)kae|etc/passwd|eval\(|self/environ|\+union\+all\+select)" '[bad_request_rule_20]';
    "~*(/)(awstats|(c99|php|web)shell|document_root|error_log|listinfo|muieblack|remoteview|site((.){0,2})copier|sqlpatch|sux0r)" '[bad_request_rule_21]';
    "~*(/)((php|web)?shell|conf(ig)?|crossdomain|fileditor|locus7|nstview|php(get|remoteview|writer)|r57|remview|sshphp|storm7|webadmin)(.*)(\.|\()" '[bad_request_rule_22]';
    "~*(/)(author-panel|bitrix|class|database|(db|mysql)-?admin|filemanager|htdocs|httpdocs|https?|mailman|mailto|msoffice|mysql|_?php-?my-?admin(.*)|sql|system|tmp|undefined|usage|var|vhosts|webmaster|www)(/)" '[bad_request_rule_23]';
    "~*(base64_(en|de)code|benchmark|child_terminate|e?chr|eval|exec|function|fwrite|(f|p)open|html|leak|passthru|p?fsockopen|phpinfo|posix_(kill|mkfifo|setpgid|setsid|setuid)|proc_(close|get_status|nice|open|terminate)|(shell_)?exec|system)(.*)(\()(.*)(\))" '[bad_request_rule_24]';
    "~*(\.)(7z|ab4|afm|aspx?|bash|ba?k?|bz2|cfg|cfml?|cgi|conf(ig)?|ctl|dat|db|dll|eml|et2|exe|fec|fla|hg|inc|ini|inv|jsp|log|lqd|mbf|mdb|mmw|mny|old|one|out|passwd|pdb|pl|psd|pst|ptdb|pwd|py|qbb|qdf|rar|rdf|sdb|sql|sh|soa|swf|swl|swp|stx|tar|tax|tgz|tls|tmd|wow|zlib)$" '[bad_request_rule_25]';
    "~*(/)(^$|00.temp00|0day|3xp|70bex?|admin_events|bkht|(php|web)?shell|configbak|curltest|db|dompdf|filenetworks|hmei7|index\.php/index\.php/index|jahat|kcrew|keywordspy|mobiquo|mysql|nessus|php-?info|racrew|sql|ucp|webconfig|(wp-)?conf(ig)?(uration)?|xertive)(\.php)" '[bad_request_rule_26]';
    }

# Converted from 7G Firewall
map $request_method $bad_request_method {
    default '[OK]';
    "~*^(connect|debug|delete|move|put|trace|track)" '[bad_request_method_rule_1]';
    }

# Converted from 7G Firewall
map $request_method $bad_request_method2 {
    default '[OK]';
    "~*^(connect|debug|delete|move|trace|track)" '[bad_request_method2_rule_1]';
    }

The original 7g file was testing the Apache request URI. Nginx unfortunately doesn’t have an exact equivalent so I created one with:

map $request_uri $request_uri_path {
  "~^(?P<path>[^?]*)(\?.*)?$"  $path;
}

This strips out the query string and places the rest into a variable called $request_uri_path

In the next section, I mapped the query string to a variable named $bad-querystring. This variable defaults to ‘[OK]’ and is changed to a unique error code if the query string happens to match one of the following rules.

I followed a similar method in the remaining sections, testing the request URI and also the request method. I actually created two tests for the request method, one of them a little less strict and allowing ‘put’ requests. This was intended for my Nextcloud site.

In the server block for my website, I added the following:

sudo nano /etc/nginx/sites-available/example.com
server {
    ...

    if ( $bad_querystring !~* "\[OK\]" ) {
        set $blockreason $bad_querystring;
        return 403;
    }

    if ( $bad_request !~* "\[OK\]" ) {
        set $blockreason $bad_request;
        return 403;
    }

    if ( $bad_request_method !~* "\[OK\]" ) {
        set $blockreason $bad_request_method;
        return 403;
    }

...
}

This blocks traffic that matches any of the rules defined in the 7g.conf file. It also populates a variable $blockreason with the error message.

In my nginx.conf file, I defined this variable and added it to the log format:

sudo nano /etc/nginx/nginx.conf
http {
    ...

    log_format  main  '$remote_addr - $remote_user [$time_local] $host "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for" "$blockreason"';


    map $host $blockreason {
        default '[OK]';
    }

    ...
}

This makes troubleshooting much easier. If a request is blocked, I can check the log and see exactly which rule blocked it. I can also pick the lines out much easier using regex, which makes it easy to use it in conjunction with something like fail2ban.

If one of the rules is causing a problem with a site, I can easily change the statement in the server block to ignore that rule, eg:

server {
    ...

    if ( $bad_querystring !~* "\[OK\]|\[bad_querystring_rule_10\]" ) {
        set $blockreason $bad_querystring;
        return 403;
    }

    ...
}

Finally, check the config and reload:

sudo nginx -t
sudo service nginx reload