CWE-526 is a product that uses an environment variable to store unencrypted sensitive information. It is not tied to a language, and it is common in cloud and CI setups.
Twelve factor style guides push configuration into the environment, so API keys and database passwords end up there. The weakness is that the environment is wider than it looks.
How it happens
MITRE explains that information in an environment variable can be reached by other processes in the same execution context, including child processes where dependencies run, or serverless functions in the cloud. Those processes often have no need for the variable. Its contents can also be inserted into messages, headers, log files or other output, so any weakness that discloses environment variables exposes the secret.
The record lists three examples.
- CVE-2022-43691: a CMS shows sensitive server side information from environment variables when run in debug mode.
- CVE-2022-27195: a plugin for an automation server inserts environment variable contents into build XML files.
- CVE-2022-25264: a CI/CD tool logs environment variables related to passwords, which are then added to content history.
Different tools, same cause. A secret in the environment travels wherever the environment is printed, copied or inherited.
# Risky patterns
import os, subprocess
db_password = os.environ["DB_PASSWORD"] # secret lives in the environment
subprocess.run(["some-tool"]) # child inherits every variable
print(dict(os.environ)) # debug line that dumps them all
What an attacker gains
MITRE lists reading application data. A leaked environment can hand over database credentials, cloud keys and tokens. From there the attacker can act as the application, and each secret may open further systems.
How to find it
Automated static analysis can find some instances by following reads from the environment and the places they end up.
- Search code and deployment files for secrets passed as environment variables: Dockerfiles with
ENV, compose files, CI settings, serverless config. - Search for code that prints, logs or serializes the whole environment, such as
os.environ,process.envorprintenvin scripts. - Check what debug modes and error pages show. Make sure they are off in production.
- Check build logs for values that look like secrets.
- Run a secrets scan over repositories and images, since Dockerfile
ENVlines are stored in the image.
How to fix it
MITRE's guidance has two parts.
- At design time, encrypt information kept in the environment variable. If that is not practical, use a properly protected configuration file instead. MITRE notes that an unencrypted config file is not guaranteed safe either, but it is still a better choice because it reduces the attack surface from related weaknesses such as CWE-214. In some settings, vaults may be a workable way to deliver secrets more safely. Users should be told when the business chooses not to encrypt.
- At implementation time, if the variable is not needed for the behavior, remove it or set it to an empty value.
Practical habits:
- Fetch secrets from a vault or secrets manager at start up, and keep them in memory only as long as needed.
- Pass a child process only the variables it needs, with a clean environment.
- Never log or print the whole environment. Mask known secret names in CI output.
- Keep secrets out of image layers and build arguments.
- Turn off debug pages in production.
import subprocess
clean_env = {"PATH": "/usr/bin:/bin", "LANG": "C"} # only what the tool needs
subprocess.run(["some-tool"], env=clean_env)
Try it yourself
Click through it at your own pace, or open it full screen.
Quick check
Why are environment variables leaky?
Child processes inherit them, and tools or debug pages often print them into logs, headers and files.
What does MITRE suggest if encryption is not practical?
A properly protected configuration file, or a vault where feasible. Or remove the variable if it is not needed.