Статья Скрытый запуск через модификацию окружения

1770409298359.webp

Зачем?​

Тот, кто читает эти строки, наверняка уже не раз слышал скрипт кидди, задорно кричащего: «У меня рут!». Он тыкает в терминал sudo su - и считает, что достиг просветления, что перед ним разверзлись небеса вычислительной вселенной и все архангелы-демоны отныне покорны его воле. Сию секунду, с наскока, через призму одного лишь привилегированного доступа, мир действительно кажется простым. Но это иллюзия, мимолетная и опасная.

Ты заходишь на заброшенный, покрытый цифровым мхом форум. Не тот, что сверкает Material Design и просит куки, а тот, что пахнет статикой моноширинного шрифта, где подписи в тредах- это не смайлики, а PGP-ключи и цитаты из «Степеней Свободы». Здесь говорят не о «взломе», а о понимании. Не о «руте», а о контроле. И контроль этот редко лежит на поверхности. Чаще всего он спрятан в механиках, которые старше многих из нас, в фундаментальных договоренностях между ядром и процессом, о которых не пишут в мануалах по DevOps.

Сегодня мы говорим об одном из таких фундаментальных, почти метафизических, понятий - переменных окружения процесса. И не просто говорим, а разбираем, как их скрытая модификация в родительском процессе может стать ключом к выполнению произвольного кода в контексте дочернего. Это не очередной CVE-сплойт, не троян в стиле Голливуда. Это тихая, почти невидимая манипуляция самой средой обитания программы. Если процессы - это живые организмы, то переменные окружения - это воздух, которым они дышат, гравитация, которая их держит, и невидимые химические сигналы, управляющие их поведением. А что, если мы научимся менять состав этого воздуха для еще не рожденных процессов?

Зачем это все, если есть sudo? - спросит тот самый скрипт-кидди.
А затем, что sudo -это всего лишь один из стражей у самых видимых ворот. Мир UNIX-подобных систем (а значит, и всего современного интернета, облаков и телефонов в твоем кармане) построен на идее наследования. Процесс-отец порождает процесс-сына и передает ему часть своего контекста. Самый очевидный кусочек этого контекста -переменные окружения. И эта передача часто происходит мимо всех систем контроля доступа, политик SELinux и мониторов целостности. Потому что это не действие, это - состояние. Система не «разрешает» или «запрещает» передачу переменной LD_PRELOAD. Она просто передает ее, потому что так задумано. Это кровь, текущая по артериям системы. И наша задача - научиться вводить в эту кровь нужные нам «вещества», которые изменят поведение «органов», которые ее получат.

Представь себе сцену.
Ты -ограниченный пользователь на сервере. sudo тебе не светит. su -тем более. Но ты обнаруживашь, что раз в минуту от имени системного демона (скажем, ntpd или redis) запускается какой-то скрипт для проверки логов. Скрипт этот -бинарный, запакованный, отладочная информация стерта. Трогать его -значит вызвать срабатывание HIDS (Host-based Intrusion Detection System). Но ты замечаешь, что демон, запускающий этот скрипт, берет свою конфигурацию (включая пути к библиотекам) из переменной окружения, которую сам читает из файла /etc/default/ntpd. А права на этот файл… О, чудо! Они стоят -rw-rw-r--, и ты в группе, которая может писать в него! Это не уязвимость в классическом понимании. Это неверная конфигурация. И она в тысячу раз ценнее, потому что эксплуатируется тихо, без падения сервисов, без segfault-ов в логах. Ты не атакуешь код. Ты атакуешь намерение администратора, его предположение о безопасности.

Вот о таких атаках на намерение и контекст мы и будем говорить. Это высший пилотаж. Когда ты перестаешь искать дыры в стенах и начинаешь искать способ убедить стражника, что ты - свой, что твои инструкции - это и есть приказы командира.

Что тебя ждет?
Мы не просто пробежимся по LD_PRELOAD. Мы погрузимся в:
  1. Глубины ядра: Как на самом деле выглядит вызов execve() и где в памяти процесса прячется указатель envp.
  2. Психологию демонов: Как различные сервисы (systemd, cron, супервизоры) управляют окружением своих детей и где оставляют бреши.
  3. Арсенал инструментов: От прямолинейного gdb и библиотек для инъекции через ptrace до хитрого использования fork() с задержкой и race condition атак на /proc/[pid]/environ.
  4. Оборону: Как современные системы (контейнеры, песочницы, мандатные контроля) пытаются заблокировать эти векторы и где проскальзывают их философские противоречия.
  5. Полноценную симуляцию: Мы построим в виртуальной машине мини-лабораторию: уязвимый демон, неправильно сконфигурированные права, и шаг за шагом, как на шахматной доске, проведем атаку от разведки до выполнения кода.
Эта статья - не готовая инструкция для хаоса. Это учебник по системному мышлению. Прочитав и проработав ее, ты станешь смотреть на любой запущенный процесс не как на черный ящик, а как на существо со своей родословной, унаследовавшей черты от целой династии предков. Ты научишься читать его «ДНК» - переменные окружения - и понимать, какую историю они в себе несут и как эту историю можно… переписать.

И помни главное правило нашего цеха: Сила обязывает. Знание, которое ты получишь, - это ответственность. Используй его, чтобы укреплять, а не крушить. Чтобы делать сети надежнее, код - чище, а системы - понятнее для тех, кто придет после тебя.

Предупреждение (самое главное): Всё, что описано ниже, - это исследование механизмов операционной системы. Применение этого в чужих системах без явного разрешения - преступление. Ты не станешь крутым хакером, сломав чужой сервер. Ты станешь крутым, ПОНЯВ, как он работает, и построив что-то своё, более надёжное. Запомни это.


Основа основ. Что такое окружение и кто его носит?

1.1. Душа процесса: argc, argv[] и envp[]

Когда ты в консоли пишешь ls -la /home, происходит примерно следующее:
  1. Твой шелл (bash, zsh, fish) - это процесс-родитель. Он вызывает системный вызов fork(). Рождается процесс-ребёнок, почти полная копия родителя.
  2. В этом ребёнке вызывается execve("/bin/ls", ["ls", "-la", "/home"], environ).
  • Первый аргумент - путь к исполняемому файлу.
  • Второй - массив строк аргументов (то, что ты видишь в argv в Си-программе).
  • Третий - тот самый массив переменных окружения. Указатель на него передаётся в новый процесс при его создании.
Environ - это не магия. Это просто массив строк в формате КЛЮЧ=значение:

Код:
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOME=/home/shadow_mason
USER=shadow_mason
SHELL=/bin/bash
TERM=xterm-256color
И так далее. Заканчивается массив NULL-указателем.

Ключевой момент №1: Этот массив передаётся по наследству. Дочерний процесс получает копию окружения родителя. Именно копию. Изменения в ребёнке не затрагивают родителя. Но! А если мы залезем в память родителя и изменим его окружение до того, как он создаст ребёнка?

1.2. Где живёт окружение? /proc/[pid]/environ

В Linux всё есть файл. А если не файл, то процесс. Окружение любого процесса лежит в виртуальном файле /proc/[pid]/environ. Это не настоящий файл на диске. Это окно, предоставленное ядром, в память процесса.

Bash:
# Посмотрим на окружение своего шелла:
echo $$ # Узнаем PID текущего шелла (допустим, 1234)
sudo cat /proc/1234/environ | tr '\0' '\n' # Заменяем нулевые байты на переносы строк

Ты увидишь свою текущую среду. Чисто, прозрачно.

Файл /proc/[pid]/environ доступен на чтение только владельцу процесса и руту. Это важное ограничение. Мы не можем просто так взять и прочитать окружение процесса другого пользователя. Но если мы уже получили привилегии (или работаем в контексте своего процесса/родителя), это наша отправная точка.

1.3. Родители и дети. Кто кого порождает?

Система - это дерево. init или systemd (PID 1) - корень. Всё растёт из него. Когда процесс делает fork() + execve(), он передаёт новому процессу свою среду. Это фундаментальный механизм.

Представь сценарий:
  1. Есть системный демон, работающий от рута. Допустим, cron.
  2. Он запускает скрипт из /etc/cron.hourly/.
  3. По умолчанию, скрипт получает очень скудное, безопасное окружение. Часто только PATH да HOME. Это называется санитизацией окружения.
  4. Но что, если мы можем до момента запуска скрипта изменить окружение самого демона cron? Теоретически, скрипт унаследует уже нашу, модифицированную среду.
Вот тут мы подходим к сути. Прямое изменение окружения произвольного процесса - операция нетривиальная и требует глубокого вмешательства. Но есть обходные, элегантные пути.


Практическое оружие. От теории к инъекции.

2.1. Классика жанра: LD_PRELOAD - твой первый друг и учитель

Это самая известная переменная окружения в хакерском мире. Динамический линковщик ld-linux.so, который загружает разделяемые библиотеки (*.so) для программы, смотрит на эту переменную. Если LD_PRELOAD содержит путь к библиотеке, эта библиотека будет загружена ПЕРВОЙ и получит приоритет над всеми другими.

Что это даёт? Возможность перехватывать вызовы стандартных библиотечных функций (libc): strcmp, fopen, getuid, да чего угодно.

Практический инструмент №1: Создание простейшей библиотеки для LD_PRELOAD.

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

inject_strcmp.c:

C:
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <dlfcn.h>

// Тип оригинальной функции
int (*original_strcmp)(const char *, const char *);

// Наша подмена
int strcmp(const char *s1, const char *s2) {
    // Находим оригинальную функцию при первом вызове
    if(!original_strcmp) {
        original_strcmp = dlsym(RTLD_NEXT, "strcmp");
    }

    // Логируем вызов
    FILE *f = fopen("/tmp/strcmp_log.txt", "a");
    if(f) {
        fprintf(f, "[%s] strcmp(\"%s\", \"%s\")\n",
                program_invocation_short_name, s1, s2);
        fclose(f);
    }

    // Вызываем оригинальную функцию
    return original_strcmp(s1, s2);
}

Собираем:

Bash:
gcc -shared -fPIC -o libinject_strcmp.so inject_strcmp.c -ldl
Применяем:

Bash:
# Классический способ - задать в окружении до запуска программы
LD_PRELOAD=/path/to/libinject_strcmp.so ls -la

После этого запуска ls, все вызовы strcmp (а их там много для сравнения имён файлов) попадут в /tmp/strcmp_log.txt.

А теперь главный вопрос: А если программа, которую мы хотим "обогатить", запускается не нами напрямую, а, например, тем же демоном cron? Нам нужно, чтобы LD_PRELOAD был в окружении родительского процесса (cron) в момент запуска целевой задачи.

Ограничение: LD_PRELOAD - мощная, но грубая сила. Многие SUID/SGID-бинарные файлы (те, что меняют эффективного пользователя, типа passwd) игнорируют LD_PRELOAD из соображений безопасности. Линковщик сбрасывает эту переменную при обнаружении бита setuid. Также многие системы используют selinux или apparmor, которые могут блокировать такое вмешательство.

2.2. Более тонкий подход: LD_LIBRARY_PATH и подмена библиотек

Если LD_PRELOAD - это штурмовой молот, то манипуляция LD_LIBRARY_PATH - это отмычка. Эта переменная говорит линковщику, в каких каталогах искать разделяемые библиотеки помимо стандартных путей (/lib, /usr/lib).

Сценарий:
  1. Есть программа, которая использует какую-то специфическую библиотеку libcustom.so.1.
  2. Мы знаем, что она ищет её по стандартному пути /usr/lib.
  3. Мы создаём каталог /tmp/fake_lib, кладём туда свою версию libcustom.so.1 с полезной нагрузкой.
  4. Мы модифицируем окружение родительского процесса так, чтобы LD_LIBRARY_PATH=/tmp/fake_lib:$LD_LIBRARY_PATH.
  5. Родитель запускает целевую программу -> программа загружает НАШУ библиотеку.
Это уже ближе к real-world атакам, типа подмены libc или библиотек, используемых в скриптах (например, через Python с его PYTHONPATH).

2.3. Неочевидные переменные: от GET до PS1

Окружение - это не только для линковщика. Это общая среда передачи данных.
  • PYTHONPATH / PERLLIB / RUBYLIB / GEM_PATH: Аналоги LD_LIBRARY_PATH для интерпретаторов. Подменив их, можно заставить скрипт загружать ваш модуль.
  • BASH_ENV: Если bash запускается неинтерактивно (как в скрипте), и эта переменная установлена, bash выполнит команды из указанного файла до запуска основного скрипта. Огромная дыра, если шелл-скрипты вызываются из уязвимого контекста.
  • PS1, PROMPT_COMMAND (bash): Казалось бы, просто настройка приглашения командной строки. Но PROMPT_COMMAND - это команда, которая выполняется ПЕРЕД каждым выводом приглашения. Если ты каким-то чудом можешь вписать туда что-то в окружение системного процесса... это будет выполняться постоянно. Экзотично, но бывало.
  • Специфичные для программ: Многие программы считывают конфигурацию из переменных. EDITOR=vim, HTTP_PROXY, TMPDIR. Изменение TMPDIR может перенаправить создание временных файлов в контролируемую нами директорию, что полезно для race condition атак.

Методы модификации. Как залезть в карман родителю?

Вот мы и подошли к самому сложному и интересному. У нас есть целевой родительский процесс (демон, служба). Мы хотим изменить его окружение до того, как он породит интересного нам ребёнка. Как?

3.1. Способ 1: Прямой доступ через /proc/[pid]/mem (путь самурая)

Файл /proc/[pid]/mem - это, грубо говоря, прямая проекция всей памяти процесса. Теоретически, зная адрес, по которому расположен массив envp[] в целевом процессе, мы можем записать туда новые строки.

Почти невозможно на практике. Почему?
  1. Нужны права рута (или мандат CAP_SYS_PTRACE).
  2. Нужно остановить процесс (ptrace(PTRACE_ATTACH, ...)), иначе данные могут быть несогласованными.
  3. Нужно найти адрес переменной environ в целевом процессе. Это требует анализа его памяти, поиска указателей, знания карты памяти (memory map). Это архисложная задача для запущенного, работающего бинарного файла без символов (strip).
  4. Даже если нашли, нужно корректно записать новые строки, не сломав выделенную память (heap/stack). Риск segmentation fault и краха процесса.
Вывод: Этот способ - для создания PoC (Proof of Concept) в контролируемой лаборатории, для понимания глубины системных механизмов. Для реального применения - слишком шумный, сложный и ненадёжный.

3.2. Способ 2: Использование ptrace() - отладчик как оружие

Ptrace - системный вызов для отладки. С его помощью один процесс может наблюдать и управлять выполнением другого: читать/писать регистры, память, перехватывать системные вызовы.

Идея: Приаттачиться к целевому родительскому процессу, дождаться момента, когда он будет готовиться к fork()/execve(), и в этот момент подменить аргументы (в том числе указатель на окружение) системного вызова.

Практический инструмент №2: Скрипт на основе strace + gdb (концепт).

Полноценный код для ptrace-инжектирования окружения - это сотни строк на C. Но есть утилиты, которые делают часть работы.
  1. Находим PID родителя.
  2. Используем gdb (GNU Debugger) для интерактивного вмешательства.

Bash:
sudo gdb -p <PARENT_PID>
(gdb) call putenv("LD_PRELOAD=/tmp/malicious.so")
(gdb) continue

Команда putenv добавит переменную в окружение отлаживаемого процесса.

Ограничения:
  • Требует прав отладчика (обычно рут).
  • Процесс будет остановлен на время работы gdb.
  • Многие демоны защищены от ptrace (например, через yama.ptrace_scope в современных ядрах Linux).
  • Слишком интерактивно, не для скрытого внедрения.

3.3. Способ 3: Атака через общие ресурсы (Inheritance Attack) - самый элегантный и практичный

Вернёмся к основам. Окружение наследуется. Ключевой вопрос: а откуда родительский процесс сам получает своё окружение?

Варианты:
  1. Из своего родителя. Это длинная цепочка. Если мы можем повлиять на раннего предка (например, на сессию пользователя, под которой запускается демон), то изменение дойдёт по цепочке.
  2. Из файлов конфигурации. Многие демоны читают переменные окружения из файлов типа /etc/default/название-демона или ~/.profile, /etc/profile, /etc/environment.
Вот он, золотой вектор! Если демон запускается через systemd, он часто использует файл конфигурации service unit (.service), где есть директива Environment= или EnvironmentFile=.

Пример уязвимой конфигурации (/etc/systemd/system/vulnerable.service):

Код:
[Service]
ExecStart=/usr/local/bin/my_daemon
User=daemon
Environment="LD_LIBRARY_PATH=/opt/myapp/lib"
# А что, если мы можем записать файл, на который ссылается EnvironmentFile?
EnvironmentFile=/etc/myapp/%i.conf

Если мы можем повлиять на содержимое /etc/myapp/ (через другую уязвимость, неправильные права), мы можем добавить туда нашу переменную, например, LD_PRELOAD=/tmp/bad.so.

Но это требует прав на запись в системные конфиги. Часто это уже привилегированная операция.

3.4. Способ 4: Атака на момент запуска (сценарий для пентеста)

Самый классический сценарий, где это работает почти из коробки.
  1. Есть скрипт (sudo, cron, init.d), который запускает какую-то программу.
  2. В этом скрипте не санитизируется окружение.
  3. У нас есть возможность задать переменные окружения перед запуском этого скрипта.
Практический инструмент №3: Эксплоит для sudo с env_keep (учебный пример).

Конфиг sudoers может содержать директиву env_keep. Она разрешает сохранять определённые переменные окружения из среды пользователя, вызывающего sudo.

Допустим, в /etc/sudoers есть строка:

Код:
Defaults        env_keep += "LD_LIBRARY_PATH"
%admin  ALL=(ALL) ALL

И есть программа /usr/local/bin/admin_tool, которая использует библиотеки.

Атакующий (в группе admin):

Bash:
# Устанавливаем свою библиотеку в путь
export LD_LIBRARY_PATH=/tmp/evil_lib
# Запускаем программу через sudo
sudo /usr/local/bin/admin_tool

Программа, запущенная от рута через sudo, унаследует LD_LIBRARY_PATH пользователя и загрузит библиотеку из /tmp/evil_lib.

Мораль: Конфигурация - это код. Плохая конфигурация - это уязвимость.


Обход защиты. Мир после Spectre и Meltdown.

Современные системы не спят. Давай посмотрим, что стоит на нашем пути.

4.1. SELinux, AppArmor, grSecurity

Мандатные системы контроля доступа (MAC). Они работают на уровне политик.
  • SELinux: Может запретить процессу, даже рутовому, доступ к файлам /proc/[pid]/mem, использование ptrace, или загрузку библиотек из неразрешённых путей (включая LD_PRELOAD).
  • AppArmor: Может явно запретить в профиле чтение/запись в /proc/*/mem или установку переменных окружения типа LD_PRELOAD.
Обход? Крайне сложен. Требует либо отключения политик (привилегия), либо эксплуатации уязвимостей в самом механизме MAC. Чаще всего это тупик. Наша задача - искать процессы с слабыми или отсутствующими профилями.

4.2. Усиленные дистрибутивы (Kernel Hardening)

  • kernel.yama.ptrace_scope = 1 или 2: Ограничивает ptrace только прямыми потомками. Не даст приаттачиться к произвольному демону.
  • kernel.dmesg_restrict = 1: Скрывает логи ядра от непривилегированных пользователей, что усложняет анализ.
  • kernel.kptr_restrict = 2: Скрывает адреса символов ядра, что ломает некоторые техники поиска в памяти.
Обход? Это на уровне ядра. Либо рут, либо 0-day.

4.3. Атаки на уровне контейнеров (Docker & Co.)

В контейнерах окружение часто контролируется жёстче, но есть нюансы.
  • docker run -e "MY_VAR=value" - явное задание переменных при запуске.
  • Но! Образ может содержать уязвимые скрипты точку входа (ENTRYPOINT, CMD), которые не очищают окружение.
  • Если есть возможность запустить произвольную команду в контейнере (через docker exec или уязвимость в приложении), можно задать переменные для дочерних процессов внутри контейнера.
Практический инструмент №4: Проверка окружения внутри контейнера.

Bash:
# Если получили шелл в контейнере:
env
cat /proc/1/environ | tr '\0' '\n' # Окружение PID 1 (основного процесса)
# Ищем чувствительные переменные, унаследованные от хоста или образа.

Полноценная симуляция атаки (от и до) в лаборатории.

Давай соберём всё вместе в учебном примере. Цель: заставить простой демон, запускающий скрипт, выполнить наш код через LD_PRELOAD.

Сценарий:
  1. Жертва: Демон test_daemon (наш собственный, написанный для теста), который раз в минуту запускает скрипт logger.sh. Работает от обычного пользователя user.
  2. Атакующий: Имеет доступ к учётной записи того же user (или рута).
  3. Задача: Подменить выполнение logger.sh так, чтобы он отправил нам шелл или записал данные, не меняя исходный скрипт.
Шаг 0: Подготовка лаборатории.
Создадим простейший демон на Python (для наглядности):

test_daemon.py:

Python:
#!/usr/bin/env python3
import os
import time
import subprocess

print(f"[DAEMON] PID: {os.getpid()}")
print(f"[DAEMON] Environment: {os.environ.get('CUSTOM_VAR', 'NOT SET')}")

while True:
    # ВАЖНО: запускаем скрипт, наследуя своё окружение
    subprocess.run(["/home/user/logger.sh"])
    time.sleep(60)

logger.sh:

Bash:
#!/bin/bash
echo "[LOGGER] $(date): Daemon is working. CUSTOM_VAR=$CUSTOM_VAR"

Запускаем демона: python3 test_daemon.py &amp;

Шаг 1: Разведка.
Смотрим его PID: ps aux | grep test_daemon. Допустим, PID = 5555.
Смотрим его окружение: cat /proc/5555/environ | tr '\0' '\n'. Видим, что CUSTOM_VAR не установлена.

Шаг 2: Создание полезной нагрузки.
Пишем библиотеку для LD_PRELOAD, которая перехватит вызов какой-нибудь функции в logger.sh. Но logger.sh - это bash-скрипт, он не загружает libc напрямую так, как бинарный файл. Нужна более хитрая тактика.

Вариант А: Заставить демон запускать НЕ оригинальный logger.sh, а нашу обёртку. Для этого можно подменить переменную PATH в окружении демона.

Создадим вредоносный скрипт с тем же именем:
/tmp/evil_logger.sh:

Bash:
#!/bin/bash
# Наш код
echo "OWNED! PID: $$" > /tmp/owned.txt
# Незаметно вызываем оригинальный скрипт
/bin/bash /home/user/logger.sh

Даём права: chmod +x /tmp/evil_logger.sh.

Шаг 3: Инъекция.
Нам нужно изменить окружение процесса 5555, добавив PATH=/tmp:$PATH. Используем gdb (в учебных целях!).

Bash:
sudo gdb -p 5555
(gdb) call (int) putenv("PATH=/tmp:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin")
(gdb) call (int) putenv("CUSTOM_VAR=HACKED")
(gdb) detach
(gdb) quit

Шаг 4: Ожидание и результат.
Ждём минуту. Демон делает subprocess.run(["logger.sh"]). Шелл ищет logger.sh в PATH. Первый путь - /tmp. Там лежит наш evil_logger.sh. Он выполняется, пишет в /tmp/owned.txt и незаметно вызывает оригинальный скрипт.

Проверяем: cat /tmp/owned.txt -> OWNED! PID: 6677.
Смотрим логи оригинального скрипта (если они есть) - всё выглядит нормально: [LOGGER] ... CUSTOM_VAR=HACKED.

Успех. Мы модифицировали окружение родительского процесса и повлияли на поведение его дочернего процесса.


Зачем всё это нужно?

Ты мог прочитать всё это и подумать: "Ну, в 2024 году это всё детские игрушки. EDR, антивирусы, изоляция - всё это ловит такие примитивные техники".

И будешь отчасти прав. Прямая модификация через /proc/mem или gdb на защищённой системе - это как идти на КПП с гранатомётом: эффектно, но тебя заметят за километр.

Так в чём смысл?
  1. Понимание фундамента. Все сложные атаки - это композиция простых примитивов. АПТ (Advanced Persistent Threat) может использовать уязвимость в софте, чтобы выполнить код, затем - технику внедрения в память, потом - запуск своего модуля через подмену библиотек, которую обеспечивает как раз модификация окружения у унаследованного процесса. Если ты не знаешь базиса, ты не поймёшь сложных отчетов от Mandiant или CrowdStrike.
  2. Обход сложных систем. Представь, что у тебя уже есть выполнение кода в контейнере или в рамках ограниченного процесса (например, через chroot или seccomp). Но тебе нужно вырваться наружу. Анализ унаследованного окружения, поиск переменных типа DBUS_SESSION_BUS_ADDRESS, XAUTHORITY (для доступа к графической сессии) - это прямые векторы эскалации. Это не про LD_PRELOAD, это про поиск ключей в среде, которая тебе "подарила" родительский процесс.
  3. Арт-хакинг. Это искусство видеть систему такой, какая она есть, а не такой, какой её хотят показать. Когда ты понимаешь, что переменная окружения, которую ты установил в .bashrc, проходит через всю цепочку процессов твоей сессии, ты начинаешь видеть систему как живую, дышащую сущность, а не как черный ящик.
  4. Защита своих систем. Прочитав это, ты должен первым делом пойти и проверить:
    • Конфиги своих systemd-сервисов: нет ли там EnvironmentFile на world-writable файлы?
    • Скрипты, запускаемые из cron: делают ли они env -i или хотя бы обнуляют опасные переменные?
    • Права на /proc в своих контейнерах?
    • Использует ли твой sudoers env_reset и правильно ли настроен env_keep?
Это понимание мотивов и методов противника, чтобы построить лучшую защиту. Это солидарность с духом исследования, а не разрушения.


Ещё один кирпич в стене.​

Итак, мы прошли долгий путь. От сухих строчек в /proc/[pid]/environ до концепции подмены воздуха, которым дышат процессы. Если ты читаешь эти строки не просто пробежав глазами, а прокручивая в голове примеры, возможно, даже запуская их на своей тестовой виртуалке - поздравляю. Ты только что прокачал свой скилл не просто как «хакер», а как системный философ.

Потому что именно здесь, в этой точке, где техническая механика сталкивается с абстрактной моделью поведения системы, и рождается настоящее понимание. И теперь, на прощание, давай выйдем за рамки чистой техники и поговорим о том, что это все на самом деле значит.

Переменные окружения как наследуемый мир

Представь на секунду, что каждый процесс - не просто исполняемый код, а целая вселенная. У нее есть свои законы (системные вызовы), свое пространство (виртуальная память), свое время (таймеры, планировщик). Но откуда берутся физические константы этой вселенной? Гравитация, скорость света, заряд электрона? В мире процессов эти константы - переменные окружения.

Когда процесс-родитель создает процесс-ребенок, он не просто копирует код. Он копирует целый мир. Со всеми его странными, иногда нелогичными, законами. PATH - это карта путей, по которым можно путешествовать в поисках исполняемых файлов. LD_LIBRARY_PATH - это список измерений, откуда можно призывать библиотеки-артефакты. HOME - это точка отсчета, твоя база. TMPDIR - это область хаоса, где можно создавать и разрушать временные файлы-миры.

И самое главное - ребенок не может просто так изменить мир родителя. Он получает его как данность. Он может изменить свою копию, но это уже будет его личная вселенная, которую он, в свою очередь, передаст своим детям.

А теперь ключевое: Что, если мы можем изменить исходный мир еще до того, как в нем начнут рождаться новые вселенные? Мы становимся богами-инженерами, которые подкручивают константы мироздания. Не взламываем двери, не ломаем стены - мы меняем само пространство-время так, что нужные нам события становятся неизбежными.

Это и есть суть атаки через окружение родителя. Мы не атакуем программу. Мы атакуем контекст, в котором она рождается и живет. И контекст, в отличие от кода, часто остается беззащитным.

Почему это до сих пор актуально? Эпоха контейнеров и изоляции

«Погоди, - скажет скептик. - Мы живем в 2024 году. У нас есть контейнеры с их собственными неймспейсами, cgroups, изолированными файловыми системами. У нас есть eBPF, который может отслеживать каждое движение. Зачем эти древние трюки с LD_PRELOAD?»

А вот затем, брат, что сложность - враг безопасности. Контейнер - это не магия. Это набор механизмов ядра, которые пытаются создать иллюзию изоляции. Но что лежит в основе каждого контейнера? Процесс. Чаще всего - PID 1 внутри этого контейнера. А откуда он берет свое окружение? От docker run -e, от Kubernetes ConfigMap, от образа. И если в этой цепочке передачи есть слабое звено (world-writable файл, из которого читается EnvironmentFile, уязвимость в рантайме оркестратора), то вся изоляция рушится, потому что мы атакуем не стену, а информацию, которую передают через эту стену.

Более того, сама идея микросервисов и оркестрации увеличила поверхность атаки, связанную с конфигурацией. Тысячи yaml-файлов, десятки тысяч переменных окружения, передаваемых между подами, сервисами, инит-контейнерами. Администратор, который вручную правит sudoers, думает трижды. Инженер, который пишет env: в деплоймент для кубернетеса, часто делает это на автомате. И здесь рождается новый класс уязвимостей: Supply Chain через конфигурацию.

Твой код может быть идеален. Твой образ - отсканирован на все CVE. Но если в момент запуска в твой контейнер попадает переменная JAVA_OPTS или NODE_EXTRA_CA_CERTS из скомпрометированного источника, все летит к чертям. И атаковать это будет не скрипт-кидди, а тот, кто прочитал эту статью и понял, что истинная сила лежит в управлении средой, а не в взломе кода.

От атаки к защите

Если ты дошел досюда и твой мозг жаждет применить эти знания где-то кроме безобидной виртуалки - остановись. Сделай шаг назад. И спроси себя: Как я могу использовать это, чтобы сделать мир хоть немного безопаснее?

Вот твои новые суперсилы:
  1. Взгляд аудитора. Теперь, глядя на любой демон, сервис, контейнер, ты будешь автоматически задавать вопросы:
    • Откуда он берет окружение? (systemctl show servicename | grep Environment)
    • Какие переменные он передает своим детям? (strace -e trace=execve -f -p PID)
    • Есть ли у него env_clean или env_reset в конфигурации?
    • На какие файлы ссылается EnvironmentFile и какие у них права?
      Это делает тебя в тысячу раз опаснее (для злоумышленников) как защитника.
  2. Понимание атаки как системы. Ты больше не будешь смотреть на эксплойт как на волшебную пулю. Ты будешь видеть его как процесс: получение начального доступа -> эскалация через унаследованное окружение -> установка персистентности через модификацию скриптов запуска. И на каждом этапе ты сможешь поставить контрмеру, именно потому, что понимаешь механику.
  3. Создание более жестких систем. Напиши свой мини-systemd, который будет санитизировать окружение для всех детей. Создай eBPF-программу, которая отслеживает попытки установки LD_PRELOAD в процессах с высокими привилегиями. Придумай механизм верификации EnvironmentFile с помощью цифровых подписей. Понимание атаки рождает творчество в защите.

Личное напутствие: Куда идти дальше?

Ты освоил фундамент. Но здание еще не построено. Вот направления, в которых стоит копать:
  • Глубже в ядро: Изучи исходники fs/exec.c в ядре Linux. Узнай, как именно do_execveat_common обрабатывает envp. Посмотри на реализацию binfmt_script (запуск скриптов) и как она взаимодействует с интерпретатором, чей путь часто берется из окружения.
  • Динамическая линковка - темный лес: Прочти man ld.so. Все. От корки до корки. Пойми, что такое DT_RPATH, DT_RUNPATH, DT_NEEDED. Изучи механизм dlopen() и dlsym(). Это сердце того, как живут современные бинарные файлы.
  • Контейнеры под лупой: Разберись, как runc, containerd и CRI-O создают окружение для контейнеров. Изучи спецификации OCI Runtime Spec. Пойми, где в этой цепочке можно вклиниться.
  • Песочницы нового поколения: Посмотри на gVisor, Firecracker, Kata Containers. Как они изолируют не только ресурсы, но и окружение? Какие у них модели угроз?
  • Атаки через аппаратное окружение: Это уже запредельный уровень. Но знаешь ли ты, что переменные окружения могут влиять на то, как JIT-компиляторы генерируют код? Или как CUDA_VISIBLE_DEVICES может изолировать GPU? Мир огромен.

Последняя правда

Техника, которую мы разобрали, - не панацея. Это один из инструментов в бесконечно большом наборе. Настоящий мастер отличается не знанием одного приема, а способностью видеть связи между разными слоями системы: от аппаратного обеспечения до бизнес-логики приложения.

Истинная сила, которую ты сегодня приобрел, - это системное мышление. Способность увидеть в простой переменной PATH не просто строку, а вектор наследования, доверия и потенциального компромета. Способность воспринимать ОС не как данность, а как диалог между процессами, где окружение - это язык этого диалога.

Запомни окончательную формулу:
Безопасность - это не стена. Это - понимание потоков информации.
Контроль - это не запрет. Это - управление контекстом.
Власть - это не root. Это - знание, как рождаются процессы и какой воздух они вдыхают с первого такта.
Теперь иди. Строй свои лаборатории. Ломай свои системы, чтобы понять, как их чинить. И, самое главное, делись знанием. Пиши статьи, выступай на митапах, веди блоги. Потому что знание, запертое в одной голове, умирает. А знание, переданное другим, становится силой, способной изменить цифровой ландшафт к лучшему.

Мы, те, кто ковыряется в /proc и читает man-страницы на ночь, - мы не вандалы. Мы - архитекторы реальности. И от нас зависит, будет ли эта реальность устойчивой, прозрачной и, в конечном счете, свободной.

Пусть твой PATH всегда ведет к мудрости, а твой LD_PRELOAD загружает только проверенные библиотеки. Система ждет своего исследователя.
 
Мы в соцсетях:

Взломай свой первый сервер и прокачай скилл — Начни игру на HackerLab