alterar / proc / PID / ambiente após o início do processo

11
$ k=v p &
[1] 3028

existe alguma maneira de palterar o conteúdo de /proc/3028/environnão mencionar k=v enquanto p ainda está em execução?

Cetin Sert
fonte
Você tentou editar o arquivo?
123
Que arquivo você está se perguntando?
ctrl-alt-delor 31/07

Respostas:

12

No Linux, você pode sobrescrever o valor das seqüências de caracteres do ambiente na pilha.

Para ocultar a entrada, substitua-a por zeros ou qualquer outra coisa:

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char* argv[], char* envp[]) {
  char cmd[100];

  while (*envp) {
    if (strncmp(*envp, "k=", 2) == 0)
      memset(*envp, 0, strlen(*envp));

    envp++;
  }

  sprintf(cmd, "cat /proc/%u/environ", getpid());

  system(cmd);
  return 0;
}

Correr como:

$ env -i a=foo k=v b=bar ./wipe-env | hd
00000000  61 3d 66 6f 6f 00 00 00  00 00 62 3d 62 61 72 00  |a=foo.....b=bar.|
00000010

o k=vfoi substituído por \0\0\0.

Observe que, setenv("k", "", 1)para substituir o valor, não funcionará, pois, nesse caso, uma nova "k="string é alocada.

Se você não modificou a kvariável de ambiente com setenv()/ putenv(), também poderá fazer algo assim para obter o endereço da k=vstring na pilha (bem, de uma delas):

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


int main(int argc, char* argv[]) {
  char cmd[100];
  char *e = getenv("k");

  if (e) {
    e -= strlen("k=");
    memset(e, 0, strlen(e));
  }

  sprintf(cmd, "cat /proc/%u/environ", getpid());

  system(cmd);
  return 0;
}

Observe, no entanto, que ele remove apenas uma das k=ventradas recebidas no ambiente. Geralmente, há apenas um, mas nada impede que alguém passe ambos k=v1e k=v2(ou k=vduas vezes) na lista env enviada para execve(). Essa foi a causa de vulnerabilidades de segurança no passado, como o CVE-2016-2381 . Isso poderia realmente acontecer bashantes do shellshock ao exportar uma variável e uma função com o mesmo nome.

De qualquer forma, sempre haverá uma pequena janela durante a qual a cadeia env var ainda não foi substituída, portanto, você pode querer encontrar outra maneira de passar as informações secretas para o comando (como um canal por exemplo), se for exposto via /proc/pid/environé uma preocupação.

Observe também que /proc/pid/cmdline, ao contrário de , /proc/pid/environmentsó é acessível por processos com o mesmo euid ou raiz (ou raiz somente se o euid e o ruid do processo não forem os mesmos que parecem).

Você pode ocultar esse valor deles /proc/pid/environ, mas eles ainda podem obter qualquer outra cópia que você tenha feito da string na memória, por exemplo, anexando um depurador a ela.

Consulte https://www.kernel.org/doc/Documentation/security/Yama.txt para obter formas de impedir que pelo menos usuários não root façam isso.

Stéphane Chazelas
fonte
8

Não é necessário sobrescrever as strings acima ( na verdade não estão ) a pilha do thread principal no Linux desde 2010.

Ambos /proc/self/cmdlinee /proc/self/environsão modificáveis ​​pelo próprio processo em tempo de execução, por chamar a prctl()função com respectivamente PR_SET_MM_ARG_START+ PR_SET_MM_ARG_ENDou PR_SET_MM_ENV_START+ PR_SET_MM_ENV_END. Eles configuram diretamente os ponteiros de memória no espaço de memória do aplicativo do processo, mantido pelo kernel para cada processo, usado para recuperar o conteúdo /proc/${PID}/cmdlinee /proc/${PID}/environ, portanto, a linha de comando e o ambiente relatados pelo pscomando.

Portanto, basta construir um novo argumento ou string de ambiente (não vetor, observe - a memória apontada deve ser os dados reais da string, concatenados e delimitados) e informar ao kernel onde está.

Isso está documentado na página de manual do Linux para a prctl(2)função e também na environ(7)página de manual. O que não está documentado é que o kernel rejeita qualquer tentativa de definir o endereço inicial acima do endereço final ou o endereço final abaixo do endereço inicial; ou para (re) definir um endereço para zero. Além disso, esse não é o mecanismo original proposto por Bryan Donlan em 2009, que permitiu definir atomicamente o início e o fim de uma única operação. Além disso, o kernel não fornece como obter os valores atuais desses ponteiros.

Isso torna difícil modificar o ambiente e as áreas da linha de comando prctl(). É necessário chamar a prctl()função até quatro vezes, porque as primeiras tentativas podem resultar em tentativas de definir o ponteiro inicial mais alto que o ponteiro final, dependendo de onde os dados antigos e novos estão na memória. É preciso chamá-lo mais quatro vezes se quiser garantir que isso não resulte em uma janela de oportunidade para outros processos no sistema inspecionarem um intervalo arbitrário do espaço de memória do processo no período em que o novo início / fim foi definido, mas o novo final / início não foi.

Uma única chamada de sistema atômico que define todo o intervalo de uma só vez seria muito mais fácil para os aplicativos usarem com segurança.

Outra desvantagem é que, por nenhuma razão realmente boa (dadas as verificações no kernel, a sobregravabilidade das áreas de dados originais de qualquer maneira e o fato de os equivalentes não serem operações privilegiadas em nenhum dos BSDs), no Linux isso requer superusuário privilégios.

Eu escrevi bastante simples setprocargv()e setprocenvv()funções para meus conjuntos de ferramentas, que empregam isso. Os programas de carregamento em cadeia dos conjuntos de ferramentas integrados, como setenve foreground, refletem os argumentos e o ambiente encadeados para o comando, onde o Linux permite.

# / package / admin / nosh / command / clearenv setenv WIBBLE oscila primeiro plano \; verdade &
[1] 1057
# hexdump -C / proc / 1057 / cmdline
00000000 66 6f 72 65 67 72 6f 75 6e 64 00 70 61 75 73 65 | foreground.pause |
00000010 00 3b 00 74 72 75 65 00 |.;. True. |
00000018
# hexdump -C / proc / 1057 / environ
00000000 57 49 42 42 4c 45 3d 77 6f 62 62 6c 65 00 | WIBBLE = oscilação. |
0000000e
# hexdump -C / proc / 1058 / cmdline
00000000 70 61 75 73 65 00 | pausa.
00000006
# hexdump -C / proc / 1058 / environ
00000000 57 49 42 42 4c 45 3d 77 6f 62 62 6c 65 00 | WIBBLE = oscilação. |
0000000e
# 

Observe que isso não milita contra coisas que rastreiam o processo e acessam sua memória diretamente por outros meios (e não através desses dois pseudo-arquivos) e, é claro, deixa uma janela antes que as strings sejam modificadas, onde essas informações podem ser vistas, apenas como sobrescrever os dados acima da pilha do encadeamento principal. E, como é o caso da substituição dos dados, isso não leva em consideração as bibliotecas de tempo de execução de idiomas que fazem cópias do ambiente (na pilha) em várias circunstâncias. Em geral, não considere isso um mecanismo tão bom para passar "segredos" para um programa quanto (digamos) tê-lo herdado um descritor de arquivo aberto para o final de leitura de um canal sem nome, lido em um buffer de entrada totalmente sob seu controle que você então limpa.

Leitura adicional

JdeBP
fonte
2
Desde o kernel 3.18, é possível usar o PR_SET_MM_MAP, que utiliza um struct prctl_mm_map e não requer raiz.
Filbranden #
2
JdeBP, @filbranden Desde do kernel 3.5 você pode ler os valores atuais do env / argv ponteiros de /proc/$pid/stat(além de outros valores que você pode precisar em struct prctl_mm_map). Veja também meu exemplo filter_env.c para uma pequena demonstração. JdeBP, você pode adicionar links para suas setprocargv()/ setprocenvv()funções?
maxschlepzig 6/02