Attenti a quella variabile!

Può capitare che siate dei fan di Unix costretti a vivere e lavorare in un mondo dominato da Windows. Ed è così che l’icona di Cygwin è finita sul vostro desktop, pronta a tirare fuori un po’ della potenza di bash sotto l’altrimenti goffo sistema operativo di Redmond.
Diciamo che state configurando un router Cisco, e avete bisogno di generare l’hash password MD5 per il comando enable. Avviate la console di Cygwin, e date il comando:

$ openssl passwd -1 -salt `openssl rand -base64 3` 'pippo123'
$GtbFfYAr5gYVoHGeC3orC0

Ferma, ferma: dov’è l’identificativo del tipo di hash? Dov’è il salt? Riproviamo con una stringa statica come salt:

$ openssl passwd -1 -salt 'ABCD' 'pippo123'
$1$ABCD$5WUpHh3qSVtWFVO9jX0PF1

Ecco qui un hash password valido. Cosa sta succedendo? Qualcosa a che vedere con il sottocomando racchiuso tra backtick, ovvio.
Vediamo di andare sotto il pelo dell’acqua e scoprire dov’è la falla.

$ SALT=`openssl rand -base64 3`
$ openssl passwd -1 -salt $SALT 'pippo123'
$oeycTEV6GRCBIOdB5ghtx0

Niente da fare. Ma cosa contiene la variabile SALT?

$ echo -n $SALT | od -t x1 -a
0000000  55  6f  48  6e  0d
          U   o   H   n  cr
0000005

Ecco il problema. La variabile SALT contiene un carattere CR di troppo. Passando questo valore ad openssl per la generazione di un hash password, succede quanto segue:

  • viene stampato il carattere $
  • viene stampato il carattere 1 (identificativo del tipo di hash)
  • viene stampato il carattere $
  • vengono stampati i caratteri UoHn (quel che dovrebbe essere il nostro salt)
  • viene stampato il carattere CR: il cursore torna ad inizio riga, e quel che è stato stampato finora viene sovrascritto
  • viene stampato il carattere $
  • viene stampato il valore dell’hash

Ma perché la variabile SALT contiene quel carattere CR di troppo? Semplice. Quando la shell deve suddividere l’output di un sottocomando, ad esempio per assegnarne il valore ad una variabile, lo fa in base alla variabile d’ambiente IFS (internal field separator). I caratteri contenuti in IFS sono considerati dalla shell separatori, e vengono usati per separare le parole. Diamo un occhio a IFS:

$ echo -n "$IFS" | od -t x1 -a
0000000  20  09  0a
         sp  ht  nl
0000003

Il carattere CR non è elencato, perciò la shell lo include nel risultato restituito dal sottoprocesso invece di tagliarlo. Proviamo a modificare IFS:

$ IFS=$' \t\n\r'

Scommettiamo?

$ openssl passwd -1 -salt `openssl rand -base64 3` 'pippo123'
$1$YAse$usRK92/ae4Ye9oM8X9VIl1

L’esempio è banale ma ci è servito a individuare un problema potenzialmente insidioso. Siamo pur sempre in ambiente Windows, ed è quindi lecito supporre che prima o poi la shell di Cygwin si trovi a gestire l’output di un programma Windows nativo, che verosimilmente terminerà le proprie linee con la famigerata accoppiata CR/LF. In questi casi, il comportamento di script di provenienza Unix potrebbe non essere quello desiderato, e il debug di procedure fatte magari da centinaia o migliaia di righe di codice potrebbe rivelarsi problematico. Meglio mettere una pezza immediatamente:

$ nano ~/.bashrc
  GNU nano 2.4.1                                 File: /home/lorenzo/.bashrc

#   #
#   # Trim down everything beyond 11th entry
#   popd -n +11 2>/dev/null 1>/dev/null
#
#   #
#   # Remove any other occurence of this dir, skipping the top of the stack
#   for ((cnt=1; cnt <= 10; cnt++)); do
#     x2=$(dirs +${cnt} 2>/dev/null)
#     [[ $? -ne 0 ]] && return 0
#     [[ ${x2:0:1} == '~' ]] && x2="${HOME}${x2:1}"
#     if [[ "${x2}" == "${the_new_dir}" ]]; then
#       popd -n +$cnt 2>/dev/null 1>/dev/null
#       cnt=cnt-1
#     fi
#   done
#
#   return 0
# }
#
# alias cd=cd_func

# imposto IFS in modo che contenga anche CR
IFS=$' \t\n\r'

Al prossimo avvio, la shell di Gygwin avrà la variabile IFS correttamente impostata e gestirà correttamente l’output di comandi in stile Windows.
Buon hacking a tutti!