bkeepers / dotenv

A Ruby gem to load environment variables from `.env`.
MIT License
6.61k stars 505 forks source link

Arbitrary code execution vulnerability (or intended behavior?) #507

Open panzi opened 5 months ago

panzi commented 5 months ago

Variable substitution and command substitution is done in two passes. This means if variable substitution results in a string containing $(command) that command is executed. I think this is unexpected, it isn't mentioned in the README in the section Command Substitution. This further means if some seemingly innocuous environment variables are user controlled and then referenced in the .env file this will execute any command in them.

This can be fixed by doing both variable substitution and command substitution in one pass instead of two.

(Personally I don't think command substitution is a good feature anyway, and it should be at least deactivated by default.)

Steps to reproduce

Put this in a .env file:

FOO='$'
BAR='(date)'
BAZ="$FOO$BAR"

Then run e.g.:

dotenv -f test.env ruby -e 'puts ENV.select {|k| %w(FOO BAR BAZ).include? k}'

Which prints:

{"FOO"=>"$", "BAR"=>"(date)", "BAZ"=>"Fr 14 Jun 2024 17:49:33 CEST"}

The command substitution doesn't need to be split over several variables, I just did this to demonstrate that even if that is done it works. Also the variables containing the command substitution syntax don't need to come from the .env file. They could be already in the environment. While I can't think of a situation where user input may control an environment variable that then is used in a .env file right off my head, I still consider that a potential problem. There's probably some obscure script somewhere that uses things like that. (The only similar situation I can think of are classical CGI scripts where HTTP headers are passed as environment variables, but Ruby isn't usually used as a CGI script and even then the HTTP header variables need to be referenced in the .env file, which is unlikely.)

Expected behavior

I think it should print this, and don't execute any commands:

{"FOO"=>"$", "BAR"=>"(date)", "BAZ"=>"$(date)"}

Actual behavior

Executes the command date in this case, or any command generally.

System configuration

dotenv version: dotenv 3.1.2

Ruby version: ruby 3.3.2 (2024-05-30 revision e5a195edf6) [x86_64-linux]