Archives pour la catégorie Sécurité

Maintenir Nginx à jour sur un vieux tromblon

Nginx est le serveur Web le plus utilisé, juste devant Apache et ces deux programmes «motorisent» à eux seuls 2/3 des sites Web.

Apache, tout comme Nginx,  peut être utilisé comme «reverse-proxy». Mais Nginx est bien plus efficace, performant et facile à configurer dans ce cadre.

Une machine virtuelle ou un conteneur Nginx peut donc être utilisée comme frontal de nombreux sites Web non routés sur Internet. L’intérêt est multiple: cela permet d’avoir de nombreux sites (pas forcément sur le même domaine) sur une seule IPv4/IPv6, les sites ne sont pas directement accessibles et le proxy peut filtrer les requêtes, il peut être utilisé comme mandataire TLS. C’est en quelque sorte une  «gare de triage» pour des requêtes HTTP, HTTPS (merci SAN et SNI) mais aussi  TCP et UDP !

Un frontal qui n’exécute rien d’autre qu’un serveur Web qui assure la fonction de proxy ne craint pas grand chose (il n’interprète pas de code PHP ou autres). Les possibilités d’attaque sont très faibles et il est acceptable d’avoir des uptimes de plusieurs années sur des OS obsolètes … du moment que le principal intéressé (Nginx) soit à jour (ainsi que tous les composants dont il dépend).

Le script suivant télécharge, compile et installe Nginx et ses dépendances en statique:

#!/bin/bash

# Compilation et installation de Nginx + OpenSSL/PCRE/ZLIB statiques
# pour une utilisation en reverse-proxy HTTP/HTTPS/TCP/UDP

mkdir -p nginx-static
cd nginx-static

##################################################

NGINX_VERSION="1.22.1"
FAKE_NGINX_NAME="Microsoft IIS"
SSL_VERSION="1.1.1t"
PCRE_VERSION="10.37"
ZLIB_VERSION="1.2.13"

OPTS="
--with-threads
--with-http_ssl_module
--with-http_v2_module
--with-http_stub_status_module
--with-http_sub_module
--with-http_auth_request_module
--with-stream
--with-stream_ssl_module
--with-stream_ssl_preread_module
--with-openssl=../openssl-${SSL_VERSION}
--with-pcre=../pcre2-${PCRE_VERSION}
--with-zlib=../zlib-${ZLIB_VERSION}
--with-http_gzip_static_module
--add-module=../headers-more-nginx-module
"

##################################################

apt-get -y install build-essential git

##################################################

[ -d nginx ] || mkdir nginx
cd nginx

##################################################

[ -f nginx-${NGINX_VERSION}.tar.gz ] || wget "http://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz"
tar -xzf nginx-${NGINX_VERSION}.tar.gz

if [ -d headers-more-nginx-module ] ; then
  cd headers-more-nginx-module
  git pull https://github.com/openresty/headers-more-nginx-module.git
  cd ..
else
  git clone https://github.com/openresty/headers-more-nginx-module.git
fi

[ -f openssl-${SSL_VERSION}.tar.gz ] || wget "http://www.openssl.org/source/openssl-${SSL_VERSION}.tar.gz"
[ -d openssl-${SSL_VERSION} ] && rm -rf openssl-${SSL_VERSION}
tar -xzf openssl-${SSL_VERSION}.tar.gz

[ -f pcre2-${PCRE_VERSION}.tar.gz ] || wget "https://github.com/PCRE2Project/pcre2/releases/download/pcre2-${PCRE_VERSION}/pcre2-${PCRE_VERSION}.tar.gz"
[ -d pcre2-${PCRE_VERSION} ] && rm -rf pcre2-${PCRE_VERSION}
tar -xzf pcre2-${PCRE_VERSION}.tar.gz

[ -f zlib-${ZLIB_VERSION}.tar.gz ] || wget "http://zlib.net/zlib-${ZLIB_VERSION}.tar.gz"
[ -d zlib-${ZLIB_VERSION} ] && rm -rf zlib-${ZLIB_VERSION}
tar -xzf zlib-${ZLIB_VERSION}.tar.gz

##################################################

cd nginx-${NGINX_VERSION}

sed -i "s/<center>nginx<\/center>/<center>${FAKE_NGINX_NAME}<\/center>/g" src/http/ngx_http_special_response.c

make clean

./configure --prefix=/usr/local/nginx-static ${OPTS}
make -j $(( $(grep -c ^processor /proc/cpuinfo) + 1 ))

[ $? -eq 0 ] || { echo "erreur compilation" ; exit 1 ; }

make install

##################################################

service nginx stop
PIDS=$(ps awx |grep " nginx: " |grep -v grep |awk '{print $1}')
[ "${PIDS}" = "" ] || kill ${PIDS}

grep -q "^DAEMON=" /etc/default/nginx || cat<<EOT>>/etc/default/nginx
DAEMON="/usr/local/nginx-static/sbin/nginx"
DAEMON_OPTS="-c /etc/nginx/nginx.conf"
EOT

##################################################

service nginx start

cd ..

 

filtre fail2ban pour DokuWiki

Comme vous le savez, dokuwiki est le wiki par excellence. Il permet de créer des wiki pour les geeks que nous sommes. Il est léger, réactif, et il est de bon goût pour tout geek d’en posséder un.

Le revers de la médaille étant que sa notoriété fait qu’il devient la cible d’attaques en tout genre.

On trouve dans le fichier /var/log/apache2/access.log de nombreuses lignes telles que

149.62.41.158 – – [14/Jan/2023:18:52:09 +0100] “GET /titiwiki/doku.php?do=register HTTP/1.1” 200 8668 “https://escola.fr/titiwiki/doku.php?id=%D1%80%D0%B0%D0%B7%D0%B1%D0%B8%D0%BB%D1%81%D1%8F-%D1%80%D1%82%D1%83%D1%82%D0%BD%D1%8B%D0%B9-%D0%B3%D1%80%D0%B0%D0%B4%D1%83%D1%81%D0%BD%D0%B8%D0%BA_-%D1%80%D0%B5%D1%88%D0%B8%D0%BB%D0%B0-%D1%81%D0%BE&do=login&sectok=” “Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36 Vivaldi/5.3.2679.68”

C’est un peu obscure, je n’ai d’ailleurs pas tenté de creuser le pourquoi de cette ligne de log, mais le résultat de ce type d’attaque a été que mon serveur a servi de passerelle de spam pendant quelques jours.

Comme j’ai déjà un fail2ban d’installé sur ma machine, pour bannir les tentatives d’accès à SSH et à l’administration de WordPress, j’ai décidé de créer un filtre supplémentaire pour bannir les machines qui ont tenté de se logguer sur mon DokuWiki de cette manière.

Tout d’abord on crée le jail

#nano /etc/fail2ban/jail.d/dokuwiki.conf
[dokuwiki]
# To use more aggressive modes set filter parameter "mode" in jail.local:
# normal (default), ddos, extra or aggressive (combines all).
# See "tests/files/logs/sshd" or "filter.d/sshd.conf" for usage example and details.
enabled = true
mode = aggressive
port = http,https
filter = dokuwiki
action = iptables-multiport[name=DOKUWIKI, port="http,https", protocol=tcp]
logpath = /var/log/apache2/access.log
maxretry = 1
bantime = 4w

il y a ici un réglage simplifié qui fonctionne, on peut le tweaker bien plus, mais je vous laisse le loisir de consulter la documentation.

Pour résumer : on vérifie le fichier de log d’Apache, on bannit à 1 seule tentative, pendant 4 semaines, en utilisant le filtre dokuwiki qu’il faut créer

#nano /etc/fail2ban/filters.d/dokuwiki.conf
[Definition]
failregex = ^<HOST> .*(GET|POST) .*/titiwiki/doku\.php.*(%%.*){10,}.*
ignoreregex =

La seule chose étant une expression régulière, alias  Regex, qui stipule qu’on détecte un GET ou un POST suivi d’un /titiwiki/doku.php puis un minimum de 10 fois le caractère % entrecoupés d’autres caractères.

une fois ces fichiers créés, on peut faire le test

#fail2ban-regex /var/log/apache2/access.log /etc/fail2ban/filter.d/dokuwiki.conf 
Results
=======
Failregex: 470 total
|- #) [# of hits] regular expression
| 1) [470] ^<HOST> .*(GET|POST) .*/titiwiki/doku\.php.*(%.*){10,}.*

On voit bien qu’il à a eu 470 correspondances entre la regex et le fichier access d’Apache.

#fail2ban reload dokuwiki
va charger ce filtre dans dokuwiki
#fail2ban status dokuwiki
au bout de quelques heures va donner

Status for the jail: dokuwiki
|- Filter
| |- Currently failed: 0
| |- Total failed: 59
| `- File list: /var/log/apache2/access.log
`- Actions
|- Currently banned: 587
|- Total banned: 587
`- Banned IP list: 103.41.106.199 135.181.74.243 139.59.186.196

et je vous fais grâce des 587 IP qui ont été bannies

SimpleSAMLphp, le couteau suisse de l’authentification

Lorsqu’on héberge de nombreux serveurs ou services Web, un problème se pose forcément un jour ou l’autre: la multiplication des pages d’authentification et des procédures de gestion des utilisateurs. Pour ne rien arranger, elles sont toutes indépendantes les unes des autres. Vous me direz qu’il est possible d’utiliser (par exemple) un annuaire LDAP ou un service authentification centralisé tel que OAuth, mais cela ne règle pas la corvée des authentifications multiples.

Continue la lecture

ProxyJump: rebond automatique vers une machine non routée

Toto travaille sur sa machine pc-perso, il besoin de se connecter à une machine S2 située sur un autre LAN. Manque de pot,  S2 a une adresse IP privée et n’est donc pas accessible depuis Internet.

Toto ne veut pas utiliser le VPN propriétaire du LAN distant soumis au CloudAct et au PatriotAct … Pour ne rien arranger, ce VPN qui coûte un bras est aussi lent et instable que sa grand-mère en rollers !

Mais Toto a la chance d’avoir un accès SSH direct à la machine S1 qui a une patte sur le LAN distant (elle est joignable par une adresse IP publique).

Toto fait donc toutes ses opérations (SSH, scp, rsync, …) par rebonds: il se connecte d’abord à S1 puis  à S2 à partir de S1:

  • La clé publique de toto@pc-perso DOIT être installé sur le compte toto@s1
  • La clé publique toto@s1 DOIT être installés sur le compte toto@s2

Ce n’est pas très pratique pour Toto, surtout pour transférer des fichiers …

toto@pc-perso:~# ssh s1
Welcome to Ubuntu
You have new mail.
Last login: Sat Nov 26 17:05:22 2022 from xxxxxxxxxxxxx
toto@s1:~# ssh s2
Welcome to Debian
No mail.
toto@s2:~#

Un jour, Toto a ouvert la porte de son frigo et a vu la lumière: il a découvert la fonction ProxyJump d’OpenSSH !

Il s’agit d’une option qui permet de spécifier comment accéder à une machine en rebondissant par une autre:

  • Toto DOIT disposer d’un compte sur les deux machines (S1 et S2),
  • Toto DOIT disposer d’une clé SSH sur pc-perso,
  • La clé SSH publique toto@pc-perso DOIT être installée sur les machines (S1 et S2): Toto n’a plus besoin d’une clé publique toto@s1 pour accéder à toto@s2
toto@pc-perso:~# ssh -J s1 s2
Welcome to Debian
No mail.
Last login: Sat Nov 26 17:05:26 2022 from xxxxxxxx
toto@s2:~#

Il est possible de graver ce comportement dans le marbre pour se simplifier la vie: il ne sera plus nécessaire de préciser explicitement l’option de rebond, toutes les opérations (SSH, scp, rsync, …) se feront de façon transparentes entre pc-perso et S2. Il suffit de créer/éditer le fichier ~.ssh/config sur pc-perso:

Host s2
 Hostname s2
 ProxyJump s1

Toto est content: il peut travailler sur S2 directement depuis pc-perso !

toto@pc-perso:~# ssh s2
Welcome to Debian
No mail.
Last login: Sat Nov 26 17:05:26 2022 from xxxxxxxx
toto@s2:~#

 

Patch RavadaVDI

Par défaut, le fichier généré par RavadaVDI n’embarque pas le mot de passe généré aléatoirement pour l’ouverture d’une session SPICE. De plus, la longueur du mot de passe (4 caractères) est beaucoup trop courte.

Le script suivant (ravada-patch.sh) permet de corriger ces deux problèmes sur RavadaVDI (version 1.8.0): il n’est plus nécessaire de saisir le mot de passe, sa longueur est portée à 16 caractères.

#!/bin/bash

dpkg -l patch >/dev/null 2>/dev/null || apt -y install patch

systemctl stop rvd_front.service
systemctl stop rvd_back.service

# Set spice passwords to 16 chars
files=$(grep -r "password = Ravada::Utils::random_name(4)" /usr/share/perl5/Ravada/* |grep password |awk '{print $1}' |cut -d: -f1)
for file in ${files} ; do
sed -i 's/password = Ravada::Utils::random_name(4)/password = Ravada::Utils::random_name(16)/g' ${file}
done

# Put spice passwords in ".vv" files, do no start viewer in fullscreen
[ -f Domain.pm.orig ] || cp /usr/share/perl5/Ravada/Domain.pm Domain.pm.orig
patch /usr/share/perl5/Ravada/Domain.pm Domain.pm.patch || cp Domain.pm.orig /usr/share/perl5/Ravada/Domain.pm

systemctl start rvd_back.service
systemctl start rvd_front.service

Le patch (Domain.pm.patch):

1861a1862,1863
> # Keep password for ".vv" file
> our $ppass="";
1864c1866,1868
< return $self->_data('spice_password');
---
> #return $self->_data('spice_password');
> our $ppass = $self->_data('spice_password');
> return $ppass;
1940c1944,1946
< $ret .="password=%s\n" if $self->spice_password();
---
> # Put password in ".vv" file
> #$ret .="password=%s\n" if $self->spice_password();
> $ret .="password=$ppass\n" if $self->spice_password();
1941a1948
> # Do not start in fullscreen
1943c1950
< "fullscreen=1\n"
---
> "fullscreen=0\n"