изменить / proc / PID / environment после запуска процесса


Ответы:


12

В Linux вы можете перезаписать значение строк окружения в стеке.

Таким образом, вы можете скрыть запись, переписав ее нулями или чем-то еще:

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

Беги как:

$ 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

k=vбыл перезаписан \0\0\0.

Обратите внимание, что setenv("k", "", 1)перезапись значения не будет работать, так как в этом случае выделяется новая "k="строка.

Если вы не изменили kпеременную окружения с помощью setenv()/ putenv(), то вы также должны сделать что-то вроде этого, чтобы получить адрес k=vстроки в стеке (ну, одного из них):

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

Обратите внимание, что он удаляет только одну из k=vзаписей, полученных в среде. Обычно есть только один, но ничто не мешает кому-либо передать оба k=v1и k=v2(или k=vдва раза) в список env, переданный execve(). Это было причиной уязвимостей в прошлом, таких как CVE-2016-2381 . Это может действительно произойти bashдо «шеллшока» при экспорте переменной и функции с одним и тем же именем.

В любом случае всегда будет маленькое окно, в течение которого строка env var еще не была переопределена, поэтому вы можете захотеть найти другой способ передачи секретной информации в команду (например, конвейер), если вы предоставляете ее через /proc/pid/environэто проблема.

Также обратите внимание, что, в отличие от /proc/pid/cmdline, /proc/pid/environmentдоступен только процессам с одинаковым euid или root (или root только если euid и ruid процесса не совпадают).

Вы можете скрыть это значение от них /proc/pid/environ, но они все равно смогут получить любую другую копию строки, которую вы сделали, например, подключив к ней отладчик.

См. Https://www.kernel.org/doc/Documentation/security/Yama.txt, чтобы узнать, как это сделать, по крайней мере, пользователям без полномочий root.


8

Это не было необходимо переписать строки выше ( на самом деле не на ) стека основного потока на Linux с 2010 года.

Оба /proc/self/cmdlineи /proc/self/environмогут быть изменены самим процессом во время выполнения, путем вызова prctl()функции с соответственно PR_SET_MM_ARG_START+ PR_SET_MM_ARG_ENDили PR_SET_MM_ENV_START+ PR_SET_MM_ENV_END. Они непосредственно устанавливают указатели памяти в пространство памяти приложения процесса, которое хранится в ядре для каждого процесса, которые используются для извлечения содержимого /proc/${PID}/cmdlineи /proc/${PID}/environ, и, следовательно, командной строки и среды, сообщаемых psкомандой.

Поэтому нужно просто создать новый аргумент или строку окружения (не вектор, обратите внимание - указанная память должна быть фактическими строковыми данными, объединенными и -delimited) и сообщить ядру, где оно находится.

Это задокументировано на странице руководства Linux для этой prctl(2)функции, а также на environ(7)странице руководства. Что не задокументировано, так это то, что ядро ​​отклоняет любую попытку установить начальный адрес выше конечного адреса или конечный адрес ниже начального адреса; или (пере) установить любой адрес на ноль. Кроме того, это не оригинальный механизм, предложенный Брайаном Донланом в 2009 году, который позволял устанавливать начало и конец одной операции, атомарно. Более того, ядро ​​не предоставляет способа получить текущие значения этих указателей.

Это затрудняет изменение среды и областей командной строки с помощью prctl(). Необходимо вызывать prctl()функцию до четырех раз, потому что первые попытки могут привести к попыткам установить начальный указатель выше конечного указателя, в зависимости от того, где старые и новые данные находятся в памяти. Нужно вызывать его еще четыре раза, если вы хотите убедиться, что это не приведет к тому, что другие процессы в системе получат возможность проверить произвольный диапазон пространства памяти процесса в период, когда начинается новый запуск. был установлен, но новый конец / начало не было.

Один атомарный системный вызов, который устанавливает весь диапазон за один раз, был бы намного проще для прикладных программ в безопасном использовании.

Еще одна проблема заключается в том, что без веской причины (учитывая проверки в ядре, перезаписываемость исходных областей данных в любом случае и тот факт, что эквиваленты не являются привилегированными операциями ни на одной из BSD), в Linux это требует суперпользователя. привилегии.

Я написал довольно простые setprocargv()и setprocenvv()функции для своих наборов инструментов, которые используют это. Программы с цепочечной загрузкой из наборов инструментов, которые являются встроенными, например, setenvи foreground, таким образом, отражают аргументы цепочки команд и среду, в которой это позволяет Linux.

# / package / admin / nosh / command / clearenv setenv WIBBLE колебание, передний план, пауза \; правда &
[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 |.;. Верно. |
00000018
# hexdump -C / proc / 1057 / Environment
00000000 57 49 42 42 4c 45 3d 77 6f 62 62 6c 65 00 | WIBBLE = колебание. |
0000000e
# hexdump -C / proc / 1058 / cmdline
00000000 70 61 75 73 65 00 | пауза. |
00000006
# hexdump -C / proc / 1058 / Environment
00000000 57 49 42 42 4c 45 3d 77 6f 62 62 6c 65 00 | WIBBLE = колебание. |
0000000e
# 

Обратите внимание, что это не препятствует вещам, которые отслеживают процесс и получают доступ к его памяти напрямую другими способами (а не через эти два псевдофайла), и, конечно, оставляет окно перед изменением строк, где эта информация может быть видна, просто как и при перезаписи данных над стеком основного потока. И так же, как в случае с перезаписью данных, это не учитывает библиотеки времени выполнения, которые делают копии среды (в куче) при различных обстоятельствах. В общем, не рассматривайте это как такой же хороший механизм для передачи «секретов» программе, как (скажем), когда она наследует дескриптор открытого файла в конец чтения безымянного канала, считываемого во входной буфер полностью под вашим контролем что вы потом вытрите.

дальнейшее чтение


2
Начиная с ядра 3.18, можно использовать PR_SET_MM_MAP, который принимает struct prctl_mm_map и не требует root.
filbranden

2
JdeBP, @filbranden Начиная с ядра 3.5 вы можете прочитать текущие значения окр / ARGV указатели с /proc/$pid/stat(помимо других значений , которые вы , возможно , потребуется в struct prctl_mm_map). Смотрите также мой пример filter_env.c для небольшой демонстрации. JdeBP, вы можете добавить ссылки на ваши setprocargv()/ setprocenvv()функции?
maxschlepzig
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.