A short code follow-up to the blog post to demonstrate why unsetting environment variables can be tricky. Often you're best off just not using them.
- An application internal dependency disclosing
ENV
during a crash or exception (language library) - An application external dependency disclosing
ENV
during a crash or exception (ffmpeg, imagemagick, etc.) - A limited command injection attack (no
$IFS
) - An arbitrary file read of
/self/proc/environ
Most language runtimes maintain a "shadow-copy" of the environment that is
manipulated and inherited by sub-processes (exec, not fork). Arbitrary file
reads of /proc/self/environ
are still game over even if the application
unsets the environment variables.
Don't use environment variables for secret storage.
Test | Threat |
---|---|
Print the envvar (simple echo) | [test] |
Unset and print the envvar | [1, 3] |
Read the envvar from /proc/self/environ |
[3, 4] |
Print envvar from subprocess | [2, 3] |
- Bash
- C
- C#
- Go
- Java
- JavaScript
- Python
- Ruby
- Rust
Language | Envvar | Envvar after unset | Envvar in /proc/self/environ after unset |
Envvar in subprocess after reset |
---|---|---|---|---|
Bash | ✅ | ✔️ | ✔️ | ✔️ |
C | ✅ | ✔️ | ✅ | ✔️ |
C# | ✅ | ✔️ | ✅ | ✔️ |
Go | ✅ | ✔️ | ✅ | ✔️ |
Java | ✅ | ✔️ | ✅ | ✔️ |
JavaScript | ✅ | ✔️ | ✅ | ✔️ |
Python | ✅ | ✔️ | ✅ | ✔️ |
Ruby | ✅ | ✔️ | ✅ | ✔️ |
Rust | ✅ | ✔️ | ✅ | ✔️ |
This table is updated manually.
For the absolute latest results, view the Github Actions CI job log.
✔️ means the envvar couldn't be read. ✅ means the envvar was readable.
Each test is hacked together. I'm not a polyglot developer. PRs to improve inadequate tests are welcome.