¿Cómo ocultar una contraseña pasada como argumento de línea de comando?

43

Estoy ejecutando un demonio de software que requiere que ciertas acciones ingresen una frase de contraseña para desbloquear algunas características que se ven así, por ejemplo:

$ darkcoind masternode start <mypassphrase>

Ahora tengo algunas preocupaciones de seguridad en mi servidor Debian sin cabeza.

Cada vez que busco en mi historial de bash, por ejemplo Ctrl+R, puedo ver esta contraseña súper segura. Ahora imagino que mi servidor está comprometido y algún intruso tiene acceso a shell y simplemente puede Ctrl+Rencontrar mi frase de contraseña en el historial.

¿Hay una manera de introducir la contraseña sin que se muestre en la historia bash, ps, /proco en otro sitio?


Actualización 1 : Pasar ninguna contraseña al demonio arroja un error. Esta no es una opción.


Actualización 2 : No me digas que elimine el software u otras sugerencias útiles como colgar a los desarrolladores. Sé que este no es un ejemplo de mejores prácticas, pero este software está basado en bitcoin y todos los clientes basados ​​en bitcoin son algún tipo de servidor json rpc que escucha estos comandos y es un problema de seguridad conocido que aún se está discutiendo ( a , b , c ) .


Actualización 3 : el demonio ya está iniciado y ejecutándose con el comando

$ darkcoind -daemon

Hacer psmuestra solo el comando de inicio.

$ 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

Por lo que pasa a los comandos con la frase de contraseña no aparece en pso /procen absoluto.

$ 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

Esto deja la pregunta ¿dónde aparece la historia? Sólo .bash_history?

Waqar Lim
fuente
1
La primera pregunta tiene que ser: ¿qué sucede si comienzas el demonio sin el argumento de frase de contraseña? ¿Solo lo solicita?
MadHatter apoya a Monica
31
No creo que haya una respuesta que funcione. La imposibilidad de solicitar una frase de contraseña es una deficiencia importante en el demonio. Si es software libre, ingresa un programador y arréglalo; No olvides publicar tus cambios. Si es un software propietario, llame al vendedor y grítele (eso no solucionará nada, pero lo hará sentir mejor).
MadHatter apoya a Monica
44
Verifique su documentación, puede admitir la lectura de esa contraseña desde una variable de entorno del sistema.
Elliott Frisch
3
Incluso si la contraseña no se da en la línea de comando al demonio, sigue siendo problemático darla en la línea de comando de cualquier otro comando. Solo es visible en la salida ps por un tiempo muy corto, pero un proceso que se ejecuta en segundo plano aún podría recuperarlo. Pero, por supuesto, todavía vale la pena hacer que sea más difícil obtener la contraseña.
kasperd
2
Mire las respuestas a esta pregunta , se ocupan exactamente de este problema.
dotancohen

Respuestas:

68

Realmente, esto debería solucionarse en la aplicación misma. Y tales aplicaciones deben ser de código abierto, por lo que solucionar el problema en la aplicación en sí debería ser una opción. Una aplicación relacionada con la seguridad que comete este tipo de error también podría cometer otros errores, por lo que no confiaría en ella.

Intercalador simple

Pero pedías una forma diferente, así que aquí hay una:

#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 esto con

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

luego ejecuta tu proceso con

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

La biblioteca de interposición ejecutará este código antes de que se ejecute la mainfunción de su aplicación. Reemplazará el último argumento de la línea de comando por la contraseña real en la llamada a main. Sin embargo, la línea de comando impresa /proc/*/cmdline(y, por lo tanto, vista por herramientas como ps) todavía contendrá el argumento falso. Obviamente, tendría que hacer que el código fuente y la biblioteca que compila de él sean legibles solo para usted, por lo que es mejor operar en un chmod 0700directorio. Y dado que la contraseña no es parte de la invocación del comando, su historial de bash también está seguro.

Intercalador más avanzado

Si desea hacer algo más elaborado, debe tener en cuenta que __libc_start_mainse ejecuta antes de que la biblioteca de tiempo de ejecución se haya inicializado correctamente. Por lo tanto, sugeriría evitar cualquier llamada a funciones a menos que sean absolutamente esenciales. Si desea poder llamar funciones al contenido de su corazón, asegúrese de hacerlo justo antes de que mainse invoque, después de que se haya realizado toda la inicialización. Para el siguiente ejemplo, tengo que agradecer a Grubermensch que señaló cómo ocultar una contraseña pasada como argumento de línea de comando que getpassme llamó la atención.

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

Esto solicita la contraseña, por lo que ya no tendrá que mantener en secreto la biblioteca de interposición. El argumento del marcador de posición se reutiliza como solicitud de contraseña, así que invoque esto como

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

Otra alternativa sería leer la contraseña de un descriptor de archivo (como, por ejemplo, gpg --passphrase-fddoes), o de x11-ssh-askpass, o lo que sea.

MvG
fuente
44
Aunque no entiendo y no puedo probar el código, entiendo lo esencial, y esto parece una respuesta real y debería ser la respuesta principal.
Mark Henderson
Esto es realmente asombroso.
Waqar Lim
Increíble. Por lo que puedo decir, esto debería funcionar. Por supuesto, necesita acceso a la fuente y poder volver a compilar. La contraseña se puede leer en la fuente y en los archivos compilados si usa "cadenas" o algo similar, así que asegúrese de que nadie más pueda leerlas.
Tonny
1
Debería ser posible tomar la contraseña en STDIN y aún tener este trabajo, lo que elimina la stringsvulnerabilidad. Ver SO: Ocultar entrada de contraseña en el terminal .
Grubermensch
1
@ mulg0r: El estándar externo "C" debería hacer el truco de suprimir el cambio de nombre para la función relevante, a saber __libc_start_main.
MvG
28

No es solo la historia. También se mostrará en la salida ps .

Quien escribió esa pieza de software debe ser colgado, dibujado y descuartizado. Es un NO absoluto tener que proporcionar una contraseña en la línea de comandos independientemente del software que sea.
Para un proceso de demonio es aún MÁS imperdonable ...

Además de rm -f en el software en sí, no conozco ninguna solución para esto. Honestamente: encuentre otro software para hacer el trabajo. No uses tanta basura.

Tonny
fuente
99
Gracias por no ser de ayuda en absoluto. Este es un problema de seguridad discutido durante mucho tiempo , aún no resuelto y necesito una mejor solución que rm -fahora.
Waqar Lim
17
En realidad, él está siendo muy útil. Si está pasando la frase de contraseña como argumento, aparecerá en ps. Entonces, hasta que el desarrollador pueda arreglar eso, sugiere usar otra cosa.
Safado
3
Entonces será mejor que empieces a escribir otro sistema operativo. No hay otra solución disponible actualmente que yo sepa. Por Dios, desearía que hubiera uno. No eres el único con este problema.
Tonny
8
Vertoe, no te pongas nervioso. Puede solicitar una forma de pasarlo en pequeños pedazos de papel, pero eso no significa que exista tal forma automáticamente. read_x está bien, pero aún expone la frase de contraseña, por ejemplo ps, por lo que no es mejor que la rmsolución.
MadHatter apoya a Monica
77
Antes de ir y lanzar otro +1 en esta no-realmente-respuesta y quejarse de que esto es imposible, le sugiero que revise la respuesta de MvG a continuación
Mark Henderson
19

Esto borrará la pssalida.

Tenga mucho cuidado : esto podría romper la aplicación. Estás debidamente advertido de que aquí hay dragones.

  • Los procesos extraños no deberían estar jugando en una memoria de procesos.
  • Si el proceso se basa en esta región para la contraseña, puede romper su aplicación.
  • Hacer esto podría corromper cualquier dato de trabajo que tenga en ese proceso.
  • Este es un truco loco.

Ahora se le notifica debidamente de estas advertencias nefastas. Esto borrará la salida que se muestra en ps. No borrará su historial, ni borrará el historial de trabajo bash (como ejecutar el proceso como myprocess myargs &). Pero psya no mostrará los argumentos.

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

Invoque el programa guardándolo chmod +x. Luego, ./whatever <pidoftarget> si esto funciona, no producirá salida. Si falla, se quejará de algo y renunciará.

Matthew Ife
fuente
18
. . . Esto es a la vez creativo y aterrador.
voretaq7
EEK! Ahora estoy asustado.
Janne Pikkarainen
Yikkes, eso podría funcionar ... ¿No estoy seguro de que algo como AppArmor capte esto? Además, el virusscanner podría atrapar esto y causar estragos al bloquear la cuenta infractora que sería 'root'. Habrá dragones de hecho ...
Tonny
@Tonny Para dominios protegidos, SELinux evitaría esto. Sus permisos básicos de Unix (DAC) carecen de suficiente granularidad de sujeto para ofrecer protección contra este comportamiento (permite la modificación de la memoria de procesos dentro del mismo UID). De todos modos, no es un error, es una característica. Creo que así es como se gdbpuede modificar la memoria de los procesos en ejecución (con mucha más precisión quirúrgica que esta podría agregar).
Matthew Ife
11

¿Puede pasar el argumento desde un archivo, accesible solo por root o por el usuario requerido?

Es un ENORME no-no escribir contraseñas en la consola, pero el último recurso ... comience su línea con un espacio para que no aparezca en el historial.

vn.
fuente
Había una opción de shell que lo habilita, pero creo que no estaba habilitado por defecto.
heinrich5991
export HISTCONTROL=ignorebothignora tanto los duplicados como las líneas con un espacio inicial para ingresar al historial. Agréguelo a su .bashrc o .bash_profile.
Andreas
7

Quizás esto funciona (?):

darkcoind masternode start `cat password.txt`
Daniele Testa
fuente
3
O incluso darkcoind masternode start `head -1`, si desea ingresar la contraseña manualmente.
Kasperd
14
La frase de contraseña todavía está disponible a través de psutilidades similares.
voretaq7
1
Pasar de una contraseña de texto sin formato .bash_historya una contraseña de texto sin formato le permite ganar password.txt¿qué, exactamente?
MikeyB
1
@MikeyB: Hay una pequeña victoria: no la expondrás accidentalmente mientras buscas en tu historial mientras alguien mira por encima de tu hombro.
MvG
1
@MikeyB, puede crear y eliminar ese archivo cada vez.
RiaD
4

Desafortunadamente, si su darkcoindcomando espera la contraseña como un argumento de línea de comando, se expondrá a través de utilidades como ps. La única solución real es educar a los desarrolladores .

Si bien la psexposición puede ser inevitable, al menos podría evitar que la contraseña se escriba en el archivo del historial del shell.

$ xargs darkcoind masternode start

password

CtrlD

El archivo de historial solo debe registrar xargs darkcoind masternode start, no la contraseña.

200_success
fuente
2
O si usted está utilizando bash, poner ignorespaceen $HISTCONTROL, y luego se puede evitar que cualquier comando de entrar en la historia cáscara anteponiendo el comando con un espacio.
derobert
3

Como han dicho otros, busque en su control de historial de shell para ocultar la información del historial.

Pero una cosa que nadie parece haber sugerido todavía es montar /proccon el hidepidparámetro. Intente modificar su /proclínea /etc/fstabpara incluir hidepid, de esta manera:

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

Puede mantener la contraseña fuera del historial de su shell ejecutando el comando desde un nuevo proceso de shell, que luego termina inmediatamente. Por ejemplo:

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

Asegúrese de que shesté configurado para no guardar su historial en un archivo.

Por supuesto, esto no soluciona los otros problemas, como que la contraseña esté visible en ps. Creo que hay formas en que el darkcoindprograma en sí mismo puede ocultar la información ps, pero eso solo acorta la ventana de vulnerabilidad.

Keith Thompson
fuente
1
la frase de contraseña todavía está disponible a través de psutilidades similares.
voretaq7
3
@ voretaq7: Sí, como reconocí explícitamente en el último párrafo de mi respuesta.
Keith Thompson,
3
De hecho, fuiste víctima de una copia sin sentido de mi parte :)
voretaq7
2

Para Bitcoin, la respuesta oficial del desarrollador es usar el contenedor de python proporcionado en contrib/bitrpc/bitrpc.py( github ):

Pide una contraseña de forma segura si utiliza el comando walletpassphrase, por ejemplo. No hay planes para agregar funcionalidad interactiva bitcoin-cli.

y:

bitcoin-cli permanecerá tal cual y no obtendrá funcionalidad interactiva.

Fuente: # 2318

Desbloquear billetera:

$ python bitrpc.py walletpassphrase

Cambiar frase de contraseña:

$ python bitrpc.py walletpassphrasechange

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

Para darkcoin funciona un diálogo:

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

Waqar Lim
fuente