ENV vars and security
Tags: webdev, security, cybersecurity
Published: 2022-08-02 16:23:00

As enhancing deployment is part of the 12-factor principles, many programming languages and frameworks include helpers and external libraries to ease the use of environment variables.

However, some practices can introduce some risks. Let's see how ENV vars work and how to secure their usage.

## Loading configuration files

Modern webapps usually require ENV vars. It's not uncommon to use `.env` files where you can write a series of key/value pairs. Each environment (like staging, dev, or production) gets its own `.env` with its own values.
Only the keys remain the same across all environments.

_N.B.: Note that some environment might also use additional vars, for example, to configure the debug_

The app loads the environment from the configuration file, which often contains secrets like API keys and DB credentials. It might be handy, but it has some risks and inconvenient:

* the `.env` files are often committed in the versioning system (e.g. git)
* the `.env` files are sometimes deployed on a public folder in production without any restriction (I've seen it multiple times)
* maintaining these files can quickly become tedious

Attackers will enumerate these files at the speed of light, using free tools like Gobuster or Nikto (there are many other scanners available). There are some workarounds:

* give the `.env` files specific permissions like `chmod 600` to restrict access
* forbid access in your `.htaccess`, nginx config, or Web.Config to all `.env` files
* store these files in a specific folder that is not publicly accessible

Let's cut to the chase: **none** of these workarounds is completely safe. Configurations can be modified on purpose or accidentally, resulting in a public disclosure. At least, it would add a thin layer of security, **in addition**, but it would not be sufficient in the long term.

In the last scenario, the server points the webapp to a specific subfolder like `Public/` instead of exposing the whole installation:

```
│
├── .gitignore
├── Public/
│   ├── style.css
│   └── index.php
│
├── Config/
│   ├── .env
│   └── config.json
│
└── README.md
```

This above imaginary structure keeps the `.env` file outside the web root. A more concrete example would be WordPress and its `wp-config.php` file that contains database credentials. 

Alternative approaches such as [Bedrock](https://roots.io/bedrock/) can provide more protection. The framework leverages ENV vars to store information and provides a convenient way to override configurations according to the environment. 

Still, it can have major drawbacks:

* the framework might not let you do whatever you want
* you might get unwanted side effects and additional bugs when migrating from a classic installation
* depending on your approach of versioning, some creds can still be in your git repo in plain text (e.g., dev config)

## How these ENV vars work?

### Getting started

Each ENV var has a name and an associated value, and the name will likely be uppercase:

```
MY_ENV_VAR="the value"
```
Technically speaking, you don't have to use uppercase, but it's a naming convention. On a Linux machine, you can print ENV vars with the `env` command (no option).

These vars are used by many processes (~ programs), and the values can deeply modify their behavior. For example, `$PATH`, the ENV var that contains... paths, allows using various tools and commands without entering the path to the binary again and again.

Roughly speaking, this is how most systems work, with processes that fork other processes. During these operations, ENV vars are implicitly available.

On your machine, you may use the `export` command to set a new ENV var:

```
export MY_ENV_VAR="the value"
``` 

### How to make ENV vars persistent

The variable is only available for child sessions and will die with the current shell. In other words, if you exit or close the window, your variable is lost.

To create a persistent var, you can use the same `export` command and save it in the `.bash_profile` or the `.bashrc` file.

It's pretty cool, but **never use such files to store secrets**, as many programs will be able to read them.

### ENV vars vs. shell vars

`MY_ENV_VAR="the value"` and `export MY_ENV_VAR="the value"` are not the same commands at all. The first one defines a local var. 

To transmit this var to every new child process automatically, you must use `export`, which converts the local var into an ENV var.
Otherwise, it won't be available.

## Don't store secrets in ENV vars

Due to the very nature of ENV vars, using them to store secrets is not the best idea:

* for any given process, all child processes will inherit ENV vars, exposing the secrets to way more processes than necessary
* the system will log events containing ENV values in plain text (e.g., debugging, error logs, third-party tools)
* hackers could use the `env` or `printenv` command to print ENV vars after exploiting a vulnerability

## Good practices and alternatives

* don't consider all ENV vars are equal
* don't use the same API keys for all environments
* be extra vigilant with permissions and accessible secrets, especially in cloud-based architectures
* use **encrypted secrets**. For example, GitHub [provides](https://docs.github.com/en/actions/security-guides/encrypted-secrets) very convenient interface to encrypt your secrets and manage them with granularity
* use CI/CD solutions that abstract ENV vars away and allow you to update values via secured APIs
* if you use Kubernetes, use the feature called ["secrets"](https://kubernetes.io/docs/concepts/configuration/secret/), but be aware that secrets are stored **unencrypted by default**

## Wrap up

ENV vars are accessible pretty much everywhere, making them particularly convenient for web development, but sometimes exposing projects to severe leaks and escalations.

The alternative solutions are not always the easiest to implement, and you won't solve all security issues just by skipping ENV vars, but storing secrets in plain text is a bad practice.