Wie verstecke ich ein als Kommandozeilenargument übergebenes Passwort?


43

Ich führe einen Software-Daemon aus, der für bestimmte Aktionen die Eingabe einer Passphrase erfordert, um einige Funktionen freizuschalten, die beispielsweise so aussehen:

$ darkcoind masternode start <mypassphrase>

Jetzt habe ich einige Sicherheitsbedenken auf meinem headless Debian-Server.

Wann immer ich zum Beispiel meine Bash-Historie mit durchsuche, Ctrl+Rkann ich dieses superstarke Passwort sehen. Jetzt stelle ich mir vor, mein Server ist kompromittiert und ein Eindringling hat Shell-Zugriff und kann einfach Ctrl+Rmeine Passphrase im Verlauf finden.

Gibt es eine Möglichkeit das Passwort eingeben , ohne es in der Bash Geschichte gezeigt werden, ps, /procoder anderswo?


Update 1 : Wenn Sie dem Dämon kein Kennwort übergeben, wird ein Fehler ausgegeben. Dies ist keine Option.


Update 2 : Sagen Sie mir nicht, dass ich die Software oder andere hilfreiche Hinweise wie das Aufhängen der Entwickler löschen soll. Ich weiß, dass dies kein Best-Practice-Beispiel ist, aber diese Software basiert auf Bitcoin und alle Bitcoin-basierten Clients sind eine Art von JSON-RPC-Server, der diese Befehle abhört. Ein bekanntes Sicherheitsproblem wird noch diskutiert ( a , b , c ) .


Update 3 : Der Daemon ist bereits gestartet und wird mit dem Befehl ausgeführt

$ darkcoind -daemon

Dabei pswird nur der Startbefehl angezeigt.

$ 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

Das Übergeben der Befehle mit der Passphrase wird daher in psoder überhaupt nicht angezeigt /proc.

$ 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

Dies lässt die Frage offen, wo die Geschichte auftaucht. Nur in .bash_history?


1
Die erste Frage muss lauten: Was passiert, wenn Sie den Dämon ohne das Argument der Passphrase starten? Fordert es nur dazu auf?
MadHatter unterstützt Monica

31
Ich glaube nicht, dass es eine Antwort gibt, die funktionieren wird. Die Unfähigkeit, nach einer Passphrase zu fragen, ist ein Hauptmangel des Daemons. Wenn es sich um freie Software handelt, holen Sie sich einen Programmierer und reparieren Sie sie. Vergessen Sie nicht, Ihre Änderungen zu veröffentlichen. Wenn es sich um proprietäre Software handelt, rufen Sie den Anbieter an und rufen Sie ihn an (das behebt zwar nichts, aber Sie fühlen sich besser).
MadHatter unterstützt Monica

4
Überprüfen Sie Ihre Dokumentation. Möglicherweise wird das Lesen dieses Kennworts aus einer Systemumgebungsvariablen unterstützt.
Elliott Frisch

3
Selbst wenn das Kennwort nicht in der Befehlszeile des Daemons angegeben wird, ist es immer noch problematisch, es in der Befehlszeile eines anderen Befehls anzugeben. Es ist nur für eine sehr kurze Zeit in der ps-Ausgabe sichtbar, aber ein Prozess, der im Hintergrund ausgeführt wird, könnte es trotzdem aufgreifen. Aber es lohnt sich natürlich immer noch, das Abrufen des Passworts zu erschweren.
Kasperd

2
Schauen Sie sich die Antworten auf diese Frage an , sie beschäftigen sich genau mit diesem Thema.
Dotancohen

Antworten:


68

Eigentlich sollte dies in der Anwendung selbst behoben werden. Und solche Anwendungen sollten Open Source sein, so dass das Beheben des Problems in der App selbst eine Option sein sollte. Eine sicherheitsrelevante Anwendung, die solche Fehler macht, kann auch andere Fehler machen, daher würde ich ihr nicht vertrauen.

Einfacher Interposer

Aber Sie haben nach einem anderen Weg gefragt, also ist hier einer:

#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);
}

Kompilieren Sie dies mit

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

dann starte deinen Prozess mit

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

Die Interposer-Bibliothek führt diesen Code aus, bevor die mainFunktion aus Ihrer Anwendung ausgeführt wird. Es ersetzt das letzte Befehlszeilenargument durch das tatsächliche Kennwort im Aufruf von main. Die Befehlszeile, wie sie in /proc/*/cmdline(und daher von Tools wie ps) gedruckt wird, enthält jedoch weiterhin das falsche Argument. Natürlich müssten Sie den Quellcode und die Bibliothek, die Sie daraus kompilieren, nur für sich selbst lesbar machen, um am besten in einem chmod 0700Verzeichnis zu arbeiten. Und da das Passwort nicht Teil des Befehlsaufrufs ist, ist auch Ihr Bash-Verlauf sicher.

Fortgeschrittener Interposer

Wenn Sie etwas ausführlicheres tun möchten, sollten Sie berücksichtigen, dass dies __libc_start_mainausgeführt wird, bevor die Laufzeitbibliothek ordnungsgemäß initialisiert wurde. Daher würde ich empfehlen, Funktionsaufrufe zu vermeiden, sofern sie nicht unbedingt erforderlich sind. Wenn Sie Funktionen nach Herzenslust aufrufen möchten, stellen Sie sicher, dass Sie dies unmittelbar vor dem Aufruf tun main, nachdem die gesamte Initialisierung abgeschlossen ist. Für das folgende Beispiel muss ich danken Grubermensch der darauf hinweist, wie ein Passwort als Kommandozeilen - Argument übergeben zu verbergen , das gebracht , getpassum meine Aufmerksamkeit.

#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);
}

Dadurch werden Sie zur Eingabe des Kennworts aufgefordert, sodass Sie die Interposer-Bibliothek nicht mehr geheim halten müssen. Das Platzhalterargument wird als Kennworteingabeaufforderung wiederverwendet. Rufen Sie dies also wie folgt auf

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

Eine andere Alternative würde das Passwort aus einem Dateideskriptor (wie z. B. gpg --passphrase-fd) oder aus x11-ssh-askpassoder was auch immer lesen .


4
Obwohl ich den Code nicht verstehe und nicht testen kann, verstehe ich das Wesentliche. Dies sieht nach einer tatsächlichen Antwort aus und sollte die beste Antwort sein.
Mark Henderson

Das ist in der Tat großartig.
Waqar Lim

Genial. Soweit ich das beurteilen kann, sollte das funktionieren. Natürlich müssen Sie Zugriff auf die Quelle haben und neu kompilieren können. Das Passwort ist in der Quelle und in der kompilierten Datei (en) lesbar, wenn Sie "Strings" oder ähnliches verwenden, stellen Sie also sicher, dass niemand anderes diese lesen kann.
Heute,

1
Es sollte möglich sein, das Kennwort für STDIN zu übernehmen und dennoch über diese Funktion zu verfügen, mit der die stringsSicherheitsanfälligkeit behoben wird . Siehe SO: Passworteingabe am Terminal verbergen .
Grubermensch

1
@ mulg0r: Standard extern "C" sollte den Trick machen, die Namensverfälschung für die betreffende Funktion zu unterdrücken, nämlich __libc_start_main.
MVG

28

Es ist nicht nur die Geschichte. Es wird auch in der ps- Ausgabe angezeigt .

Wer diese Software geschrieben hat, sollte aufgehängt, gezeichnet und geviertelt werden. Es ist ein absolutes NEIN, unabhängig von der verwendeten Software ein Kennwort in der Befehlszeile eingeben zu müssen.
Für einen Daemon-Prozess ist es sogar noch unverzeihlicher ...

Außer rm -f auf der Software selbst kenne ich keine Lösung dafür. Ganz ehrlich: Finden Sie andere Software, um die Arbeit zu erledigen. Verwenden Sie keinen solchen Müll.


9
Vielen Dank, dass Sie überhaupt nicht hilfreich sind. Dies ist ein lange diskutiertes Sicherheitsproblem , das immer noch ungelöst ist, und ich brauche eine bessere Umgehung als rm -fjetzt.
Waqar Lim

17
Eigentlich ist er sehr hilfsbereit. Wenn Sie die Passphrase als Argument übergeben, wird sie in angezeigt ps. Solange der Entwickler das nicht beheben kann, schlägt er vor, etwas anderes zu verwenden.
Safado

3
Dann schreiben Sie besser ein anderes Betriebssystem. Derzeit gibt es KEINE andere mir bekannte Lösung. Bei Gott, ich wünschte, es gäbe einen. Sie sind nicht der einzige mit diesem Problem.
Tonny

8
Vertoe, werde nicht schnippisch. Sie können nach einer Möglichkeit fragen, es auf kleinen Zetteln weiterzugeben, aber das bedeutet nicht, dass es eine solche Möglichkeit automatisch gibt. read_x ist in Ordnung, macht die Passphrase jedoch weiterhin über zB verfügbar ps, sodass sie nicht besser ist als die rmLösung.
MadHatter unterstützt Monica

7
Bevor du noch eine +1 auf diese nicht wirklich eine Antwort wirfst und
Mark Henderson

19

Dadurch wird die psAusgabe gelöscht.

SEHR BEACHTEN : Dies könnte die Anwendung beschädigen . Sie werden gebührend gewarnt, dass hier Drachen sind.

  • Fremde Prozesse sollten nicht in einem Prozessspeicher herumspielen.
  • Wenn der Prozess für das Kennwort auf dieser Region beruht, können Sie Ihre Anwendung brechen.
  • Andernfalls können alle in diesem Prozess vorhandenen Arbeitsdaten beschädigt werden.
  • Dies ist ein verrückter Hack.

Jetzt werden Sie ordnungsgemäß über diese ernsten Warnungen informiert. Dadurch wird die in angezeigte Ausgabe gelöscht ps. Es löscht weder Ihren Verlauf noch den Bash-Job-Verlauf (z. B. Ausführen des Prozesses wie myprocess myargs &). Aber psdie Argumente werden nicht mehr angezeigt.

#!/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)

Rufen Sie das Programm auf, indem Sie chmod +xes speichern . Dann mache ./whatever <pidoftarget> Wenn dies funktioniert, wird keine Ausgabe erzeugt. Wenn es fehlschlägt, wird es sich über etwas beschweren und aufhören.


18
. . . Das ist sowohl kreativ als auch beängstigend.
voretaq7

EEK! Jetzt habe ich Angst.
Janne Pikkarainen

Yikkes, das könnte funktionieren ... Ich bin mir nicht sicher, ob AppArmor das fangen würde? Auch der Virenscanner könnte dies möglicherweise abfangen und Chaos anrichten, indem er das anstößige Konto blockiert, das "root" wäre. Es gibt in der Tat Drachen ...
Tonny

@Tonny Bei geschützten Domänen würde SELinux dies verhindern. Ihren grundlegenden Unix-Berechtigungen (DAC) fehlt eine ausreichende Subjektgranularität, um Schutz vor diesem Verhalten zu bieten (dies ermöglicht die Änderung des Prozessspeichers innerhalb derselben UID). Wie auch immer, es ist kein Fehler - es ist eine Funktion. Ich glaube, so gdbkann das Gedächtnis von laufenden Prozessen verändert werden (mit viel mehr chirurgischer Präzision, als ich hinzufügen könnte).
Matthew Ife

11

Können Sie das Argument aus einer Datei übergeben, auf die nur root oder der gewünschte Benutzer zugreifen kann?

Es ist ein RIESIGES Nein-Nein, Kennwörter in die Konsole einzugeben, aber die letzte Möglichkeit ... Beginnen Sie Ihre Zeile mit einem Leerzeichen, damit es nicht im Verlauf erscheint.


Es gab eine Shell-Option, die es aktiviert, aber ich denke, es war nicht standardmäßig aktiviert.
heinrich5991

export HISTCONTROL=ignorebothIgnoriert sowohl Duplikate als auch Zeilen mit einem führenden Leerzeichen für den Eintrag in den Verlauf. Fügen Sie es Ihrem .bashrc- oder .bash_profile hinzu.
Andreas

7

Vielleicht funktioniert das (?):

darkcoind masternode start `cat password.txt`

3
Oder auch darkcoind masternode start `head -1`, wenn Sie das Passwort manuell eingeben möchten.
Kasperd

14
Die Passphrase ist weiterhin über psund ähnliche Dienstprogramme verfügbar .
Voretaq7

1
Wenn Sie von einem Klartext-Passwort .bash_historyzu einem Klartext-Passwort password.txtwechseln, erhalten Sie was genau?
MikeyB

1
@MikeyB: Es gibt einen kleinen Gewinn: Sie werden ihn nicht versehentlich aufdecken, während Sie Ihre Chronik durchsuchen, während jemand über Ihre Schulter schaut.
MvG

1
@MikeyB, Sie können diese Datei jedes Mal erstellen und entfernen.
RIAD

4

Wenn Ihr darkcoindBefehl das Kennwort als Befehlszeilenargument erwartet, wird es leider durch Dienstprogramme wie verfügbar gemacht ps. Die einzige wirkliche Lösung besteht darin , die Entwickler zu schulen .

Während die psEnthüllung unvermeidbar sein kann, können Sie zumindest verhindern, dass das Kennwort in die Shell-Verlaufsdatei geschrieben wird.

$ xargs darkcoind masternode start

password

CtrlD

In der Verlaufsdatei sollte nur xargs darkcoind masternode startdas Kennwort und nicht das Kennwort aufgezeichnet werden.


2
Oder wenn Sie bash verwenden, setzen ignorespacein $HISTCONTROL, und dann können Sie verhindern , dass jeder Befehl aus, die in der Shell - Geschichte , indem Sie den Befehl mit einem Leerzeichen voranstellen .
Derobert

3

Sehen Sie sich, wie bereits erwähnt, Ihr Shell-Verlaufssteuerelement an, um die Informationen vor dem Verlauf zu verbergen.

Aber eine Sache, die noch niemand angedeutet zu haben scheint, ist, /procmit dem hidepidParameter zu mounten . Versuchen Sie, Ihre /procZeile so /etc/fstabzu ändern hidepid, dass sie Folgendes enthält:

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

2

Sie können das Kennwort aus dem Shell-Verlauf entfernen, indem Sie den Befehl über einen neuen Shell-Prozess ausführen, den Sie dann sofort beenden. Zum Beispiel:

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

Stellen Sie sicher, dass shkonfiguriert ist, dass der Verlauf nicht in einer Datei gespeichert wird.

Natürlich werden die anderen Probleme, wie das Kennwort, das in angezeigt wird, dadurch nicht behoben ps. Es gibt meines Erachtens Möglichkeiten, vor denen das darkcoindProgramm selbst die Informationen verbirgt ps, aber dies verkürzt nur das Fenster der Sicherheitsanfälligkeit.


1
Die Passphrase ist weiterhin über psund ähnliche Dienstprogramme verfügbar .
Voretaq7

3
@ voretaq7: Ja, wie ich im letzten Absatz meiner Antwort ausdrücklich bestätigt habe.
Keith Thompson

3
In der Tat - Sie waren das Opfer mutwilliger Copypasta meinerseits :)
voretaq7

2

Für Bitcoin lautet die offizielle Antwort des Entwicklers, den mitgelieferten Python-Wrapper in contrib/bitrpc/bitrpc.py( github ) zu verwenden:

Sie werden auf sichere Weise nach einem Kennwort gefragt, wenn Sie beispielsweise den Befehl verwenden walletpassphrase. Es ist nicht geplant, interaktive Funktionen hinzuzufügen bitcoin-cli.

und:

bitcoin-cli bleibt unverändert und erhält keine interaktive Funktionalität.

Quelle: # 2318

Brieftasche freischalten:

$ python bitrpc.py walletpassphrase

Passwort ändern:

$ python bitrpc.py walletpassphrasechange

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

Für darkcoin funktioniert es wie folgt:

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

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.