SPIP WAF: Web Application Firewall for SPIP

Hello All!
I’ve started to work on a WAF for SPIP.

I have several high traffic site and I see a lot of automated attacks. Some against previous SPIP-CVEs, but others that are just sniffing for other CMS routes.

I have considered installing mod_security for Apache but this is cumbersome when running lots of SPIP sites in different circumstances. Plus, a WAF should be application specific, so and let through what needs to come though, and block the rest.

I know the ecran_securite already does a good job at blocking crawlers. The idea here is to block malicious hacker requests like /spip.php?get=<?php … or similar, clearly malicious requests.

The project is on the forge (for now only visible for logged in users):
https://git.spip.net/spip-contrib-extensions/waf

I will test these on several sites over the next weeks, to see if false positives come through.

Considerations:

  • Normally a WAF is run BEFORE the traffic reaches the application.
    – This can be feasible in the future by running the core functions of the plugin via PHP’s auto_prepend_file
  • But for now, I am directly calling the plugin’s main function via waf_options.php.
    – At this point, SPIP is bootstrapped very minimally, this is very early in the request, where I don’t expect security issues.

The plugin checks the requests parameters for malicious patters. Every violation results in a strike.

On strike, the response is delayed, the user gets an « blocked » screen.

On 3 strikes, the user is blocked. Meaning, any other request (malicious or not), is also blocked. The user gets a « blocked » screen.

  1. On the blocked screen, the user can unblock themselves.

  2. After, the user can use the site normally. If another strike is counted, the user is blocked again. The block is lifted after a timeout, currently 24h.

  3. BUT: If a max threshold is reached (e.g. 10 malicious requests in total, unblocked or not), the IP is permanently banned.

All the values can be adjusted via formulaire_config. The formulaire helps the user select the right HTTP header based on which to determine the IPs to block (important for users behind proxies). It also asks the user to whitelist their IP. Just in case…!

Anyhow, I will keep working on this on the forge, and hope it will reach a state where I’ll be able to deploy it on more SPIPs and eventually publish it.

Please feel free to have a look at the code on the forge and I’m happy if anyone wants to collaborate on this or is simply interested (or critical) in/of the approach.

Kind regards,
Urs

1 « J'aime »

Hello all,

I’ve made good progress on this plugin and have pushed a first version onto plugins.spip.net. I’ve been testing this on 3 sites with good results.

It is effectively blocking malicious requests e.g. those containing exec() and sql injection attempts. It is now also pulling in public IP blocklists, like those from Firehol and Spamhaus. It is also blocking Spam requests on forms for advanced bots where NoSpam does not work.

I will continue to monitor the plugin on several sites to understand false positives. Already I have experienced some false positives, leading to users blocked in the backend when entering certain texts that are caught as malicious. On some site I used a pipeline to disable the rule on that route, but the best is if no config is needed, so over time I hope to develop a list of safe rules and make the others optional.

The plugin adds two pages:

The stats page, where we can see the blocked requests:

The config page, where we must set the request header that lists the correct IP:

So, for people interested to block malicious traffic, the plugin is accomplishing this well now. It may be a bit too aggressive, especially for backend users. There is a Whitelist, that can be supplied via mes_options.php in case of lockouts, and this is also documented in the contrib page: https://contrib.spip.net/WAF (awaiting publication).

On the use of AI: I have used Zed-editor’s built in auto-completion and some agent workflows (Opus 3.6). It was especially handy for translations (currently English, French and German). I can see the value but also how it over-builds and bloats things very quickly. Everything needs to be reviewed, that’s for sure. Maybe it’s better to have no translations than an AI-one - please let me know your opinion on the French side!

Moving forward: I will be testing it on about 10 SPIP sites and see what feedback I get from visitors and backend users. There are other things to do, like a SPIP-cron to clear out old data, to keep the database small; and the unblock form (presented to users when their request is blocked) is a simple button, not even a captcha yet. It has proven very effective so far, but it won’t be lasting for long…

Anyhow, I’m happy if anyone wants to test it, provide feedback or contributions!

Kind regards,
Urs

3 « J'aime »

298
Hi Urs,

I activated the plugin on one of my Cloudflare sites to see how it performs.

I submitted pull requests this morning to fix/improve the code, and I think it could be improved further with a separate CSS file and the PHP code contained in functions, perhaps?

Have a good day.

Hello Pierre,

It was great to see your PR’s, thank you.

Especially the IP sanitization one! I’ve approved the others as well.

For the one about the the TEMP path: I added a comment directly on your PR about using sous_repertoire() instead. I think that’s the recommended way now?

Thanks about the other inputs too. I’ll separate the CSS into a shared file.
For separating waf() into smaller functions, I agree the main function is super long! Let me see if I can break it up into smaller function, like waf_config() to load all settings, waf_check_blocklist(), waf_check_cms_probe(), waf_check_malicious_payload, waf_check_header_injection() etc for the actual checks.

Thanks again for the feedback!
Urs

Hello @erational,

Thanks for the redesign of the logo in line with the SPIP guidelines!

I’ve approved your PR and updated this with version 1.0.4!

Kind regards,
Urs

The logo is no longer visible. Because of the file name change :
Ajouté
prive/themes/spip/images/wax-xx.svg
Supprimé
prive/themes/spip/images/waf-xx.svg

Yes, just saw. Let me rename.

I’ve pushed a new version with the fix. Thanks!

Hello,
Thanks for your work !
A quick question: does the WAF replace ecran_securite or does it work with it ?
Thanks.

Hello Pierr0t,
Not at all, this is not a replacement for ecran_securite.

ecran_securite will bring CVE mitigation with actual hotfixes for the specific issues.

This plugin will detect generic malicious requests trying to exploit 0-days or other vulnerabilities (fixed or not). Once a request is identified as a bad request it is blocked, and if more requests come in, the IP is blocked.

It is to prevent automated hacker attacks identifying SPIP and launching attacks.

For example, if the request contains /?exec(shudown), the WAF will catch it and block the request.

I’ll work more on the detection rules, they are very basic right now but they already catch many CMS scanners and exploit attempts.

In addition, the plugin pulls in public IP blocklists, blocking many of the known bad IPs. This also makes it harder for bad actors to attack the site.

Kind regards,
Urs

1 « J'aime »

Hello,

I’m mainly talking about not putting PHP in the HTML and using filters. Do you want pull requests along those lines?

Hello Pierre,

Yes, that would be great. Happy to learn always. I was also attempting to output everything in boucles, but the expandable tree seemed a bit hard to do so I fell back to PHP.

Kind regards,
Urs

Hello Urs,

I’ve done the necessary pull requests; the code seems cleaner and more maintainable.

Great thank you, I’ve approved the PR’s, will make a new release after testing.

Hi Urs, thanks for the fantastic work! How much overhead does your WAF add to Spip? Is it realistic to consider using it on a shared hosting plan?

Hi maathieu,

I am expecting it is, but let me verify this.

I think it will add just a few milliseconds (less than 10), and in this case it’s worth it. Let me get back to you on this!

On shared hosting it is advised to not tick the « Spamhaus » list as their terms forbid use on shared hostings.

1 « J'aime »

Hello Urs,

Can we add https://greensnow.co/ to the plugins?

I think grensnow is already included in Firehol Level 2.

Good

Hi maathieu,

I’ve added a profiling function and launched this on one of sites visited by a lot of bots with about 10 requests per second.

Here are the results:

Analyzing 86268 requests:
p50 = 8.49 ms (half of all WAF checks complete in under 9 ms)
p95 = 18.64 ms (95% of all checks complete in under 19 ms)
p99 = 37.13 ms (99% of all checks complete in under 38 ms)

That’s not bad for a start!

8ms is acceptable and was within my 10ms target.
But 37ms is not great, so we will optimize.

If we can reach p95 at 10ms and p99 at 15ms I will be satisfied. Let’s see how far we can push this!

The methodology and instructions are here: SPIP Web Application Firewall (WAF) - SPIP-Contrib

I’ve analyzed only the PASS requests, meaning these requests went through all checks. The BLOCKed requests would be much faster as less of the checks run.

@pierretux has already pointed out that the way we check blocked IPs is not efficient. I can confirm this: The IP check accounts for about half the time spent in the WAF check (waf_ti_ip_in_cidr) and this will be optimized.

The following is a cachegrind of how much time is spent on what in the request, and the box chart is time spent within the waf() function:

He also mentioned we can cache the regex that inspect the request values. That should bring another few milliseconds of performance.

I think with performance optimization we can cut times in half. That would be acceptable.

My tests ran on a 6-core 10gb ram virtualized server, I’ll also install the plugin on a shared hosting to see how it performs there, hehe…

It’s good this performance profiling system is in place now, as when we add more rules and more IPs we have to consider what it does to the performance. I think the goal should be to waste no more than 10ms per request!

Kind regards,
Urs

1 « J'aime »