Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Campagne de tests de sécurité d’air-sshd — journal de résultats (ADR-105)

Journal vivant de la campagne définie par ADR-105. Chaque axe : méthode, résultat mesuré, critère coché ou correctif ouvert. La campagne est menée après la clôture fonctionnelle d’air-sshd (SFTP S.5, re-sceau couche-1-v2.4).

Topologie

  • Attaquant : carbon (air-test-host, 8 c / 16 Gio), atteint depuis speedy via ssh carbon.net (192.168.1.21 port 52222). Outils hors dépôt : nmap, ncrack, hydra, hping3, ssh-audit (~/sectools-venv), Terrapin-Scanner (~/.local/bin).
  • Cible : air-sshd linux-air (*-unknown-linux-air, static-pie, 0 glibc, vérifié readelf -d → aucun NEEDED), service systemd air-sshd.service actif sur 192.168.1.43:2222 (IP interne ; ⚠️ speedy.net résout côté carbon vers une IP publique — ne pas l’utiliser). Réseau interne uniquement.

Axe 1 — Audit non-intrusif du transport (2026-07-26)

Méthode : nmap -sV, ssh-audit, Terrapin-Scanner depuis carbon.

CritèreVerdictPreuve
Terrapin (CVE-2023-48795)NON vulnérableTerrapin-Scanner : strict key exchange true, ChaCha20-Poly1305 true, CBC-EtM false — « can establish connections that are NOT VULNERABLE to Terrapin »
Profil crypto moderne-only✅ conformessh-audit : KEX curve25519-sha256(@libssh.org) + kex-strict-s-v00@openssh.com ; hôte ssh-ed25519 ; chiffres AEAD chacha20-poly1305@openssh.com + aes256-gcm@openssh.com ; compression none ; aucun RSA/DSA/CBC/SHA-1
Bannièreℹ️SSH-2.0-Air_0.1.0 (serveur Air, pas un OpenSSH déguisé)
Débit de connexions pré-auth⚠️ finding → Axe 2ssh-audit : ~2890–4302 conn/s acceptées sans throttling par source (heuristique DHEat, CVE-2002-20001)

Axe 2 — DoS pré-auth : throttling par source (2026-07-26) — CLOS

Finding de l’Axe 1 : aucune limite de connexions pré-auth par source — un flood force autant de KEX. Correctif (couche 2 pure, air-sshd::throttle) :

  • Concurrence par source (16) et globale (256) — PR #438 ;
  • Débit par source (token bucket : 10 conn/s soutenu, rafale 20) — PR #439 ;
  • au-delà d’une borne : refus avant tout KEX (fermeture du fd accepté) ; crédit de concurrence rendu à l’authentification (une session établie ne pèse plus).

Validation empirique (carbon → cible redéployée) :

TestAttenduMesuré
24 connexions concurrentes d’une source≤ 16 admises16 admises / 8 refusées avant KEX
300 handshakes KEX en rafale d’une sourcefortement bornés27 admises / 273 refusées (~91 % coupées)
Handshake légitime complet, source distincte, pendant le churn KEXlatence ≈ baselineOK, clé récupérée, ~0.05 s (baseline 0.04–0.07 s)

Critère ADR-105 « un DoS pré-auth n’empêche pas une connexion légitime » : ✅ ATTEINT, prouvé au niveau du KEX complet (pas seulement l’admission).

Nuance consignée (transparence) : ssh-audit continue d’afficher l’avertissement DHEat — son test compte les connexions acceptées au niveau TCP (noyau), or notre limite agit après l’accept ; ssh-audit ne peut structurellement pas la voir, et son propre message admet que « même PerSourceMaxStartups 1 yield a false positive ». De plus, air-sshd n’offre que curve25519 (pas de DH à corps fini) : le vecteur DHEat au sens strict (coût de l’exponentiation modulaire) ne s’applique pas. Un throttling au niveau pare-feu (nftables limit) ferait taire l’heuristique, mais relève du déploiement, hors périmètre du démon.


Axe 3 — dudect : temps constant de la comparaison de tag (2026-07-26) — CLOS

Méthode (ADR-105 niveau 1 : local, cycle-précis) : dudect (Reparaz et al.). On mesure AirHmac::verify (la comparaison de tag constant-time subtle sur laquelle reposent les vérifications d’air-sshd) sur deux classes de même longueur — tag correct (correspondance complète) vs tag au premier octet inversé (divergence précoce) — et on applique un test t de Welch. Timing cycle-précis (rdtsc), 300 000 échantillons/ classe, ordre entrelacé aléatoire, cropping d’outliers (p99/p95). Harnais in-tree : crates/air-crypto/tests/dudect_constant_time.rs (gaté AIR_DUDECT=1, hors gate CI).

CritèreVerdictPreuve
Comparaison de tag constant-time✅ prouvé`

Un memcmp à court-circuit aurait donné |t| ≫ 10 sur la classe « divergence précoce ». Portée : la même primitive subtle sous-tend la comparaison finale d’Argon2id (verify_password) et de Poly1305 (ssh_aead) — couvertes par extension. La vérification de signature Ed25519 est constant-time par construction (crate ed25519). La résolution de login (anti-énumération) est un temps bout-en-bout : elle relève de l’Axe 4 (réseau).

Axe 4 — anti-énumération de comptes (2026-07-26) — CLOS

Méthode : niveau 1 (dudect local) + corroboration réseau (niveau 2).

Niveau 1 — dudect sur is_authorized (crates/air-sshd/tests/dudect_authorization.rs, gaté AIR_DUDECT=1) : temps d’autorisation d’un compte existant (mauvaise clé) vs inexistant (même longueur de nom), rdtsc, 400 000/classe.

BackendVerdictPreuve
InMemoryAuthorizedKeys✅ aucune fuite détectable`
FileAuthorizedKeys (production)⚠️ différence au niveau fonction, non exploitablepar analyse : un compte existant déclenche une lecture de fichier en plus (son magasin par-utilisateur) qu’un compte inexistant n’a pas — écart ~µs

Niveau 2 — corroboration réseau (carbon → démon de production, 30 essais/classe, espacés sous le rate-limit) : temps d’échec d’auth pour thierry (existant) vs zznouser (inexistant), clé non autorisée.

Compteminp10médianep90
existant23.224.733.836.8 ms
inexistant24.426.032.036.4 ms

Distributions indistinguables (quantiles bas à ~1 ms près ; plancher KEX ~24 ms). Le sur-coût d’I/O de FileAuthorizedKeys (~µs) est noyé sous le coût réseau+KEX.

Critère ADR-105 « user-existant vs -inexistant indistinguable » : ✅ ATTEINT (réponse uniforme U.4 + timing indistinguable au réseau). Limitation consignée (pas exploitable) : FileAuthorizedKeys n’est pas constant-time par construction (I/O supplémentaire pour un compte existant) ; un durcissement (I/O uniforme) est possible mais à rendement décroissant (l’écart est déjà sous le jitter, et des canaux cache/inode subsisteraient) — différé, décision ouverte.

Axe 5 — brute-force (2026-07-26) — CLOS

air-sshd est publickey-only (ADR-094) : la surface de brute-force par mot de passe est nulle. Tests depuis le vrai client OpenSSH :

TestVerdictPreuve
Surface password✅ nullePreferredAuthentications=password → « Authentications that can continue: publickey » puis « No more authentication methods »
Plafond d’essais publickey/connexion (U.4)67 clés non autorisées offertes → exactement 6 offres puis déconnexion (DEFAULT_MAX_AUTH_FAILURES = 6, cf. MaxAuthTries OpenSSH)
Débit de brute-force effectif✅ borné6 essais/connexion × ≤ 10 connexions/s/source (rate-limit, axe 2) = ≤ 60 essais/s/source — infaisable contre Ed25519 (2¹²⁸) ; une source légitime distincte reste servie

Axe 6 — SYN flood (2026-07-26) — CLOS

Méthode : hping3 --flood -S (raw socket, root) depuis carbon contre 192.168.1.43:2222, 8 s.

CritèreVerdictPreuve
Défense noyaunet.ipv4.tcp_syncookies = 1 (SYN cookies actifs)
Joignabilité pendant le floodhandshake légitime complet 0.05 s (×3) pendant les 8 s de flood
Récupération aprèsservice active, handshake post-flood OK

Le SYN flood est absorbé par la pile TCP du noyau (SYN cookies) ; air-sshd ne voit que les connexions établies, protégées en amont par le throttling par source (axe 2).


Conclusion — campagne ADR-105 close (2026-07-26)

air-sshd (livrable linux-air, 0 glibc) a été confronté à toutes les classes d’attaques SSH connues ciblées par l’ADR : downgrade/Terrapin, DoS pré-auth (concurrence

  • débit), fuite de temps (comparaison de secret, énumération de comptes), brute-force, SYN flood. Tous les critères de réussite sont atteints.

Le seul finding (débit de connexions pré-auth non borné, axe 1) a ouvert un correctif tracé (throttling par source, #438/#439) validé empiriquement contre un vrai attaquant (axe 2). Une limitation non exploitable reste consignée (axe 4 : FileAuthorizedKeys non constant-time par construction, écart ~µs noyé sous ~24 ms de réseau) — durcissement I/O-uniforme différé (rendement décroissant, décision ouverte).

Méthode à deux niveaux honorée : les preuves constant-time reposent sur le niveau 1 dudect (cycle-précis, local) ; le niveau 2 réseau corrobore sans prétendre au constant-time (jitter). Les harnais niveau 1 sont in-tree et gatés AIR_DUDECT=1 (hors gate CI) ; les attaques réseau sont menées depuis carbon (outillage hors dépôt).

Chantier air-sshd clos : shell + exec + forwarding + SFTP (S.5) + durcissement sécurité (ADR-105). Prochaine décision BDFL : durcissement FileAuthorizedKeys (optionnel) ou passage à la suite.