Frage Wie versteckt man ein als Kommandozeilenargument übergebenes Passwort?


Ich betreibe einen Software-Daemon, der für bestimmte Aktionen eine Passphrase benötigt, um einige Funktionen freizuschalten, die beispielsweise so aussehen:

$ darkcoind masternode start <mypassphrase>

Jetzt habe ich Sicherheitsbedenken auf meinem kopflosen Debian-Server.

Immer wenn ich meine Bash-Historie zum Beispiel mit suche Ctrl+R Ich kann dieses super starke Passwort sehen. Jetzt stelle ich mir vor, mein Server ist kompromittiert und einige Eindringlinge haben Shellzugriff und können einfach Ctrl+R um meine Passphrase in der Geschichte zu finden.

Gibt es eine Möglichkeit, die Passphrase einzugeben, ohne dass sie in der Bash-Historie angezeigt wird, ps, /proc oder anderswo?


Update 1: Wenn Sie kein Kennwort an den Daemon übergeben, wird ein Fehler ausgegeben. Das ist keine Option.


Update 2: Sag 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 auf diese Befehle hört und dessen bekanntes Sicherheitsproblem noch diskutiert wird (ein, b, c).


Update 3: Der Daemon wurde bereits gestartet und läuft mit dem Befehl

$ darkcoind -daemon

Tun ps Zeigt nur den Startbefehl an.

$ 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 also nicht angezeigt ps oder /proc überhaupt.

$ 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 im .bash_history?


42
2018-05-02 15:30


Ursprung


Die erste Frage muss lauten: Was passiert, wenn Sie den Daemon ohne das Passphrase-Argument starten? Gibt es nur darauf hin? - MadHatter
Ich denke nicht, dass es eine Antwort gibt, die funktioniert. Die Unfähigkeit zur Eingabe einer Passphrase ist a Haupt Fehler im Daemon. Wenn es sich um freie Software handelt, nehmen Sie einen Programmierer und beheben Sie es; Vergessen Sie nicht, Ihre Änderungen zu veröffentlichen. Wenn es proprietäre Software ist, rufen Sie den Verkäufer an und rufen Sie ihn an (das wird nichts reparieren, aber Sie werden sich besser fühlen). - MadHatter
Überprüfen Sie Ihre Dokumentation. Möglicherweise wird das Lesen des Kennworts von einer Systemumgebungsvariablen unterstützt. - Elliott Frisch
Selbst wenn das Passwort nicht in der Befehlszeile für den Daemon angegeben wird, ist es immer noch problematisch, es in der Befehlszeile eines anderen Befehls anzugeben. Es ist nur in der ps-Ausgabe für eine sehr kurze Zeit sichtbar, aber ein Prozess, der im Hintergrund läuft, könnte es immer noch aufnehmen. Aber es lohnt sich natürlich immer noch, das Passwort aufzuheben. - kasperd
Sieh dir die Antworten an diese Frage, sie beschäftigen sich genau mit diesem Thema. - dotancohen


Antworten:


Wirklich, das sollte in der Anwendung selbst behoben werden. Und solche Anwendungen sollte Open Source sein, so dass das Problem in der App selbst behoben werden sollte. Eine sicherheitsbezogene Anwendung, die diese Art von Fehler macht, könnte auch andere Fehler machen, also würde ich ihr nicht vertrauen.

Einfacher Interposer

Aber Sie haben nach einem anderen Weg gefragt, also hier ist 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);
}

Kompiliere das mit

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

dann führe deinen Prozess mit

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

Die Interposer-Bibliothek wird diesen Code vor dem ausführen main Funktion von Ihrer Anwendung wird ausgeführt. Es ersetzt das letzte Befehlszeilenargument durch das tatsächliche Passwort im Aufruf von main. Die Befehlszeile wie in /proc/*/cmdline (und daher von Werkzeugen wie ps) enthält jedoch immer noch das falsche Argument. Natürlich müssen Sie den Quellcode und die Bibliothek, die Sie kompilieren, nur für sich selbst lesbar machen, also arbeiten Sie am besten in einem chmod 0700 Verzeichnis. Und da das Passwort nicht Teil des Befehlsaufrufs ist, ist Ihr Bash-Verlauf ebenfalls sicher.

Fortgeschrittener Interposer

Wenn Sie etwas ausführlicher machen möchten, sollten Sie das im Hinterkopf behalten __libc_start_main wird ausgeführt, bevor die Laufzeitbibliothek ordnungsgemäß initialisiert wurde. Daher würde ich vorschlagen, Funktionsaufrufe zu vermeiden, es sei denn, sie sind absolut notwendig. Wenn Sie Funktionen nach Herzenslust aufrufen möchten, sollten Sie dies vorher tun main selbst wird aufgerufen, nachdem alle Initialisierung erfolgt ist. Für das folgende Beispiel muss ich Grubermensch danken, der darauf hingewiesen hat wie man ein Passwort versteckt, das als Befehlszeilenargument übergeben wurde was gebracht hat getpass zu meiner 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);
}

Dies fordert zur Eingabe des Passworts auf, sodass Sie die Interposer-Bibliothek nicht mehr geheim halten müssen. Das Platzhalterargument wird als Passwortaufforderung wiederverwendet, also rufen Sie dieses Like auf

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

Eine andere Alternative würde das Passwort von einem Dateideskriptor lesen (wie z.B. gpg --passphrase-fd tut) oder von x11-ssh-askpass, oder Wasauchimmer.


65
2018-05-03 19:49



Obwohl ich den Code nicht verstehe und nicht testen kann, bekomme ich den Kern davon und diese sieht wie eine tatsächliche Antwort aus und sollte die beste Antwort sein. - Mark Henderson♦
Das ist wirklich großartig. - Afri
Genial. Soweit ich das beurteilen kann, sollte das funktionieren. Natürlich brauchen Sie Zugriff auf die Quelle und können neu kompilieren. Das Passwort ist in der Quelle und der kompilierten Datei (en) lesbar, wenn Sie "Strings" oder etwas Ähnliches verwenden, also stellen Sie sicher, dass niemand anderes diese lesen kann. - Tonny
Es sollte möglich sein, das Passwort auf STDIN zu nehmen und trotzdem diese Arbeit zu haben, die das entfernt strings Verletzlichkeit. Sehen SO: Passworteingabe am Terminal ausblenden. - Grubermensch
@Grubermensch Du hast recht. - Tonny


Es ist nicht nur die Geschichte. Es wird in erscheinen ps Ausgabe auch.

Wer auch immer diese Software geschrieben hat, sollte aufgehängt, geviertelt und geviertelt werden. Es ist ein absolutes NEIN, ein Passwort in der Befehlszeile eingeben zu müssen, egal welche Software es ist.
Für einen Daemon-Prozess ist es sogar noch unverzeihlicher ...

Außerdem rm -f über die Software selbst kenne ich keine Lösung dafür. Ehrlich gesagt: Finden Sie andere Software, um die Arbeit zu erledigen. Benutze keinen solchen Müll.


28
2018-05-02 15:41



Danke, dass du überhaupt nicht hilfreich bist. Dies ist lange diskutiert Sicherheitsproblem, immer noch ungelöst und ich brauche einen besseren Workaround als rm -f jetzt. - Afri
Eigentlich ist er sehr hilfsbereit. Wenn Sie die Passphrase als Argument übergeben, wird es angezeigt ps. Bis der Entwickler das beheben kann, schlägt er vor etwas anderes zu benutzen. - Safado
Dann fängst du besser an, ein anderes Betriebssystem zu schreiben. Es gibt KEIN andere Lösung, die mir bekannt ist. Bei Gott, ich wünschte, es gäbe eins. Du bist nicht der Einzige mit diesem Problem. - Tonny
Vertoe, schnipple nicht. Sie können nach einem Weg fragen, um es auf kleinen Zetteln zu übergeben, aber das bedeutet nicht, dass es so etwas automatisch gibt. read_x ist in Ordnung, stellt aber die Passphrase weiterhin über zB psAlso ist es nicht besser als das rm Lösung. - MadHatter
Bevor Sie gehen und werfen Sie noch einmal +1 auf diese nicht-wirklich-eine-Antwort und beschweren Sie sich, dass dies unmöglich ist, schlage ich vor, Sie überprüfen MvG's Antwort unten - Mark Henderson♦


Dies wird das klären ps Ausgabe.

SEHR GEEIGNET: Dies könnte die Anwendung zerstören. Sie werden gebührend gewarnt, dass hier Drachen sind.

  • Ausländische Prozesse sollten nicht in einem Prozessgedächtnis herumspielen.
  • Wenn der Prozess für das Kennwort auf diese Region angewiesen ist, können Sie Ihre Anwendung beschädigen.
  • Dies könnte dazu führen, dass alle Arbeitsdaten in diesem Prozess beschädigt werden.
  • Das ist ein wahnsinniger Hack.

Jetzt werden Sie ordnungsgemäß über diese schrecklichen Warnungen informiert. Dies löscht die angezeigte Ausgabe in ps. Es wird weder Ihren Verlauf löschen, noch den Bash-Jobverlauf löschen (z. B. den Prozess wie ausführen) myprocess myargs &). Aber ps wird die Argumente nicht mehr anzeigen.

#!/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 es speichern, chmod +x es. Dann tun ./whatever <pidoftarget> Wenn dies funktioniert, wird keine Ausgabe erzeugt. Wenn es scheitert, wird es sich beschweren und beenden.


17
2018-05-02 22:04



. . . 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, dass so etwas wie AppArmor das fangen würde? Auch der Virenscanner könnte dies möglicherweise erfassen und Chaos verursachen, indem er den beleidigenden Account blockiert, der "root" wäre. Es gibt tatsächlich Drachen. - Tonny
@Tonny Für geschützte Domains würde SELinux dies verhindern. Ihre grundlegenden Unix-Berechtigungen (DAC) verfügen nicht über ausreichende Subjekt-Granularität, um Schutz vor diesem Verhalten bieten zu können (ermöglicht die Änderung des Prozessspeichers innerhalb derselben UID). Wie auch immer, es ist kein Fehler - es ist ein Feature. Ich glaube, das ist wie gdb kann den Speicher laufender Prozesse modifizieren (mit viel mehr chirurgischer Präzision, als ich hinzufügen könnte). - Matthew Ife


Können Sie das Argument aus einer Datei weitergeben, auf die nur root oder der erforderliche Benutzer zugreifen kann?

Es ist ein RIESIGES Nein-Nein, um Passwörter in der Konsole einzugeben, aber letzter Rückgriff ... Beginnen Sie Ihre Zeile mit einem Leerzeichen, so dass es nicht im Verlauf erscheint.


10
2018-05-02 16:33



Es gab eine Shell-Option, die es aktiviert, aber ich denke, es war nicht standardmäßig aktiviert. - heinrich5991
export HISTCONTROL=ignoreboth ignoriert sowohl Duplikate als auch Zeilen mit einem führenden Leerzeichen für den Einstieg in die Historie. Fügen Sie es zu Ihrer .bashrc oder .bash_profile hinzu. - Andreas


Vielleicht funktioniert das (?):

darkcoind masternode start `cat password.txt`

6
2018-05-02 19:31



Oder auch darkcoind masternode start `head -1`, wenn Sie das Passwort manuell eingeben möchten. - kasperd
Die Passphrase ist weiterhin über verfügbar ps und ähnliche Hilfsmittel. - voretaq7
Von einem Klartext-Passwort in .bash_history zu einem Klartext Passwort in password.txt gewinnt dich was genau? - MikeyB
@MikeyB: Es gibt einen kleinen Gewinn: Sie werden ihn nicht versehentlich beim Durchsuchen Ihrer Geschichte aussetzen, während jemand über Ihre Schulter schaut. - MvG
@MikeyB, Sie können diese Datei jedes Mal erstellen und entfernen. - RiaD


Leider, wenn Ihr darkcoind Der Befehl erwartet das Kennwort als Befehlszeilenargument und wird dann über Dienstprogramme wie z ps. Die einzige wirkliche Lösung ist es Erziehen Sie die Entwickler.

Während ps Exposition könnte unvermeidlich sein, Sie könnten zumindest verhindern, dass das Passwort in der Shell-History-Datei ausgeschrieben wird.

$ xargs darkcoind masternode start

peinsswOrd

StrgD

Die Verlaufsdatei sollte nur aufzeichnen xargs darkcoind masternode startnicht das Passwort.


3
2018-05-04 08:13



Oder wenn Sie bash verwenden, setzen Sie ignorespace im $HISTCONTROLund dann kannst du verhindern irgendein befehlen Sie, in die Shell-Historie zu gehen, indem Sie dem Befehl ein Leerzeichen voranstellen. - derobert


Sie können das Kennwort aus dem Shell-Verlauf entfernen, indem Sie den Befehl aus einem 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$

Stelle sicher sh ist konfiguriert nicht um seinen Verlauf in einer Datei zu speichern.

Andere Probleme, wie zB das Passwort, werden dadurch nicht angesprochen ps. Es gibt, glaube ich, Möglichkeiten für die darkcoind Programm selbst, um die Informationen zu verbergen ps, aber das verkürzt nur das Fenster der Verwundbarkeit.


1
2018-05-02 20:38



Die Passphrase ist weiterhin über verfügbar ps und ähnliche Hilfsmittel. - voretaq7
@ Voretaq7: Ja, wie ich ausdrücklich im letzten Absatz meiner Antwort bestätigt habe. - Keith Thompson
In der Tat - Sie waren das Opfer von mutwilligen Copypasta meinerseits :) - voretaq7