Kontakt

Carsten Rieger IT Services
Am Danglfeld 8 | 83132 Pittenhart
Telefon: 08624.9009794
E-Mail: info@c-rieger.de

Nextcloud mit ModSecurity v. 3 Web Application Firewall (WAF)

Wir setzen dieser Anleitung voraus, dass der Webserver nginx bereits, wie in dieser Anleitung beschrieben wird, installiert wurde. Um den Webserver mittels einer Web Application Firewall (WAF) zusätzlich abzusichern und zu härten bereiten wir nginx durch die Installation weiterer Module (ModSecurity 3.0.2 und OWASP CRS 3.3.2) wie folgt vor:

sudo -s
apt install -y apt-utils autoconf automake build-essential git libcurl4-openssl-dev libgeoip-dev liblmdb-dev libpcre++-dev libtool libxml2-dev libyajl-dev pkgconf wget zlib1g-dev

Um die WAF nutzen zu können laden und kompilieren wir die relevante Software:

cd /usr/local/src
git clone --depth 1 -b v3/master --single-branch https://github.com/SpiderLabs/ModSecurity

Wechseln Sie in das neue Verzeichnis und kompilieren Sie die WAF

cd ModSecurity
git submodule init
git submodule update
./build.sh

Das Kompilieren kann mehrere Minuten dauern und Meldungen wie bspw.

fatal: No names found, cannot describe anything.

hervorrufen. Diese Meldungen „fatal: No names found, cannot describe anything.“ können aber ignoriert werden. Nach dem Kompilierenfolgt das Konfigurieren und Installieren:

./configure
make
make install

Des Weiteren benötigen wir noch den NGINX-Konnektor für ModSecurity, den wir ebenfalls kompilieren und dann als dynamisches Modul in den Webserver einbinden.

git clone --depth 1 https://github.com/SpiderLabs/ModSecurity-nginx.git

Bestimmen Sie die aktuell verwendete nginx Version:

nginx -V

und laden diese Version (nginx-Sources) herunter (im Beispiel 1.21.6)

wget http://nginx.org/download/nginx-1.21.6.tar.gz
tar zxvf nginx-1.21.6.tar.gz

Wechseln Sie dann in das zuvor entpackte Source-Verzeichnis, konfigurieren und kopieren dann das dynamische Modul:

cd nginx-1.21.6/
./configure --with-compat --add-dynamic-module=../ModSecurity-nginx
make modules
cp objs/ngx_http_modsecurity_module.so /etc/nginx/modules

Binden Sie nun die WAF in Ihren Webserver ein. Fügen Sie dazu die roten Zeilen hinzu:

nano /etc/nginx/nginx.conf
load_module modules/ngx_http_modsecurity_module.so;
user www-data;
worker_processes auto;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
multi_accept on; use epoll;
}
http {
server_names_hash_bucket_size 64;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log warn;
# set_real_ip_from 127.0.0.1;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
send_timeout 3600;
tcp_nopush on;
tcp_nodelay on;
open_file_cache max=500 inactive=10m;
open_file_cache_errors on;
keepalive_timeout 65;
reset_timedout_connection on;
server_tokens off;
resolver 127.0.0.53 valid=30s;
resolver_timeout 5s;
include /etc/nginx/conf.d/*.conf;
}

Um die Funktionsfähigkeit der WAF zu überprüfen aktivieren wir diese und setzen zur Verifizierung einen Testaufruf ab:

mkdir /etc/nginx/modsec
wget -P /etc/nginx/modsec/ https://raw.githubusercontent.com/SpiderLabs/ModSecurity/v3/master/modsecurity.conf-recommended
mv /etc/nginx/modsec/modsecurity.conf-recommended /etc/nginx/modsec/modsecurity.conf

Passen Sie die Konfiguration durch das Ausführen beider Kommandos an:

sed -i 's/SecRuleEngine DetectionOnly/SecRuleEngine On/' /etc/nginx/modsec/modsecurity.conf
sed -i 's/SecStatusEngine On/SecStatusEngine Off/' /etc/nginx/modsec/modsecurity.conf

Bearbeiten Sie die main.conf wie folgt

nano /etc/nginx/modsec/main.conf
# Edit to set SecRuleEngine On
Include "/etc/nginx/modsec/modsecurity.conf"
# Basic test rule
SecRule ARGS:testparam "@contains test" "id:1234,deny,status:403"

und erweitern die nginx vHostdateien, um die WAF für Nextcloud zu aktivieren:

nano /etc/nginx/conf.d/http.conf
upstream php-handler {
server unix:/run/php/php8.0-fpm.sock;
}
server {
listen 80 default_server;
listen [::]:80 default_server;
modsecurity on;
modsecurity_rules_file /etc/nginx/modsec/main.conf;
server_name ihre.domain.de;
root /var/www;
location ^~ /.well-known/acme-challenge {
default_type text/plain;
root /var/www/letsencrypt;
}
location / {
return 301 https://$host$request_uri;
}
}
nano /etc/nginx/conf.d/nextcloud.conf
server {
listen 443      ssl http2;
listen [::]:443 ssl http2;
modsecurity on;
modsecurity_rules_file /etc/nginx/modsec/main.conf;
server_name ihre.domain.de;
#ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
#ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;
#ssl_trusted_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
ssl_certificate /etc/letsencrypt/rsa-certs/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/rsa-certs/privkey.pem;
ssl_certificate /etc/letsencrypt/ecc-certs/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/ecc-certs/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/ecc-certs/chain.pem;
ssl_dhparam /etc/ssl/certs/dhparam.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
ssl_protocols TLSv1.3 TLSv1.2;
ssl_ciphers 'TLS-CHACHA20-POLY1305-SHA256:TLS-AES-256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384';
ssl_ecdh_curve X448:secp521r1:secp384r1;
ssl_prefer_server_ciphers on;
ssl_stapling on;
ssl_stapling_verify on;
client_max_body_size 10G;
client_body_timeout 3600s;
fastcgi_buffers 64 4K;
gzip on;
gzip_vary on;
gzip_comp_level 4;
gzip_min_length 256;
gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/wasm application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy;
add_header Strict-Transport-Security            "max-age=15768000; includeSubDomains; preload;" always;
add_header Permissions-Policy                   "interest-cohort=()";
add_header Referrer-Policy                      "no-referrer"   always;
add_header X-Content-Type-Options               "nosniff"       always;
add_header X-Download-Options                   "noopen"        always;
add_header X-Frame-Options                      "SAMEORIGIN"    always;
add_header X-Permitted-Cross-Domain-Policies    "none"          always;
add_header X-Robots-Tag                         "none"          always;
add_header X-XSS-Protection                     "1; mode=block" always;
fastcgi_hide_header X-Powered-By;
root /var/www/nextcloud;
index index.php index.html /index.php$request_uri;
location = / {
if ( $http_user_agent ~ ^DavClnt ) {
return 302 /remote.php/webdav/$is_args$args;
}
}
location = /robots.txt {
allow all;
log_not_found off;
access_log off;
}
location ^~ /apps/rainloop/app/data {
deny all;
}
location ^~ /.well-known {
location = /.well-known/carddav { return 301 /remote.php/dav/; }
location = /.well-known/caldav  { return 301 /remote.php/dav/; }
location /.well-known/acme-challenge { try_files $uri $uri/ =404; }
location /.well-known/pki-validation { try_files $uri $uri/ =404; }
return 301 /index.php$request_uri;
}
location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/)  { return 404; }
location ~ ^/(?:\.|autotest|occ|issue|indie|db_|console)                { return 404; }
location ~ \.php(?:$|/) {
rewrite ^/(?!index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|oc[ms]-provider\/.+|.+\/richdocumentscode\/proxy) /index.php$request_uri;
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
set $path_info $fastcgi_path_info;
try_files $fastcgi_script_name =404;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $path_info;
fastcgi_param HTTPS on;
fastcgi_param modHeadersAvailable true;
fastcgi_param front_controller_active true;
fastcgi_pass php-handler;
fastcgi_intercept_errors on;
fastcgi_request_buffering off;
fastcgi_read_timeout 3600;
fastcgi_send_timeout 3600;
fastcgi_connect_timeout 3600;
}
location ~ \.(?:css|js|svg|gif|png|jpg|ico|wasm|tflite)$ {
try_files $uri /index.php$request_uri;
expires 6M;
access_log off;
location ~ \.wasm$ {
default_type application/wasm;
}
}
location ~ \.woff2?$ {
try_files $uri /index.php$request_uri;
expires 7d;
access_log off;
}
location /remote {
return 301 /remote.php$request_uri;
}
location / {
try_files $uri $uri/ /index.php$request_uri;
}
}

Kopieren Sie die folgende unicode-mapping Datei

cp /usr/local/src/ModSecurity/unicode.mapping /etc/nginx/modsec/

und starten dann den Webserver neu:

systemctl restart nginx

Rufen Sie auf der Shell den nachfolgenden Befehl auf und prüfen Sie den html-Status Code 403 – die WAF funktioniert!

curl http://localhost?testparam=test

Im ModSecurity Logfile finden Sie die entsprechenden Nachrichten:

tail -f /var/log/nginx/error.log | grep "id\ \"1234\""

Abschließend laden wir das aktuelle Ruleset (OWASP CRS), richten die notwendigen Rules ein und starten dann den Webserver letztmalig neu:

cd /usr/local/src
wget https://github.com/SpiderLabs/owasp-modsecurity-crs/archive/v3.2.0.tar.gz 
tar -xzvf v3.2.0.tar.gz
cd owasp-modsecurity-crs-3.2.0
cp crs-setup.conf.example crs-setup.conf

Wir bedienen uns aus dem Regelsatz der Nextcloud-Rules und bearbeiten dazu die Datei /etc/nginx/modsec/main.conf erneut.

nano /etc/nginx/modsec/main.conf 
# Edit to set SecRuleEngine On
Include "/etc/nginx/modsec/modsecurity.conf"
# Basic test rule
#SecRule ARGS:testparam "@contains test" "id:1234,deny,status:403"
# OWASP CRS v3 rules
Include /usr/local/src/owasp-modsecurity-crs-3.2.0/crs-setup.conf
Include /usr/local/src/owasp-modsecurity-crs-3.2.0/rules/REQUEST-903.9003-NEXTCLOUD-EXCLUSION-RULES.conf
# Include /usr/local/src/owasp-modsecurity-crs-3.2.0/rules/*.conf

Die WAF muss noch an den Bedarf Ihrer Nextcloud angepasst werden:

nano /etc/nginx/modsec/modsecurity.conf
# Maximum request body size we will accept for buffering. If you support
# file uploads then the value given on the first line has to be as large
# as the largest file you are willing to accept. The second value refers
# to the size of data, with files excluded. You want to keep that value as
# low as practical.
#
# SecRequestBodyLimit 13107200
SecRequestBodyLimit 10000000000
# SecRequestBodyNoFilesLimit 131072
SecRequestBodyNoFilesLimit 10000000000

Nach dem angekündigt letzten Webserver Neustart sollten wir uns das Logfile proaktiv ansehen:

systemctl restart nginx

Verhält sich die Nextcloud „merkwürdig“ so lassen sich möglicherweise Rückschlüsse über das ModSecurity-Logfile bilden:

Warnungen (Warnings) könnten sich wie folgt darstellen:

cat /var/log/modsec_audit.log | egrep -i "warning\." | egrep -i "id \"[0-9]{6}\"" -o | sort | uniq -c | sort -nr
7 id "980130"
7 id "930110"
7 id "930100"

Verbote (Denied) bspw. wie folgt:

cat /var/log/modsec_audit.log | egrep -i "access denied" | egrep -i "id \"[0-9]{6}\"" -o | sort | uniq -c | sort -nr
7 id "949110"

Solche Regeln lassen sich bei Beadarf deaktivieren, jedoch sollte man sehr restriktiv mit der Deaktivierung von solchen Rules vorgehen!

Die Härtung Ihres Webservers und somit Ihrer Nextcloud wurde erfolgreich abgeschlossen und so wünsche ich Ihnen viel Spaß mit Ihren Daten in Ihrer privaten Cloud. Über Ihre Unterstützung (diese wird ordnungsgemäß versteuert!) würden sich meine Frau, meine Zwillinge und ich sehr freuen!