Como o curl protege uma senha de aparecer na saída do ps?

64

Eu percebi há algum tempo que nomes de usuários e senhas dados a curl como argumentos de linha de comando não aparecem em ps output (embora, é claro, possam aparecer em seu histórico bash).

Eles também não aparecem em /proc/PID/cmdline .

(O comprimento do argumento username / password combinado pode ser derivado, no entanto.)

Demonstração abaixo:

[root@localhost ~]# nc -l 80 &
[1] 3342
[root@localhost ~]# curl -u iamsam:samiam localhost &
[2] 3343
[root@localhost ~]# GET / HTTP/1.1
Authorization: Basic aWFtc2FtOnNhbWlhbQ==
User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.15.3 zlib/1.2.3 libidn/1.18 libssh2/1.4.2
Host: localhost
Accept: */*



[1]+  Stopped                 nc -l 80
[root@localhost ~]# jobs
[1]+  Stopped                 nc -l 80
[2]-  Running                 curl -u iamsam:samiam localhost &
[root@localhost ~]# ps -ef | grep curl
root      3343  3258  0 22:37 pts/1    00:00:00 curl -u               localhost
root      3347  3258  0 22:38 pts/1    00:00:00 grep curl
[root@localhost ~]# od -xa /proc/3343/cmdline 
0000000    7563    6c72    2d00    0075    2020    2020    2020    2020
          c   u   r   l nul   -   u nul  sp  sp  sp  sp  sp  sp  sp  sp
0000020    2020    2020    0020    6f6c    6163    686c    736f    0074
         sp  sp  sp  sp  sp nul   l   o   c   a   l   h   o   s   t nul
0000040
[root@localhost ~]# 

Como este efeito é alcançado? Está em algum lugar no código-fonte de curl ? (Eu suponho que é um recurso curl , não um recurso ps ? Ou é um recurso do kernel de algum tipo?)

Além disso: isso pode ser feito fora do código-fonte de um executável binário? Por exemplo: usando comandos shell, provavelmente combinados com permissões de root?

Em outras palavras, eu poderia de alguma forma mascarar um argumento de aparecer em /proc ou em ps output (mesma coisa, eu acho) que eu passei para algum comando shell arbitrário ? (Eu acho que a resposta para isso é "não", mas parece que vale a pena incluir esta meia-pergunta extra).

    
por Wildcard 11.08.2017 / 00:41

3 respostas

76

Quando o kernel executa um processo, ele copia os argumentos da linha de comando para a memória de leitura / gravação pertencente ao processo (na pilha, pelo menos no Linux). O processo pode gravar nessa memória como qualquer outra memória. Quando ps exibe o argumento, ele lê o que está armazenado naquele endereço específico na memória do processo. A maioria dos programas mantém os argumentos originais, mas é possível alterá-los. A descrição POSIX de ps afirma que

It is unspecified whether the string represented is a version of the argument list as it was passed to the command when it started, or is a version of the arguments as they may have been modified by the application. Applications cannot depend on being able to modify their argument list and having that modification be reflected in the output of ps.

A razão pela qual isso é mencionado é que a maioria das variantes unix refletem a mudança, mas as implementações POSIX em outros tipos de sistemas operacionais não.

Esse recurso é de uso limitado porque o processo não pode fazer alterações arbitrárias. No mínimo, o comprimento total dos argumentos não pode ser aumentado, porque o programa não pode alterar a localização em que ps buscará os argumentos e não poderá estender a área além de seu tamanho original. O comprimento pode efetivamente ser diminuído colocando-se bytes nulos no final, porque os argumentos são strings com terminação nula no estilo C (isto é indistinguível de ter um monte de argumentos vazios no final).

Se você realmente quer cavar, pode ver a fonte de uma implementação de código aberto. No Linux, a fonte de ps não é interessante, tudo o que você verá é que lê os argumentos da linha de comando do sistema de arquivos proc , em /proc/PID/cmdline . O código que gera o conteúdo deste arquivo está no kernel, em proc_pid_cmdline_read em fs/proc/base.c . A parte da memória do processo (acessada com access_remote_vm ) vai do endereço mm->arg_start a mm->arg_end ; esses endereços são registrados no kernel quando o processo é iniciado e não podem ser alterados posteriormente.

Alguns daemons usam essa capacidade para refletir seu status, por exemplo, eles alteram seu argv[1] para uma string como starting ou available ou exiting . Muitas variantes unix têm uma função setproctitle para fazer isso. Alguns programas usam essa capacidade para ocultar dados confidenciais. Observe que isso é de uso limitado, pois os argumentos da linha de comando ficam visíveis enquanto o processo é iniciado.

A maioria dos idiomas de alto nível copia os argumentos para objetos de string e não permite modificar o armazenamento original. Aqui está um programa em C que demonstra essa habilidade, alterando argv elementos diretamente.

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
    int i;
    system("ps -p $PPID -o args=");
    for (i = 0; i < argc; i++)
    {
        memset(argv[i], '0' + (i % 10), strlen(argv[i]));
    }
    system("ps -p $PPID -o args=");
    return 0;
}

Exemplo de saída:

./a.out hello world
0000000 11111 22222

Você pode ver a modificação de argv no código-fonte da onda. O Curl define uma função cleanarg em src/tool_paramhlp.c que é usada para alterar um argumento para todos os espaços usando memset . Em src/tool_getparam.c esta função é usada algumas vezes, e. redigindo a senha do usuário . Como a função é chamada a partir da análise de parâmetros, isso acontece no início de uma chamada de curl, mas descarregar a linha de comando antes que isso aconteça ainda mostrará todas as senhas.

Como os argumentos são armazenados na própria memória do processo, eles não podem ser alterados a partir do exterior, exceto pelo uso de um depurador.

    
por 11.08.2017 / 01:42
14

As outras respostas respondem bem à pergunta de maneira geral. Para responder especificamente " Como este efeito é alcançado? Está em algum lugar no código-fonte do curl?":

Na seção análise de argumentos do código-fonte de curl , a% A opção-u é tratada da seguinte forma:

    case 'u':
      /* user:password  */
      GetStr(&config->userpwd, nextarg);
      cleanarg(nextarg);
      break;

E a % funçãocleanarg() é definida da seguinte forma:

void cleanarg(char *str)
{
#ifdef HAVE_WRITABLE_ARGV
  /* now that GetStr has copied the contents of nextarg, wipe the next
   * argument out so that the username:password isn't displayed in the
   * system process list */
  if(str) {
    size_t len = strlen(str);
    memset(str, ' ', len);
  }
#else
  (void)str;
#endif
}

Assim, podemos ver explicitamente que o argumento nome de usuário: senha em argv é sobrescrito com espaços, conforme descrito pelas outras respostas.

    
por 12.08.2017 / 01:43
3

Um processo pode não apenas ler seus parâmetros, mas também escrevê-los.

Eu não sou um desenvolvedor, então não estou familiarizado com essas coisas, mas pode ser possível de fora com uma abordagem semelhante à alteração dos parâmetros do ambiente:

link

    
por 11.08.2017 / 00:48