This is a translation of the original article published in French on Aukfood’s Blog.
In this article, we will explore the topic of Web Application Firewalls (WAFs). But why implement a WAF? To put it simply, you can use a WAF to protect your LAMP stack (Linux, Apache, MySQL, and PHP) against the numerous threats that target online applications daily.
A website exposed on the Internet is susceptible to multiple types of attacks, including SQL injections, cross-site scripting (XSS) attempts, brute force attacks, exploitation of known vulnerabilities in PHP or Apache, and automated bots that constantly scan vulnerable servers. A WAF acts as an intelligent barrier between your visitors and your application, capable of identifying and blocking these attacks before they reach your server.
With a WAF, you become a true Gandalf of the web, raising your shield against attacks of all kinds. Like the famous wizard who declares “You shall not pass!”, your web application firewall prevents malicious intruders from crossing the threshold of your infrastructure.
It’s a bit like in a heavy metal music video: the WAF is your steel sink, resistant to all digital splashes and torrents of attacks that crash against it. Malicious robots, SQL injections, or bizarre requests? They bounce off it like poorly washed dishes. In the end, your LAMP stack remains clean and operational, while your WAF plays the role of the unsung hero, absorbing chaos with “metal” power worthy of its name.
Open Source WAF?
Opting for an open source WAF means choosing a solution that is economical, flexible, and transparent. No expensive licensing, completely customizable configuration to your needs, and best of all, open code that anyone can audit to guarantee its reliability. Finally, the support of an active community enables you to benefit from ongoing improvements and established best practices. In short: more control, more confidence, fewer constraints.
Chosen Solutions
To effectively secure your LAMP stack, we will combine two complementary approaches: collaborative detection and prevention with CrowdSec and classic application protection with ModSecurity.
CrowdSec AppSec
CrowdSec is an open source collaborative security solution. Its objective is to analyze traffic in real-time, identify malicious behaviors, and make blocking decisions. With its AppSec component, CrowdSec provides a range of WAF security rules, ready to use, specifically tailored for web applications.
PHP Bouncer (Remediation Component)
To apply these AppSec rules, we will configure a Remediation Component (also known as a bouncer), which is a module responsible for remedying attacks by blocking malicious requests. We will use the PHP bouncer, which integrates perfectly with Apache/PHP. There is also a bouncer for Apache (or NGINX), but these versions are still recent and in development, making the PHP bouncer more stable and reliable for our stack. Additionally, the PHP bouncer will give us the ability to implement a CAPTCHA.
ModSecurity
ModSecurity is a historic and widely recognized WAF in the open source world. It functions as an Apache module, capable of analyzing each HTTP request according to predefined rules, notably the famous OWASP Core Rules. Its strength lies in its maturity and flexibility: it can block a large number of common attacks and be finely tuned according to the specific needs of your application.
Prerequisites
Before deploying CrowdSec, its PHP bouncer, and ModSecurity, make sure you have a server ready to host your configuration. Here’s what it should contain:
- One of your preferred servers, with:
- A Debian distribution
- A LAMP stack is installed
- CrowdSec is already installed and operational (v0.6.11)
Installation and Configuration
We can now begin the installation of the different solutions listed previously.
AppSec
- Installing collections
cscli collections install crowdsecurity/appsec-default crowdsecurity/appsec-crs crowdsecurity/appsec-generic-rules crowdsecurity/appsec-virtual-patching
2. Setting up the acquis
mkdir -p /etc/crowdsec/acquis.d
cat > /etc/crowdsec/acquis.d/appsec.yaml << 'EOF'
listen_addr: 127.0.0.1:7422
appsec_configs:
- crowdsecurity/appsec-default
name: myAppSecComponent
source: appsec
labels:
type: appsec
EOF
3. Restart CrowdSec
systemctl restart crowdsec
4. Verify proper functioning
ss -tulnp | grep 7422
PHP Bouncer
Prerequisites
- Updated system
sudo apt update && sudo apt upgrade -y
2. Installing various dependencies
sudo apt install -y php-cli php-curl php-gd php-json php-mbstring php-xml php-zip git
php -v
3. Installing Composer
cd ~
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
php composer-setup.php --install-dir=/usr/local/bin --filename=composer
rm composer-setup.php
composer --version
Setting up the PHP bouncer
- Creating the directory
sudo mkdir -p /var/www/crowdsec-standalone-bouncer
sudo chown -R $(whoami):$(whoami) /var/www/crowdsec-standalone-bouncer
2. Setting up the bouncer via Composer
composer create-project crowdsec/standalone-bouncer /var/www/crowdsec-standalone-bouncer --keep-vcs
Note: We must keep the VCS data, as we will use it to update the bouncer when a new version is available.
3. Setting up permissions
sudo chown -R www-data /var/www/crowdsec-standalone-bouncer
sudo chmod g+w /var/www/crowdsec-standalone-bouncer
4. Setting up settings.php
cp scripts/settings.php.dist scripts/settings.php
5. Setting up the API_KEY
API_KEY=$(sudo cscli bouncers add standalone-bouncer -o raw) \
&& sed -i "s|'api_key' => 'YOUR_BOUNCER_API_KEY'|'api_key' => '$API_KEY'|" scripts/settings.php \
&& unset API_KEY
6. Set up the CAPTCHA
expiration
sed -i "s/'captcha_cache_duration' => Constants::CACHE_EXPIRATION_FOR_CAPTCHA,/'captcha_cache_duration' => 3600,/" scripts/settings.php
By default, when a user completes the CAPTCHA, for 24 hours, even if a scenario is triggered by their IP address, the bouncer will consider the CAPTCHA already solved. This prevents the user from having to redo it too often. In this example, we chose to reduce this duration to 1 hour.
7. Enable AppSec
sed -i "s/'use_appsec' => false/'use_appsec' => true/" scripts/settings.php
So that the bouncer can use the AppSec rules installed previously.
8. To set up in the .htaccess
of different sites
php_value auto_prepend_file "/var/www/crowdsec-standalone-bouncer/scripts/bounce.php"
You can also add this line to the .ini
file.
auto_prepend_file = /var/www/crowdsec-standalone-bouncer/scripts/bounce.php
The CrowdSec Part
- To add in the CrowdSec iptables bouncer
scenarios_not_containing: ["http", "modsecurity", "appsec"]
This step is important: if we want only the PHP bouncer to handle HTTP requests, we must tell CrowdSec/iptables
not to take into account HTTP scenarios from ModSecurity and AppSec.
2. Add the captcha part to the CrowdSec profile
name: crawler_captcha_remediation
filters:
- Alert.Remediation == true && Alert.GetScope() == "Ip" && Alert.GetScenario() in ["crowdsecurity/http-crawl-non_statics", "crowdsecurity/http-bad-user-agent"]
decisions:
- type: captcha
duration: 1h
on_success: break
3. Install the ModSecurity collection
cscli collection install crowdsecurity/modsecurity
ModSecurity
- Install ModSecurity
sudo apt update
sudo apt install libapache2-mod-security2
2. Enable the module
sudo a2enmod security2
sudo systemctl restart apache2
3. Apply the configuration
sudo cp /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf
4. Check this file /etc/apache2/mods-enabled/security2.conf
# Default Debian dir for modsecurity's persistent data
SecDataDir /var/cache/modsecurity
# Include all the *.conf files in /etc/modsecurity.
# Keeping your local configuration in that directory
# will allow for an easy upgrade of THIS file and
# make your life easier
IncludeOptional /etc/modsecurity/*.conf
# Include OWASP ModSecurity CRS rules if installed
IncludeOptional /usr/share/modsecurity-crs/*.load
5. Verify that the module has been loaded properly
apachectl -M | grep security2
Testing & Validation
Once CrowdSec, its PHP bouncer, and ModSecurity are installed, we must verify that our protection works correctly. The objective is to ensure that common attacks are properly detected and blocked.
Basic Verification
We access our site normally from a browser to confirm that legitimate traffic is not blocked. We can also check Apache, CrowdSec, and ModSecurity logs to verify that everything is working correctly.
Simple Attack Simulation
We can test some classic attacks to validate the proper functioning of the WAF:
- SQL Injection:
curl "https://ourpenguinsite.com/index.php?id=1' OR '1'='1"
# or: curl -G "https://ourpenguinsite.com/index.php" --data-urlencode "id=1' OR '1'='1"
We can see that we indeed trigger an alert from ModSecurity following an SQLi attack:
╭─────────┬──────────┬──────────────────┬───────────────────────────────────┬─────────┬─────────┬──────────────────────────────────┬────────┬────────────┬──────────╮
│ ID │ Source │ Scope:Value │ Reason │ Action │ Country │ AS │ Events │ expiration │ Alert ID │
├─────────┼──────────┼──────────────────┼───────────────────────────────────┼─────────┼─────────┼──────────────────────────────────┼────────┼────────────┼──────────┤
│ 6050427 │ crowdsec │ Ip:11.22.33.111 │ crowdsecurity/modsecurity-sqli │ ban │ FR │ 3215 MonAS │ 1 │ 3h59m53s │ 1606 │

- XSS Injection:
curl "https://ourpenguinsite.com/index.php?q="
Once again, we can see the scenario trigger
╭─────────┬──────────┬──────────────────┬───────────────────────────────────┬─────────┬─────────┬──────────────────────────────────┬────────┬────────────┬──────────╮
│ ID │ Source │ Scope:Value │ Reason │ Action │ Country │ AS │ Events │ expiration │ Alert ID │
├─────────┼──────────┼──────────────────┼───────────────────────────────────┼─────────┼─────────┼──────────────────────────────────┼────────┼────────────┼──────────┤
│ 6050430 │ crowdsec │ Ip:11.22.33.111 │ crowdsecurity/modsecurity-xss │ ban │ FR │ 3215 MonAS │ 1 │ 3h59m59s │ 1612 │
- vpatch-CVE-2017-9841:
curl "https://ourpenguinsite.com/util/php/eval-stdin.php"
We can now see that we triggered a specific rule present in AppSec, which corresponds to CVE-2017-9841, the PHPUnit RCE vulnerability
╭─────────┬──────────┬──────────────────┬───────────────────────────────────┬─────────┬─────────┬──────────────────────────────────┬────────┬────────────┬──────────╮
│ ID │ Source │ Scope:Value │ Reason │ Action │ Country │ AS │ Events │ expiration │ Alert ID │
├─────────┼──────────┼──────────────────┼───────────────────────────────────┼─────────┼─────────┼──────────────────────────────────┼────────┼────────────┼──────────┤
│ 6050431 │ crowdsec │ Ip:11.22.33.111 │ crowdsecurity/CVE-2017-9841 │ ban │ FR │ 3215 MonAS │ 1 │ 3h59m57s │ 1613 │
Captcha with the PHP bouncer
curl -A "Nikto" "https://ourpenguinsite.com/"
And now, we can see that if we trigger a rule that itself activates a CAPTCHA, we get the desired result. Useful when a legitimate person finds themselves blocked:
╭─────────┬──────────┬──────────────────┬───────────────────────────────────┬─────────┬─────────┬──────────────────────────────────┬────────┬────────────┬──────────╮
│ ID │ Source │ Scope:Value │ Reason │ Action │ Country │ AS │ Events │ expiration │ Alert ID │
├─────────┼──────────┼──────────────────┼───────────────────────────────────┼─────────┼─────────┼──────────────────────────────────┼────────┼────────────┼──────────┤
│ 6050433 │ crowdsec │ Ip:11.22.33.111 │ crowdsecurity/http-bad-user-agent │ captcha │ FR │ 3215 MonAS │ 2 │ 59m50s │ 1615 │

Optimization & Best Practices
Once our protections are in place, we must ensure that our LAMP stack remains both secure and functional. Here are the key points:
- Adjust rules according to our site
- CrowdSec and ModSecurity come with default rules, but some may block legitimate traffic.
- We must analyze logs and adjust scenarios to reduce false positives without compromising security.
- Update regularly
- Threats evolve constantly. We must maintain CrowdSec, its scenarios, its PHP bouncer, and ModSecurity up to date to stay protected.
- Monitoring and tracking
- We can regularly check logs (
/var/log/crowdsec.log, /var/log/apache2/modsec_audit.log
) to detect suspicious behaviors and verify the effectiveness of rules. - Tools like
cscli
for CrowdSec allow getting a quick summary of blocking decisions and detected activity.
- Test before production
- Any modification of rules or addition of new scenarios must be tested in a pre-production environment or in simulation mode, which is very easily configurable.
- Take advantage of the community
- CrowdSec and ModSecurity benefit from an active community. We can share our scenarios and benefit from updates and feedback to improve our configuration.
By applying these best practices, we ensure that our dual CrowdSec + ModSecurity protection remains effective, reliable, and adapted to our environment.
Conclusion
By combining CrowdSec, its PHP bouncer, and ModSecurity, we have implemented a robust double layer of defense for our LAMP stack. On one side, CrowdSec detects and blocks malicious behaviors collaboratively and adaptively; on the other, ModSecurity filters HTTP requests according to proven rules.
Thanks to this configuration, we become a bit like the Gandalf of the web: we prevent attacks from breaching our servers while allowing legitimate traffic to pass. With the PHP bouncer, we can even integrate additional measures like CAPTCHA to strengthen protection.
Finally, by following best practices - regular updates, rule adjustments, log monitoring, and testing - we guarantee that our WAF remains effective, reliable, and scalable against threats that never stop evolving.
In summary, a well-configured open-source WAF doesn't just protect our site: it offers us control, transparency, and peace of mind to manage our web infrastructure with confidence.
Troubleshooting
- PHP FPM
- Make sure to specify the bouncer directory in the opendir of php.conf.
- Check the PHP dependencies in
composer.lock