SPIP Web Application Firewall (WAF)

Code work in progress

Web Application Firewall for SPIP. Blocks malicious requests and bans repeat offenders.

Web Application Firewall for SPIP. Blocks malicious requests and bans repeat offenders.

Installation

SPIP WAF is installed like any other SPIP plugin. It requires the PHP-extension APCU enabled.

Most servers have APCU enabled by default. The plugin will refuse to install if APCU is not found. So you can try installing, and if it fails, check if you can find instructions on how to enable/install php-apcu for your hosting provider.

Background

SPIP is a secure CMS with a strong track record in terms of security and stability. At the same time, webmasters see an increased flood of malicious server attacks. This plugin will block malicious requests and help protect sites from malicous traffic.

How it works

  1. Rules check every request for suspicious patterns (SQL injection, path traversal, CMS probes, etc.)
  2. Each violation is blocked and logged.
  3. After several violations (_WAF_STRIKE_THRESHOLD), the IP gets banned.
  4. Banned visitors see an unblock form (useful for false positives).
  5. If they keep triggering rules after unblocking, they get banned again.
  6. After too many bans (_WAF_BLOCK_THRESHOLD), the IP is permanently hard-blocked. In this case, users (bots) see a plain 403 error.

Flow: Blocked → Banned → Hard-blocked

Full docs: https://contrib.spip.net/WAF (coming soon)

Locked out?

If you got hard-blocked (e.g. after testing with malicious payloads), whitelist your IP via mes_options.php.

Find your IP at https://iplocation.net (looks like 203.0.113.42).

Tip: If you can’t access the site at all, switch to a different network (e.g. phone hotspot), log into /ecrire from there, and add your IP in the WAF config form. Then switch back.

To whitelist via code, add this to config/mes_options.php (create the file if needed):

define('_WAF_WHITELIST', '203.0.113.42');

Multiple IPs, one per line:

define('_WAF_WHITELIST',
'203.0.113.42
198.51.100.7
198.51.100.8'
);

Once saved, the WAF lets these IPs through.

Custom rules

The plugin exposes a waf_custom_rules pipeline. Define your rule function in mes_options.php and hook it into the pipeline.

Block a specific GET parameter:

function waf_custom_rule__block_foo_bar($ctx) {
	if (isset($ctx['get']['foo']) && $ctx['get']['foo'] === 'bar') {
		waf_handle_violation('CUSTOM_RULE', 'foo=bar', $ctx['client_ip']);
	}
}
$GLOBALS['spip_pipeline']['waf_custom_rules'] = '|waf_custom_rule__block_foo_bar';

Block e.g. ?page=spipdf (when the SPIPDF plugin is not installed):

function waf_custom_rule__block_spipdf($ctx) {
	if (isset($ctx['get']['page']) && $ctx['get']['page'] === 'spipdf') {
		waf_handle_violation('CUSTOM_RULE', 'blocked_param=page, value=spipdf', $ctx['client_ip']);
	}
}
$GLOBALS['spip_pipeline']['waf_custom_rules'] .= '|waf_custom_rule__block_spipdf';

Disabling rules

The waf_handle_violation pipeline lets you ignore specific violations. Return true to skip the violation.

Example: allow <script> tags when editing articles in the back-office:

function waf_override__allow_script_in_articles($ctx) {
	$args = $ctx['args'];
	if (
		isset($args['get']['exec']) && $args['get']['exec'] === 'article_edit'
		&& $args['reason'] === 'PATTERN_MATCH'
		&& (
			$args['pattern'] == '/<script/i'
			|| $args['pattern'] == '/<\/script>/i'
		)
	) {
		return true;
	}
	return false;
}
$GLOBALS['spip_pipeline']['waf_handle_violation'] = '|waf_override__allow_script_in_articles';

Profiling

Add to config/mes_options.php:

define('_WAF_PROFILE', microtime(true));
define('_LOG_FILTRE_GRAVITE', _LOG_INFO);

The second line is required if _LOG_FILTRE_GRAVITE is not defined, in order to set the proper log leve. Otherwise, no log output is written.

Each request logs one line to tmp/log/waf.log:

WAF_PROFILE PASS total:3.42ms fn:1.18ms
WAF_PROFILE VIOLATION total:5.01ms fn:2.73ms
  • total — time from the define() to the WAF exit (includes SPIP bootstrap)
  • fn — time inside waf() only
  • Outcomes: PASS, WHITELISTED, NO_IP, BLACKLISTED, BLOCKLISTED, HARDBLOCKED, BANNED, UNBLOCK_REDIRECT, VIOLATION

Remove both lines when done.

CLI stats

Stats for a given outcome (replace PASS with any outcome) for MIN, AVERAG; MAX:

For basic min, average, max stats:

grep 'WAF_PROFILE PASS ' tmp/log/waf.log | grep -oP 'fn:\K[0-9.]+' | awk '{s+=$1;n++;if(!min||$1<min)min=$1;if($1>max)max=$1} END{printf "n=%d min=%.2fms avg=%.2fms max=%.2fms\n",n,min,s/n,max}'

For Percentiles (p50/p95/p99): (This bash code is run in the the root directory of the SPIP install).

#go to root directory of SPIP install: 
cd /var/www/html

It loops over the possible outcomes and lists the stats.

#Output percentiles per outcome: 
for outcome in PASS WHITELISTED NO_IP BLACKLISTED BLOCKLISTED HARDBLOCKED BANNED UNBLOCK_REDIRECT VIOLATION; do
  echo ""
  echo "--- $outcome ---"
  grep "WAF_PROFILE $outcome " tmp/log/waf.log \
    | grep -oP 'fn:\K[0-9.]+' \
    | sort -n \
    | awk '{a[NR]=$1} END{
        printf "n=%d  p50=%.2fms  p95=%.2fms  p99=%.2fms\n",
          NR,
          a[int(NR*0.50)+1],
          a[int(NR*0.95)+1],
          a[int(NR*0.99)+1]
      }'
done

Overview of all outcome types in the log:

grep -oP 'WAF_PROFILE \K\S+' tmp/log/waf.log | sort | uniq -c | sort -rn

Sample screenshots

The config page, where we must set the request header that lists the correct IP
The stats page, where we can see the blocked requests
The stats page, where we can see the blocked requests

Discussion

No discussion

Add a comment

Avant de faire part d’un problème sur un plugin X, merci de lire ce qui suit :

  • Désactiver tous les plugins que vous ne voulez pas tester afin de vous assurer que le bug vient bien du plugin X. Cela vous évitera d’écrire sur le forum d’une contribution qui n’est finalement pas en cause.
  • Cherchez et notez les numéros de version de tout ce qui est en place au moment du test :
    • version de SPIP, en bas de la partie privée
    • version du plugin testé et des éventuels plugins nécessités
    • version de PHP (exec=info en partie privée)
    • version de MySQL / SQLite
  • Si votre problème concerne la partie publique de votre site, donnez une URL où le bug est visible, pour que les gens puissent voir par eux-mêmes.
  • En cas de page blanche, merci d’activer l’affichage des erreurs, et d’indiquer ensuite l’erreur qui apparaît.

Merci d’avance pour les personnes qui vous aideront !

Par ailleurs, n’oubliez pas que les contributeurs et contributrices ont une vie en dehors de SPIP.

Who are you?
[Log in]

To show your avatar with your message, register it first on gravatar.com (free et painless) and don’t forget to indicate your Email addresse here.

Enter your comment here

This form accepts SPIP shortcuts {{bold}} {italic} -*list [text->url] <quote> <code> and HTML code <q> <del> <ins>. To create paragraphs, just leave empty lines.

Add a document

Follow the comments: RSS 2.0 | Atom