ApisCP has a new WAF, ladies and gentlemen!
mod_shield is the spiritual successor to mod_evasive that solves a couple key problems and adds several new enhancements to help combat unwanted traffic.
Evasive had a sharing problem - data in one httpd
process wasn’t visible in another httpd
process. An attack that closes its TCP connection may have subsequent TCP requests sent to a different process, which starts counting anew thereby bypassing existing counters. More httpd
processes translates to greater likelihood of requests exceeding their block threshold (or avoiding it altogether!).
Shield implements a cyclic shared buffer - default 512 KB - for each tracking criteria (site, page, and blocks). Each record is 176 bytes allowing for 2,978 unique entries that covers approximately 25 unique requests/second over a 2 minute tracking window. Entries are evicted through TTL and LRU, more on that later.
Tech landscape has evolved significantly since Evasive was released in 2005. Cloudflare serving both as a CDN and firewall is common. Either scenario ensconces a malicious connection behind their network, which makes edge-router blocking impossible. Connections flow through - hopeful that Cloudflare steps in to block - and continue to flow after the initial blocking period subside.
Shield solves this by adding blocking generations, for each successive block a new TTL is set extending the block up to 1 day.
Here’s a sample request pattern from a site behind Cloudflare. Note that once blocked, traffic still flows through. Shield closes the connection immediately without further processing.
52.169.150.195 x.y - [08/Jun/2025:01:33:39 -0400] "GET /xx.php HTTP/1.1" 404 196 "-" "-"
52.169.150.195 x.y - [08/Jun/2025:01:33:39 -0400] "GET /flower.php HTTP/1.1" 404 196 "-" "-"
52.169.150.195 x.y - [08/Jun/2025:01:33:39 -0400] "GET /admin.php HTTP/1.1" 404 196 "-" "-"
52.169.150.195 x.y - [08/Jun/2025:01:33:39 -0400] "GET /alfa.php HTTP/1.1" 404 196 "-" "-"
52.169.150.195 x.y - [08/Jun/2025:01:33:39 -0400] "GET /autoload_classmap.php HTTP/1.1" 404 196 "-" "-"
52.169.150.195 x.y - [08/Jun/2025:01:33:39 -0400] "GET /xxx.php HTTP/1.1" 404 196 "-" "-"
52.169.150.195 x.y - [08/Jun/2025:01:33:39 -0400] "GET /NewFile.php HTTP/1.1" 404 196 "-" "-"
52.169.150.195 x.y - [08/Jun/2025:01:33:39 -0400] "GET /css.php HTTP/1.1" 404 196 "-" "-"
52.169.150.195 x.y - [08/Jun/2025:01:33:40 -0400] "GET /info.php HTTP/1.1" 404 196 "-" "-"
52.169.150.195 x.y - [08/Jun/2025:01:33:40 -0400] "GET /gecko.php HTTP/1.1" 404 196 "-" "-"
52.169.150.195 x.y - [08/Jun/2025:01:33:40 -0400] "GET /admin/controller/extension/extension/ultra.php HTTP/1.1" 429 227 "-" "-"
52.169.150.195 x.y - [08/Jun/2025:01:33:40 -0400] "GET /classwithtostring.php HTTP/1.1" 429 227 "-" "-"
52.169.150.195 x.y - [08/Jun/2025:01:33:40 -0400] "GET /moon.php HTTP/1.1" 429 227 "-" "-"
52.169.150.195 x.y - [08/Jun/2025:01:33:40 -0400] "GET /ioxi-o.php HTTP/1.1" 429 227 "-" "-"
52.169.150.195 x.y - [08/Jun/2025:01:33:40 -0400] "GET /chosen HTTP/1.1" 429 227 "-" "-"
52.169.150.195 x.y - [08/Jun/2025:01:33:40 -0400] "GET /cong.php HTTP/1.1" 429 227 "-" "-"
52.169.150.195 x.y - [08/Jun/2025:01:33:40 -0400] "GET /item.php HTTP/1.1" 429 227 "-" "-"
52.169.150.195 x.y - [08/Jun/2025:01:33:40 -0400] "GET /x.php HTTP/1.1" 429 227 "-" "-"
52.169.150.195 x.y - [08/Jun/2025:01:33:41 -0400] "GET /k.php HTTP/1.1" 429 227 "-" "-"
52.169.150.195 x.y - [08/Jun/2025:01:33:41 -0400] "GET /lv.php HTTP/1.1" 429 227 "-" "-"
What’s new
-
Shield monitors bi-directionally. In addition to ingress, the following egress parameters have been added:
DOSSiteStatusPenalty (int status) (int score) increments (or decrements) score based upon status. May be used multiple times. Counts toward DOSSiteCount.
Example: Adds 50 points if a 404 is returned
DOSSiteStatusPenalty 404 50
Example: Immediately trigger block if authentication failsDOSSiteStatusPenalty 401 10000
DOSDeadlinePenalty (float time) (int score) increments (or decrements) score based upon request duration. Intended to thwart multiplexed attacks. May be used multiple times. Counts toward DOSPageCount.
Example: Add 30 points if a page takes longer than 5 seconds to load
DOSDeadlinePenalty 5 30
Example: Add 1 point if a page takes longer than 500 ms to loadDOSDeadlinePenalty 0.5 1
-
Blocking duration may grow for each successive block. This is useful if a connection is behind a downstream proxy, such as Cloudflare. Shield supports up to 16 blocking periods. When a block is active both RFC7231 Retry-After and working IETF draft RateLimit headers are sent to instruct the client to throttle requests.
Example: First block is 5 seconds, then 30 seconds, 60 seconds, and finally 1 hour
DOSBlockingPeriod 5 30 600
-
DOSWhitelist accepts CIDR notation as well as former * quad wildcards. May be used multiple times.
Example: Whitelist IPv6 range
DOSWhitelist 2a03:dead:beef::/24
-
DOSCache is discussed in Monitoring below.
-
Blocks in /tmp contain additional fields. Format is now:
PID<NL>[site|page] GENERATION COUNT HOSTNAME ORIGIN-TIMESTAMP<NL>URI
Shield is intended to be backwards compatible with Evasive directives. From benchmarks, Shield is approximately 13% faster owing to its binary protocol. Memory-efficient storage permits extending monitoring windows well beyond previously advised 5 second thresholds. Likewise, since Apache’s cache provider is used internally, setting DOSCache
to a provider other than shmcb (e.g. redis, memcache) is possible albeit concurrency issues may arise in high frequency environments.
Monitoring
A separate status handler has been added. It may be activated with the following directives in /etc/httpd/conf/httpd-custom.conf
:
<Location /shield>
SetHandler shield-handler
# It's a good idea to limit traffic to your IP.
# Accepts CIDR-style
Require ip your.ip.address
</Location>
Of interest, adjust DOSCache
if total (pre-expiry) entries reports a high eviction rate (>3%). It’s a standard shmcb directive, so to increase cache from 512 KB to 1 MB:
DOSCache shmcb:none(1048576)
Like to gawk at the junk you’re filtering? Use Cymru’s IP to ASN mapper:
(find /tmp -maxdepth 1 -type f -iname 'dos-*' | cut -d- -f2 | while read ip ; do
echo "$ip: $(whois -h whois.cymru.com $ip | tail -n 1)"
done) | sort -t'|' -k3
Installing
dnf --enablerepo=apnscp-testing update mod_shield
sed -ie 's/evasive/shield/g' /etc/fail2ban/jail.conf
systemctl restart httpd fail2ban
Configuring
Defaults are provided in /etc/httpd/conf.d/shield.conf
. Make changes to /etc/httpd/conf/httpd-custom.conf
to prevent overwrite.
Enjoy