Come nascondere una password passata come argomento della riga di comando?


43

Sto eseguendo un demone software che richiede che determinate azioni inseriscano una passphrase per sbloccare alcune funzionalità che sembrano ad esempio così:

$ darkcoind masternode start <mypassphrase>

Ora ho avuto alcuni problemi di sicurezza sul mio server debian senza testa.

Ogni volta che cerco nella mia cronologia di bash per esempio con Ctrl+Rposso vedere questa password super forte. Ora immagino che il mio server sia compromesso e alcuni intrusi abbiano accesso alla shell e riescano semplicemente Ctrl+Ra trovare la mia passphrase nella cronologia.

C'è un modo per inserire la passphrase senza che sia mostrata nella storia di bash ps, /proco altrove?


Aggiornamento 1 : il passaggio di nessuna password al demone genera un errore. Questa non è un'opzione.


Aggiornamento 2 : non dirmi di eliminare il software o altri suggerimenti utili come appendere gli sviluppatori. So che questo non è un esempio di best practice, ma questo software si basa su bitcoin e tutti i client basati su bitcoin sono una sorta di server json rpc che ascolta questi comandi ed è ancora in discussione un problema di sicurezza noto ( a , b , c ) .


Aggiornamento 3 : il daemon è già avviato e in esecuzione con il comando

$ darkcoind -daemon

Fare psmostra solo il comando di avvio.

$ ps aux | grep darkcoin
user     12337  0.0  0.0  10916  1084 pts/4    S+   09:19   0:00 grep darkcoin
user     21626  0.6  0.3 1849716 130292 ?      SLl  May02   6:48 darkcoind -daemon

Quindi passare i comandi con la passphrase non viene visualizzato pso /procaffatto.

$ darkcoind masternode start <mypassphrase>
$ ps aux | grep darkcoin
user     12929  0.0  0.0  10916  1088 pts/4    S+   09:23   0:00 grep darkcoin
user     21626  0.6  0.3 1849716 130292 ?      SLl  May02   6:49 darkcoind -daemon

Questo lascia la domanda dove si presenta la storia? Solo in .bash_history?


1
La prima domanda deve essere: cosa succede se si avvia il demone senza l'argomento passphrase. Lo richiede semplicemente?
MadHatter supporta Monica

31
Non penso che ci sia una risposta che funzionerà. L'incapacità di richiedere una passphrase è un grave difetto nel demone. Se è un software gratuito, cerca un programmatore e risolvilo; non dimenticare di pubblicare le modifiche. Se è un software proprietario, chiama il venditore e urla contro di loro (ciò non risolverà nulla, ma ti farà sentire meglio).
MadHatter supporta Monica

4
Controlla la tua documentazione, potrebbe supportare la lettura di quella password da una variabile di ambiente di sistema.
Elliott Frisch

3
Anche se la password non viene fornita nella riga di comando al demone, è comunque problematico fornirla sulla riga di comando di qualsiasi altro comando. È visibile nell'output ps solo per un tempo molto breve, ma un processo in esecuzione in background potrebbe ancora rilevarlo. Ma ovviamente vale ancora la pena rendere più difficile la raccolta della password.
Kasperd,

2
Guarda le risposte a questa domanda , affrontano esattamente questo problema.
dotancohen,

Risposte:


68

Davvero, questo dovrebbe essere risolto nell'applicazione stessa. E tali applicazioni dovrebbero essere open source, quindi l'opzione di risolvere il problema nell'app stessa dovrebbe essere un'opzione. Un'applicazione relativa alla sicurezza che commette questo tipo di errore potrebbe fare anche altri errori, quindi non mi fiderei.

Interposer semplice

Ma stavi chiedendo un modo diverso, quindi eccone uno:

#define _GNU_SOURCE
#include <dlfcn.h>

int __libc_start_main(
    int (*main) (int, char * *, char * *),
    int argc, char * * ubp_av,
    void (*init) (void),
    void (*fini) (void),
    void (*rtld_fini) (void),
    void (* stack_end)
  )
{
  int (*next)(
    int (*main) (int, char * *, char * *),
    int argc, char * * ubp_av,
    void (*init) (void),
    void (*fini) (void),
    void (*rtld_fini) (void),
    void (* stack_end)
  ) = dlsym(RTLD_NEXT, "__libc_start_main");
  ubp_av[argc - 1] = "secret password";
  return next(main, argc, ubp_av, init, fini, rtld_fini, stack_end);
}

Compila questo con

gcc -O2 -fPIC -shared -o injectpassword.so injectpassword.c -ldl

quindi eseguire il processo con

LD_PRELOAD=$PWD/injectpassword.so darkcoind masternode start fakepasshrase

La libreria interposer eseguirà questo codice prima dell'esecuzione della mainfunzione dell'applicazione. Sostituirà l'ultimo argomento della riga di comando con la password effettiva nella chiamata a main. La riga di comando stampata /proc/*/cmdline(e quindi vista da strumenti come ps) conterrà comunque l'argomento falso. Ovviamente dovresti rendere il codice sorgente e la libreria che compili da esso leggibile solo a te stesso, quindi è meglio operare in una chmod 0700directory. E poiché la password non fa parte dell'invocazione del comando, anche la cronologia di bash è sicura.

Interposer più avanzato

Se vuoi fare qualcosa di più elaborato, dovresti tenere presente che __libc_start_mainviene eseguito prima che la libreria di runtime sia stata inizializzata correttamente. Quindi suggerirei di evitare qualsiasi chiamata di funzione a meno che non sia assolutamente essenziale. Se vuoi essere in grado di chiamare funzioni al contenuto del tuo cuore, assicurati di farlo appena prima che mainvenga invocato, dopo che è stata eseguita tutta l'inizializzazione. Per il seguente esempio devo ringraziare Grubermensch che ha sottolineato come nascondere una password passata come argomento della riga di comando che ha portato getpassalla mia attenzione.

#define _GNU_SOURCE
#include <dlfcn.h>
#include <unistd.h>

static int (*real_main) (int, char * *, char * *);

static int my_main(int argc, char * * argv, char * * env) {
  char *pass = getpass(argv[argc - 1]);
  if (pass == NULL) return 1;
  argv[argc - 1] = pass;
  return real_main(argc, argv, env);
}

int __libc_start_main(
    int (*main) (int, char * *, char * *),
    int argc, char * * ubp_av,
    void (*init) (void),
    void (*fini) (void),
    void (*rtld_fini) (void),
    void (* stack_end)
  )
{
  int (*next)(
    int (*main) (int, char * *, char * *),
    int argc, char * * ubp_av,
    void (*init) (void),
    void (*fini) (void),
    void (*rtld_fini) (void),
    void (* stack_end)
  ) = dlsym(RTLD_NEXT, "__libc_start_main");
  real_main = main;
  return next(my_main, argc, ubp_av, init, fini, rtld_fini, stack_end);
}

Questo richiede la password, quindi non è più necessario mantenere segreta la libreria interposer. L'argomento segnaposto viene riutilizzato come prompt della password, quindi invocare questo come

LD_PRELOAD=$PWD/injectpassword.so darkcoind masternode start "Password: "

Un'altra alternativa potrebbe leggere la password da un descrittore di file (come ad esempio gpg --passphrase-fd), o da x11-ssh-askpass, o qualsiasi altra cosa.


4
Anche se non capisco e non riesco a testare il codice, ne capisco l'essenza, e questa sembra una risposta reale e dovrebbe essere la risposta migliore.
Mark Henderson

Questo è davvero fantastico.
Waqar Lim

Eccezionale. Per quanto ne so, dovrebbe funzionare. Ovviamente devi accedere alla fonte ed essere in grado di ricompilare. La password è leggibile nel file sorgente e compilato se si utilizzano "stringhe" o qualcosa di simile, quindi assicurarsi che nessun altro possa leggerli.
Tonny,

1
Dovrebbe essere possibile prendere la password su STDIN e avere ancora questo lavoro, che rimuove la stringsvulnerabilità. Vedere SO: nasconde l'immissione della password sul terminale .
Grubermensch,

1
@ mulg0r: l' esterno standard "C" dovrebbe fare il trucco di sopprimere il nome mangling per la relativa funzione, vale a dire __libc_start_main.
MvG

28

Non è solo la storia. Verrà visualizzato anche nell'output ps .

Chiunque abbia scritto quel pezzo di software dovrebbe essere appeso, disegnato e diviso in quarti. È un NO assoluto dover fornire una password sulla riga di comando, indipendentemente dal software.
Per un processo daemon è persino PIÙ imperdonabile ...

Oltre a rm -f sul software stesso non conosco alcuna soluzione per questo. Onestamente: trova altri software per completare il lavoro. Non usare questa spazzatura.


9
Grazie per non essere stato affatto utile. Questo è un problema di sicurezza discusso a lungo , ancora irrisolto e ho bisogno di una soluzione migliore di rm -fadesso.
Waqar Lim,

17
In realtà, è stato molto utile. Se stai passando la passphrase come argomento, verrà visualizzata ps. Quindi, fino a quando lo sviluppatore non riuscirà a risolvere il problema, sta suggerendo di usare qualcos'altro.
Safado,

3
Quindi è meglio iniziare a scrivere un altro sistema operativo. Attualmente non sono disponibili altre soluzioni di cui sono a conoscenza. Per Dio, vorrei che ce ne fosse uno. Non sei l'unico con questo problema.
Tonny,

8
vertoe, non diventare snippy. Puoi chiedere un modo per passarlo su piccoli foglietti di carta, ma ciò non significa che tale modo esista automaticamente. read_x va bene, ma espone ancora la passphrase tramite ad es. ps, quindi non è migliore della rmsoluzione.
MadHatter supporta Monica

7
Prima di andare a lanciare un altro +1 su questo non proprio una risposta e lamentarti che è impossibile, ti suggerisco di rivedere la risposta di MvG di seguito
Mark Henderson

19

Questo cancellerà l' psoutput.

ATTENZIONE : questo potrebbe interrompere l'applicazione. Sei debitamente avvisato che qui ci sono draghi.

  • I processi estranei non dovrebbero armeggiare nella memoria dei processi.
  • Se il processo si basa su questa regione per la password, è possibile interrompere l'applicazione.
  • Ciò potrebbe danneggiare i dati di lavoro che hai in quel processo.
  • Questo è un trucco folle.

Ora sei debitamente informato di questi terribili avvertimenti. Questo cancellerà l'output visualizzato in ps. Non cancellerà la tua cronologia, né cancellerà la cronologia dei lavori bash (come eseguire il processo come myprocess myargs &). Ma psnon mostrerà più gli argomenti.

#!/usr/bin/python
import os, sys
import re

PAGESIZE=4096

if __name__ == "__main__":
  if len(sys.argv) < 2:
    sys.stderr.write("Must provide a pid\n")
    sys.exit(1)

  pid = sys.argv[1]

  try:
    cmdline = open("/proc/{0}/cmdline".format(pid)).read(8192)

    ## On linux, at least, argv is located in the stack. This is likely o/s
    ## independent.
    ## Open the maps file and obtain the stack address.
    maps = open("/proc/{0}/maps".format(pid)).read(65536)
    m = re.search('([0-9a-f]+)-([0-9a-f]+)\s+rw.+\[stack\]\n', maps)
    if not m:
      sys.stderr.write("Could not find stack in process\n");
      sys.exit(1)

    start = int("0x"+m.group(1), 0)
    end = int("0x"+m.group(2), 0)

    ## Open the mem file
    mem = open('/proc/{0}/mem'.format(pid), 'r+')
    ## As the stack grows downwards, start at the end. It is expected
    ## that the value we are looking for will be at the top of the stack
    ## somewhere
    ## Seek to the end of the stack minus a couple of pages.
    mem.seek(end-(2*PAGESIZE))

    ## Read this buffer to the end of the stack
    stackportion = mem.read(8192)
    ## look for a string matching cmdline. This is pretty dangerous.
    ## HERE BE DRAGONS
    m = re.search(cmdline, stackportion)
    if not m:
      ## cause this is an example dont try to search exhaustively, just give up
      sys.stderr.write("Could not find command line in the stack. Giving up.")
      sys.exit(1)

    ## Else, we got a hit. Rewind our file descriptor, plus where we found the first argument.
    mem.seek(end-(2*PAGESIZE)+m.start())
    ## Additionally, we'll keep arg0, as thats the program name.
    arg0len = len(cmdline.split("\x00")[0]) + 1
    mem.seek(arg0len, 1)

    ## lastly overwrite the remaining region with nulls.
    writeover = "\x00" * (len(cmdline)-arg0len)
    mem.write(writeover)

    ## cleanup
    mem.close()

  except OSError, IOError:
    sys.stderr.write("Cannot find pid\n")
    sys.exit(1)

Richiama il programma salvandolo chmod +x. Quindi facendo ./whatever <pidoftarget> Se funziona, non produrrà alcun output. Se fallisce, si lamenterà di qualcosa e si fermerà.


18
. . . questo è sia creativo che spaventoso.
voretaq7,

EEK! Adesso ho paura.
Janne Pikkarainen,

Yikkes, potrebbe funzionare ... Non sono sicuro che qualcosa come AppArmor lo catturerebbe? Anche il virusscanner potrebbe potenzialmente cogliere questo e causare il caos bloccando l'account offensivo che sarebbe "root". Ci sono davvero draghi ....
Tonny,

@Tonny Per domini protetti, SELinux lo impedirebbe. Le tue autorizzazioni Unix di base (DAC) mancano di una granularità del soggetto sufficiente per offrire qualsiasi protezione da questo comportamento (consente la modifica della memoria dei processi all'interno dello stesso UID). Comunque, non è un bug, è una caratteristica. Credo che sia così che si gdbpossa modificare la memoria dei processi in esecuzione (con molta più precisione chirurgica di quella che potrei aggiungere).
Matthew Ife,

11

Puoi passare l'argomento da un file, accessibile solo dal root o dall'utente richiesto?

È un ENORME no-no digitare le password nella console, ma l'ultima risorsa ... iniziare la linea con uno spazio in modo che non compaia nella cronologia.


C'era un'opzione shell che la abilita, ma penso che non sia stata abilitata di default.
heinrich5991,

export HISTCONTROL=ignorebothignora sia i duplicati che le righe con uno spazio iniziale per l'ingresso nella cronologia. Aggiungilo al tuo .bashrc o .bash_profile.
Andreas,

7

Forse funziona (?):

darkcoind masternode start `cat password.txt`

3
O anche darkcoind masternode start `head -1`, se si desidera inserire la password manualmente.
Kasperd,

14
La passphrase è ancora disponibile tramite pse utilità simili.
voretaq7,

1
Passare da una password in chiaro .bash_historya una password in chiaro password.txtti consente di ottenere esattamente cosa?
MikeyB,

1
@MikeyB: c'è una piccola vittoria: non la scoprirai accidentalmente mentre cerchi nella tua storia mentre qualcuno ti guarda alle spalle.
MvG,

1
@MikeyB, puoi creare e rimuovere quel file ogni volta.
RiaD

4

Sfortunatamente, se il tuo darkcoindcomando prevede che la password sia un argomento della riga di comando, sarà esposta attraverso utility come ps. L'unica vera soluzione è educare gli sviluppatori .

Sebbene l' psesposizione possa essere inevitabile, è possibile almeno impedire che la password venga scritta nel file cronologico della shell.

$ xargs darkcoind masternode start

password

CtrlD

Il file della cronologia deve solo registrare xargs darkcoind masternode start, non la password.


2
Oppure, se si sta utilizzando bash, messo ignorespacein $HISTCONTROL, e quindi è possibile evitare qualsiasi comando di andare in storia shell anteponendo al comando con uno spazio.
derobert,

3

Come altri hanno già detto, controlla il controllo della cronologia della shell per nascondere le informazioni dalla cronologia.

Ma una cosa che nessuno sembra aver ancora suggerito è montare /proccon il hidepidparametro. Prova a modificare la /procriga /etc/fstabper includerla hidepid, in questo modo:

# <file system> <mount point>   <type>  <options>       <dump>  <pass>
proc            /proc           proc    defaults,hidepid=2        0       0

2

È possibile mantenere la password fuori dalla cronologia della shell eseguendo il comando da un nuovo processo di shell, che si termina immediatamente. Per esempio:

bash$ sh
sh$ darkcoind masternode start 'correct horse battery staple'
sh$ exit
bash$

Assicurarsi che shsia configurato per non salvare la sua cronologia in un file.

Naturalmente questo non affronta gli altri problemi, come la password in cui è visibile ps. Credo che ci siano modi in cui il darkcoindprogramma stesso può nascondere le informazioni ps, ma ciò riduce solo la finestra della vulnerabilità.


1
la passphrase è ancora disponibile tramite pse utilità simili.
voretaq7,

3
@ voretaq7: Sì, come ho esplicitamente riconosciuto nell'ultimo paragrafo della mia risposta.
Keith Thompson,

3
Effettivamente - sei stata vittima di un coponasta sfrenata da parte mia :)
voretaq7,

2

Per Bitcoin, la risposta ufficiale dello sviluppatore è utilizzare il wrapper python fornito in contrib/bitrpc/bitrpc.py( github ):

Richiede una password in modo sicuro se si utilizza il comando walletpassphrase, ad esempio. Non ci sono piani per aggiungere funzionalità interattive a bitcoin-cli.

e:

bitcoin-cli rimarrà così com'è e non acquisirà funzionalità interattive.

Fonte: # 2318

Sblocca portafoglio:

$ python bitrpc.py walletpassphrase

Cambia passphrase:

$ python bitrpc.py walletpassphrasechange

https://github.com/bitcoin/bitcoin/tree/master/contrib/bitrpc

Per darkcoin funziona anlogue:

https://github.com/darkcoin/darkcoin/tree/master/contrib/bitrpc

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.