· 7 min read
Sécuriser votre application avec un WAF et les règles OWASP
Vous avez mis votre application en ligne et vous vous demandez comment la protéger des attaques ? Découvrez comment mettre en place un WAF avec FrankenPHP, Coraza et les règles OWASP.
Il etait une fois internet …
Internet est un endroit merveilleux, mais il peut aussi être un endroit dangereux. Les attaques en ligne sont de plus en plus fréquentes et de plus en plus sophistiquées. Il est donc important de protéger votre application contre ces attaques.
Il existe 1000 et une façon de sécuriser votre application, mais l’une des plus efficaces est d’utiliser un WAF (Web Application Firewall).
Qu’est-ce qu’un WAF ?
Un WAF est un “pare-feu” qui protège votre application web contre les attaques en ligne. Il analyse le trafic entrant et sortant de votre application et bloque les attaques avant qu’elles n’atteignent votre serveur.
Les WAF sont souvent utilisés pour protéger les applications web contre les attaques par injection SQL, les attaques par force brute, les attaques par déni de service, etc.
Les règles OWASP
L’OWASP (Open Web Application Security Project) est une organisation à but non lucratif qui fournit des outils et des ressources pour aider les développeurs à sécuriser leurs applications web.
L’OWASP a publié une liste des 10 principales vulnérabilités de sécurité des applications web. Ces vulnérabilités sont souvent exploitées par les pirates informatiques pour attaquer les applications web.
Coraza WAF ?
Coraza WAF est un WAF open source basé sur les règles OWASP. Il est facile à installer et à configurer et offre une protection efficace contre les attaques en ligne.
Nous allons utiliser la version pour Caddy, en effet, FrankenPHP est basé sur Caddy.
Notre labo
Pour ce tutoriel, nous allons donc utiliser FrankenPHP, un serveur PHP basé sur Caddy, et donc compatible avec les modules Caddy. Pratique !
- Créons un dossier
FrankenPhpSec
et plaçons-nous dedans.
mkdir FrankenPhpSec
cd FrankenPhpSec
- Nous allons créer un simple fichier
index.php
pour notre application.
<?php
echo "Hello, World!";
- Pour tester simplement que notre application fonctionne avec FrankenPHP, nous allons lancer un conteneur Docker.
docker run -v $PWD:/app/public \
-p 80:80 \
-e SERVER_NAME=:80 \
dunglas/frankenphp
Le
-e SERVER_NAME=:80
permet de dire à Caddy de servir notre application sans HTTPS et gestion de certificat.
- Testons notre application (j’utilise le client HTTPie).
http 127.0.0.1
OK, notre application fonctionne, passons à la suite !
Créons notre Dockerfile de base
Pour aller plus loin, nous allons créer un Dockerfile
à la racine de notre dossier FrankenPhpSec
.
FROM dunglas/frankenphp
- Construisons notre image Docker.
docker build -t frankenphpsec .
- Et lançons notre conteneur.
docker run -v $(PWD):/app/public -p 80:80 -e SERVER_NAME=:80 frankenphpsec
- Testons notre application.
http 127.0.0.1
Ok, notre application fonctionne toujours, passons à la suite !
Un Dockerfile pour personnaliser notre FrankenPHP
Nous allons modifier notre Dockerfile
afin de personnaliser notre FrankenPHP. Pour cela, nous allons utiliser le Dockerfile de FrankenPHP proposé sur le site officiel pour ajouter des modules Caddy.
FROM dunglas/frankenphp:builder AS builder
# Copier xcaddy dans l'image du constructeur
COPY --from=caddy:builder /usr/bin/xcaddy /usr/bin/xcaddy
# CGO doit être activé pour construire FrankenPHP
RUN CGO_ENABLED=1 \
XCADDY_SETCAP=1 \
XCADDY_GO_BUILD_FLAGS="-ldflags='-w -s' -tags=nobadger,nomysql,nopgx" \
CGO_CFLAGS=$(php-config --includes) \
CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" \
xcaddy build \
--output /usr/local/bin/frankenphp \
--with github.com/dunglas/frankenphp=./ \
--with github.com/dunglas/frankenphp/caddy=./caddy/ \
--with github.com/dunglas/caddy-cbrotli \
# Mercure et Vulcain sont inclus dans la construction officielle, mais n'hésitez pas à les retirer
--with github.com/dunglas/mercure/caddy \
--with github.com/dunglas/vulcain/caddy
# Ajoutez des modules Caddy supplémentaires ici
FROM dunglas/frankenphp AS runner
# Remplacer le binaire officiel par celui contenant vos modules personnalisés
COPY --from=builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp
- Construisons notre image Docker.
docker build -t frankenphpsec .
- Et lançons notre conteneur.
docker run -v $(PWD):/app/public -p 80:80 -e SERVER_NAME=:80 frankenphpsec
- Testons notre application.
http 127.0.0.1
Ok, notre application fonctionne toujours, logique, nous n’avons rien changé. Passons à la suite !
Avant d’aller plus loin, automatisons tout ça avec un Makefile
Pour nous simplifier la vie, nous allons créer un Makefile
à la racine de notre dossier FrankenPhpSec
.
Il va nous permettre de construire notre image Docker, de lancer notre conteneur et de tester notre application (avec quelques tests basiques de l’OWASP).
# Build the Docker image
build:
docker build -t frankenphpsec .
# Run the Docker container
run:
docker run -v $(PWD):/app/public -p 80:80 -e SERVER_NAME=:80 frankenphpsec
# Test OWASP with HTTPie
test:
# Test for SQL Injection
@echo "\033[1;34mTesting for SQL Injection\033[0m"
@echo "\033[1;34m--------------------------------\033[0m"
http GET "http://127.0.0.1/vulnerable_endpoint?param=' OR 1=1 --"
@sleep 1
# Cross-Site Scripting
@echo "\033[1;34mTesting for Cross-Site Scripting\033[0m"
@echo "\033[1;34m--------------------------------\033[0m"
http GET "http://127.0.0.1/vulnerable_endpoint?param=<script>alert('XSS')</script>"
@sleep 1
# Command Injection
@echo "\033[1;34mTesting for Command Injection\033[0m"
@echo "\033[1;34m--------------------------------\033[0m"
http POST "http://127.0.0.1/vulnerable_endpoint" param="; ls -la"
@sleep 1
# Path Traversal
@echo "\033[1;34mTesting for Path Traversal\033[0m"
@echo "\033[1;34m--------------------------------\033[0m"
http GET "http://127.0.0.1/vulnerable_endpoint?file=../../etc/passwd"
@sleep 1
# Test Method Delete
@echo "\033[1;34mTesting for Method Delete\033[0m"
@echo "\033[1;34m--------------------------------\033[0m"
http DELETE "http://127.0.0.1/vulnerable_endpoint"
# Final Message
@echo "\033[1;32mAll tests passed!\033[0m"
- Construisons notre image Docker.
make build
- Et lançons notre conteneur.
make run
- Testons notre application.
make test
Parfait, notre application fonctionne toujours et nous avons automatisé les tests OWASP. Passons à la suite !
Ajoutons Coraza WAF à notre FrankenPHP
Pour ajouter Coraza WAF à notre FrankenPHP, nous allons modifier notre Dockerfile
pour inclure le module Caddy de Coraza WAF et copier le fichier de configuration de Caddy.
FROM dunglas/frankenphp:builder AS builder
# Copy xcaddy in the builder image
COPY --from=caddy:builder /usr/bin/xcaddy /usr/bin/xcaddy
# CGO must be enabled to build FrankenPHP
RUN CGO_ENABLED=1 \
XCADDY_SETCAP=1 \
XCADDY_GO_BUILD_FLAGS="-ldflags='-w -s' -tags=nobadger,nomysql,nopgx" \
CGO_CFLAGS=$(php-config --includes) \
CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" \
xcaddy build \
--output /usr/local/bin/frankenphp \
--with github.com/dunglas/frankenphp=./ \
--with github.com/dunglas/frankenphp/caddy=./caddy/ \
--with github.com/dunglas/caddy-cbrotli \
# Mercure and Vulcain are included in the official build, but feel free to remove them
--with github.com/dunglas/mercure/caddy \
--with github.com/dunglas/vulcain/caddy \
# Add extra Caddy modules here
--with github.com/corazawaf/coraza-caddy/v2
FROM dunglas/frankenphp AS runner
# Replace the official binary by the one contained your custom modules
COPY --from=builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp
# Copy Caddyfile to the correct location
COPY Caddyfile /etc/caddy/Caddyfile
- Créons un fichier
Caddyfile
à la racine de notre dossierFrankenPhpSec
. Pour cela on se base sur le Caddyfile de FrankenPHP auquel on ajoute les directives de Coraza WAF.
{
{$CADDY_GLOBAL_OPTIONS}
order coraza_waf first
frankenphp {
{$FRANKENPHP_CONFIG}
}
}
{$CADDY_EXTRA_CONFIG}
{$SERVER_NAME:localhost} {
root * public/
encode zstd br gzip
coraza_waf {
load_owasp_crs
directives `
Include @coraza.conf-recommended
Include @crs-setup.conf.example
Include @owasp_crs/*.conf
SecRuleEngine On
`
}
handle_errors 403 {
header X-Blocked "true"
respond "Your request was blocked by the WAF 🏴☠️"
}
{$CADDY_SERVER_EXTRA_DIRECTIVES}
php_server
}
- Le
order coraza_waf first
permet de dire à Caddy de traiter les requêtes avec Coraza WAF en premier.- Le
load_owasp_crs
permet de charger les règles OWASP.- Le
SecRuleEngine On
permet d’activer le moteur de règles de sécurité.- Le
handle_errors 403
permet de renvoyer un message d’erreur personnalisé en cas de blocage par le WAF.- Le
php_server
permet de dire à Caddy de servir notre application PHP.
- Construisons notre image Docker.
make build
- Et lançons notre conteneur.
make run
- Testons notre application.
make test
Testing for SQL Injection
--------------------------------
http GET "http://127.0.0.1/vulnerable_endpoint?param=' OR 1=1 --"
HTTP/1.1 403 Forbidden
Content-Length: 49
Content-Type: text/plain; charset=utf-8
Date: Fri, 03 Jan 2025 23:36:12 GMT
Server: Caddy
X-Blocked: true
Your request was blocked by the WAF 🏴☠️
Comme nous pouvons le voir, nos requêtes simulant des attaques types OWASP sont bloquées par Coraza WAF, on le voit avec le code HTTP 403 et le message d’erreur personnalisé Your request was blocked by the WAF 🏴☠️
.
Bravo, vous avez sécurisé votre application avec un WAF et les règles OWASP, en utilisant FrankenPHP et Coraza WAF ! C’etait pas si compliqué, n’est-ce pas ?
Conclusions
Dans ce tutoriel, nous avons vu comment sécuriser une application avec un WAF et les règles OWASP. Nous avons utilisé FrankenPHP, un serveur PHP basé sur Caddy, et Coraza WAF, un WAF open source basé sur les règles OWASP.
Ce n’est qu’une possibilité parmi d’autres, ce n’est qu’une bribe de ce que vous pouvez faire pour sécuriser votre application. N’hésitez pas à explorer d’autres solutions et à les adapter à vos besoins.
Loading comments...