PremiumComes with the add-on reqad-modsecurity. Installing add-ons

ModSecurity (WAF)

The ModSecurity add-on puts a web application firewall in front of your sites: ModSecurity v3 on nginx with the OWASP Core Rule Set, which stops SQL injection, cross-site scripting, remote code execution and scanners looking for weak spots before they reach PHP. You switch it on per hosting account, see every request it stopped, and switch off a rule that gets in a site's way, for one page or everywhere.

Premium add-on. Package reqad-modsecurity, see installing an add-on. It adds Settings > ModSecurity to the menu and the reqad modsec command, and installs the OWASP rules (reqad-coreruleset, a signed package of the rules' LTS release) and libmodsecurity with it. It works with nginx in front of your sites (the nginx_php-fpm template, and Apache behind nginx); sites served by Apache alone are not covered yet.

What the first install does

  • Loads the rules in Detection only mode: requests are checked and logged, nothing is blocked.
  • Paranoia level 1, blocking threshold 5, request bodies inspected up to 128 MB, every known exploit rule on.
  • Switches it on for no account. You choose which accounts get it on the page. Accounts created from then on get it by themselves (the New accounts switch, on by default).
  • Adds the load_module line for ModSecurity to /etc/nginx/nginx.conf if it is missing.

Requests from the server's own addresses (WordPress cron, loopback checks) and Let's Encrypt's certificate checks are never inspected.

The safe way to start

  1. Leave the server in Detection only and switch ModSecurity on for the accounts you want protected.
  2. For a week or two, look at Blocked requests. Everything listed as Would be blocked is what Blocking mode would stop. Most of it is scanners and attacks; look for pages of your own sites, such as a contact form, a page builder or a shop checkout.
  3. For each one that is not an attack, press Switch off on its row and keep Only on this page (see switching rules off).
  4. Switch the mode to Blocking.

After 14 days in Detection only, Needs attention reminds you that nothing is blocked yet.

Server settings

SettingWhat it does
ModeOff: nothing is inspected. Detection only: attacks are logged, nothing is blocked. Blocking: a request whose score reaches the threshold gets an error page. It applies to every account that has ModSecurity on.
Paranoia levelHow many OWASP rules run. Level 1 has few false positives and suits shared hosting; levels 2 to 4 add stricter rules that need tuning site by site.
Blocking thresholdEach rule a request matches adds to its score (critical 5, error 4, warning 3). 5 blocks on one critical match. A higher threshold blocks less.
New accountsSwitch ModSecurity on for accounts created from now on.
Request sizeRequest bodies are inspected up to this size, and bigger uploads are refused (413), so an attack cannot hide past the part that is inspected.

nginx checks every change before it goes live. A change it does not accept changes nothing, and the page says why. Re-apply writes the configuration again from the saved settings.

A visitor who is blocked sees the site's "page not found" page: Reqad's error pages answer a 403 with it, so a blocked request does not tell an attacker which rule stopped it.

Known exploits

Reqad's own rules for exploits that are being used right now, shipped with the add-on and its updates, with a table of what each one covers, its CVE and the date it became public. They block in every mode, Detection only included, for every account that has ModSecurity on, because they only match the exploit itself. They are a stopgap until the software is updated, not a replacement for the update. Each one has its own switch.

Accounts

Tick accounts and press Turn on or Turn off. The switch covers all the account's sites: its main domain, addon domains, subdomains and aliases, and sites it adds later. If a site's configuration was written again since (a new certificate, a PHP change), an hourly check puts the switch back; the list shows On, missing in N sites until then. An account's badge shows how many rules it switched off.

A site with ModSecurity lines of its own in its nginx configuration (a manual setup from before) is marked own ModSecurity lines. If those lines load rules too, nginx refuses the panel's rules (the same rule loaded twice). Take the lines out under Accounts > Manage > Config, switch the account on here, and move your own rules to Custom rules.

Blocked requests

Settings > ModSecurity > Blocked requests lists what ModSecurity stopped in the last 7 days. The log is read every minute.

OutcomeMeaning
BlockedThe visitor got an error page instead of the site (Blocking mode, or a known-exploit rule).
Would be blockedThe score reached the threshold, but the server is in Detection only, so it was let through and logged.
LoggedA rule matched but the score stayed under the threshold, or the site's own rules had already refused the request. Hidden unless you choose Everything a rule matched.
  • Filters: account, domain, IP address (or the start of one), rule id, a search in the address, browser and rule message, the outcome, and the last hour, 24 hours or 7 days.
  • Group repeats (on by default) puts the requests of one IP address to one site that the same rule stopped in a single row, with a count. A scanner trying 200 addresses takes one row. Untick it to see each request.
  • At the top: how many were blocked, would be blocked and logged, and the rules, IP addresses and sites seen most.
  • Details on a row shows the full address, the answer, the browser and every rule that matched with what it matched.

What is kept: the time, site, IP address, method and address, the browser name, the answer and the rules with their messages. Request bodies are never logged, and of the headers only the browser name is kept: never cookies or Authorization headers. When a rule matched a cookie or a field whose name looks secret (password, token, key), the value is left out.

Switching rules off

A false positive is a request that is not an attack but looks like one to a rule: a page builder saving HTML, a form that accepts code snippets. The easiest fix is Switch off on its row in Blocked requests. It fills in everything:

  • Rules: every rule that matched the request, all ticked. Untick the ones to keep.
  • For: the sites of that account, or every site on the server.
  • Where: Only on this page (the address of the request, which you can edit; end it with * for everything under it, such as /wp-json/*) or on every page.

Prefer one page: the rule keeps protecting everything else. Rule 949110 only adds up the score of the others, so switch off the rules named with it, not 949110.

Rules switched off for the whole server are listed under Rules switched off for the whole server, where you can also add one by its id. Rules switched off for an account, by you or by the account itself in its panel, are listed under Rules switched off by accounts. Each row has Switch on again. Accounts can only switch off rules of the OWASP Core Rule Set (ids 905000 to 999999), never the known-exploit rules or your custom rules.

Custom rules

Your own ModSecurity rules, loaded after the OWASP rules for every account that has ModSecurity on, in an editor with syntax highlighting. nginx tests them before they go live: a rule it does not accept changes nothing, and the page shows the line and the column. Things to know:

  • Use rule ids 1-99999 or 1000000-1099999. 900000-999999 belong to the OWASP rules, and 1290000-1299999 to Reqad.
  • Like the OWASP rules, a deny only logs while the server is in Detection only. Add ctl:ruleEngine=On to a rule that must block anyway.
  • A phase:1 rule with ctl:ruleRemoveById can switch OWASP rules off for the requests it matches.
  • skipAfter needs a SecMarker of that name further down.
  • Include is not allowed. Put lists in a data file instead (IP addresses and ranges for @ipMatchFromFile, words for @pmFromFile, one per line) and use its path, /etc/nginx/reqad-modsec/data/<name>, in the rule.

In the account panel

With the account panel, each account has Advanced > WAF rules:

  • A switch that turns ModSecurity on or off for all its sites.
  • Blocked requests of its own sites, with the same outcomes, a filter by site, IP address, outcome and period, and the details of each request.
  • Switch this rule off on a request, with Only on this page chosen. Rules that protect the whole server (the known exploits, your custom rules) say that only you can change them.
  • The rules it switched off, each with the page it applies to, and a button to switch it on again.

API and command line

The REST API has GET/PATCH /v1/waf (an account's switch and rules), POST /v1/waf/rules (id or ids, path, note), DELETE /v1/waf/rules/{id} and GET /v1/waf/blocked (the blocked requests, with the filters above). An account token sees only its own sites. There are no AI assistant tools for them: they switch protection off.

reqad modsec status
reqad modsec set --mode=on                     # off | detect | on; also --pl, --threshold, --body-mb
reqad modsec account <account> on|off
reqad modsec blocked --domain=example.com --period=24h
reqad modsec blocked --ip=203.0.113.7 --show=all --each
reqad modsec rule off 942100 --account=<account> --path=/contact/
reqad modsec rule off 941100,941160 --path='/wp-json/*'   # every site
reqad modsec rule list [--account=<account>]
reqad modsec rule on 942100 --account=<account> --path=/contact/
reqad modsec patch list
reqad modsec custom set --file=my-rules.conf

Where things are

PathWhat it is
/etc/nginx/reqad-modsec/The configuration the add-on writes: the main file, the OWASP setup, the known-exploit rules, your custom rules and data files, the rules switched off, and accounts/<account>.conf for each account's own. Do not edit: the next change from the panel writes it again.
/etc/nginx/conf.d/00-reqad-modsecurity.confLoads the rules once for the whole server. A site is inspected when its configuration has the reqad-modsec block with modsecurity on;.
/usr/share/reqad-coreruleset/The OWASP Core Rule Set, updated by its own package.
/var/log/reqad-modsec/audit.logModSecurity's log of the requests a rule matched, one line each, readable by root only, rotated daily and kept 14 days.
/var/lib/reqad/stats/modsec.dbThe list behind Blocked requests, 7 days.
/etc/cron.d/reqad-modsecurityReads the log every minute (reqad modsec collect) and checks the account switches hourly (reqad modsec sync).

Removing it

dnf remove reqad-modsecurity stops loading the rules and removes the page and the command. The modsecurity on; lines in the sites' configuration stay, harmless without rules, and so does /etc/nginx/reqad-modsec/, so installing the add-on again brings back your settings.


Need a hand? Create a free account or contact us.