Aller au contenu
Sylvain Joffraud l4w
Retour

Backup PULL : clé SSH restreinte avec `from=` + `no-pty`

Backup serveur → NAS, le réflexe c’est « le serveur push au NAS ». Pratique. Sauf que ça a un trou énorme : si le serveur est compromis, l’attaquant a un canal direct vers le NAS. Ransomware moderne = il chiffre les backups en plus du live → tu perds tout.

Inversion : le NAS pull les backups depuis le serveur. Le serveur ne sait pas où vont ses données. La clé SSH du puller vit sur le NAS, jamais sur le serveur. Ransomware sur serveur = NAS inaccessible, qui continue son backup nightly. C’est le modèle de BorgBackup, Restic, et toute infra sérieuse.

Le piège du PULL : restreindre la clé

Si le NAS a une clé qui peut SSH root@serveur, on a juste inversé le problème. La clé doit être restreinte au minimum, dans /root/.ssh/authorized_keys côté serveur :

from="203.0.113.42,2001:db8::42",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-ed25519 AAAAC3Nz... monuser@srv-c

Test depuis une autre IP que srv-c :

$ ssh -i id_srv-b_backup -p 2222 root@analytics.example.com 'whoami'
Permission denied (publickey).

Refus immédiat même avec la bonne clé : from= filtre au niveau OpenSSH, avant l’auth. Depuis l’IP autorisée, un shell est refusé (no-pty) mais les commandes one-shot passent :

$ ssh -i id_srv-b_backup -p 2222 root@analytics.example.com 'docker exec plausible-plausible_db-1 pg_dumpall -U postgres'
... dump SQL stream ...

Exactement ce qu’on veut : commandes précises OK, pas de shell ni de tunnel.

Blinder avec command= forcée

Pour aller plus loin, forcer un wrapper quoi que tape le client :

command="/usr/local/bin/backup-helper.sh",from="...",no-pty,... ssh-ed25519 AAAA...

Le wrapper reçoit la demande dans $SSH_ORIGINAL_COMMAND et la valide :

case "$SSH_ORIGINAL_COMMAND" in
  "docker exec plausible-plausible_db-1 pg_dumpall -U postgres") exec $SSH_ORIGINAL_COMMAND ;;
  *) echo "command not allowed" >&2; exit 1 ;;
esac

Compromission NAS = le puller fait EXACTEMENT les commandes prévues, rien d’autre. Pas de cat /etc/shadow, pas de rm -rf. Least privilege au niveau auth SSH.

Lessons learned


Partager cet article :

Article précédent
Double Gitea mirror : 3 pièges API GitHub & perm `gitea.db`
Article suivant
Monitoring perso : l’API SMS gratuite de Free Mobile