Mod-Security WAF

Glen Tomkowiak
4 min readSep 26, 2021

The nginx ingress for Kubernetes has a built-in module that provides a web application firewall (WAF). Below is a bit of a cheat sheet that explains how options behave and common solutions to odd problems. There are several gotchas that can result in legitimate traffic being denied.

The following are the most common issues I personally ran into:

· Request / response limits still block requests even in report only mode. The fix is to enable a partial scan or just allow the traffic. This ultimately lowers security because a clever attacker could just use a large enough post. But blocking large posts could be unacceptable.

· Large requests have to be chunked to disk if you really want to process them entirely. This is a bad idea for performance, so limiting what you read is important.

· Partial processing is not without its quirks. For example, a large upload containing only compressed data has higher odds of randomly tripping an alert. Also many rules want to read text and part of a compressed file is unlikely to be useful.

· Not reading responses from the server can improve performance. This obviously presents some risk. But it also consumes CPU / memory to read every response and it’s not great for large downloads. So it could be better to just accept this risk and not attempt to read response data. Other counter measures could then be used to prevent risky responses.

Below are some rules worth examining:

SecRuleEngine On — This option enables the WAF in active mode, attacks will get a 403, this is based on a scoring system, rules are run in phases, 1–5. 1 is headers, 2 is body, etc. Then the score is added up and a threshold will cause a block.

SecUploadFileLimit — The default number of files you can upload in a post can be low, don’t accidently hit the limit.

SecRequestBodyLimit — A limit for the body size of a request.

SecRequestBodyNoFilesLimit — A similar limit but this relates only to requests without any file attachments.

SecRequestBodyAccess On — Request body buffering.

SecRequestBodyLimitAction ProcessPartial — Set this for requests that are larger than our limits. Your app might want to reject them or accept them depending on your priorities / application’s function. There are two risks from allowing partial uploads.

1: An attacker could just use large requests to attack.

2: The partial read can confuse the scanner, partial data is very likely to trip rules, so exempting URIs you know will get large requests is also required to prevent false positives. Consider implementing other countermeasures to prevent this being a problem.

SecRequestBodyLimitAction ProcessPartial — This is essential to ensure requests that are larger than our limits still pass to our server, without this the default action is to hard block anything large, which will render apps with large uploads unusable. There are two risks from allowing partial uploads. 1: An attacker could just use large requests to attack, 2: The partial read can / will confuse the scanner, partial data is very likely to trip rules, so exempting URIs you know will get large requests is also required to prevent false positives. Consider implementing other countermeasures to prevent this being a problem.

SecResponseBodyAccess Off — The response body can also be scanned to stop an attack from returning sensitive data. This isn’t the best place to stop an attack and it also consumes a good deal of CPU / memory to read through images, zips, etc. So it’s recommended to just not try.

SecResponseBodyLimit — Requests also need a limit or they will waste memory

SecResponseBodyLimitAction ProcessPartial — Large requests are just blocked by default even if you do nothing with them and even in DetectOnly mode, so it’s critical to just let them pass or you can break your application

SecAuditEngine RelevantOnly — Limit logging to relevant information.

SecRule REQUEST_URI “@beginsWith /some/uri/here” “id:1,phase:1,nolog,allow,ctl:ruleEngine=off” — You can exempt specific URIs by using @beginsWith or @contains if required.

SecRule ARGS_POST_NAMES “@contains password” “id:5,phase:2,nolog,allow,ctl:ruleEngine=off” — Passwords present a few odd and very bad problems. 1: A very complex password can trip a block rule, not sure why but it’s been raised a few times in the project’s issues, so best to exclude it. 2: Passwords could be logged to plain-text.

SecRule REQUEST_URI “.*.(dcm|nii|pdf|png|jpg|zip|csv|xlsx)?$” “id:6,phase:1,nolog,allow,ctl:ruleEngine=off” — Specific file types can be exempted.

SecAuditLog /dev/stdout — You can send logs to stdout, this can be useful if you are using tools like Loki and Grafana to stream logs. Then you can easily set alerts based on the log events.

Include /etc/nginx/owasp-modsecurity-crs/nginx-modsecurity.conf — A handy default list of OWASP attacks.

References:

· A good guide to basic setup: Enabling ModSecurity in the Kubernetes Ingress-NGINX Controller | by Fernando Diaz | Medium

· A nice book on all the options: ModSecurity Handbook: Getting Started: Chapter 3. Configuration (feistyduck.com)

· Tuning ModSecurity to reduce false positives: How to tune your WAF installation to reduce false positives — O’Reilly (oreilly.com)

--

--

Glen Tomkowiak

Things that interest me: cloud computing, cyber security, DevOps, and mobile / web development.