Sometimes, as a developer you need to execute commands in the OS. How can you achieve that in Erlang and Elixir, and how can you do it securely?

Here you will find information that may be of interest both from a development and an attack points of view.

OS command execution

os module

The simplest way to execute something in the OS is to use the cmd function.

os:cmd("cat /etc/passwd").

The response will be a string from stdout or stderr. There is no separation between commands and arguments.

The insecure analog in Elixir is :os.cmd/1,2

:os.cmd(:"cat /etc/passwd")

open_port of erlang module

spawn

Command = "ls > ls.results",
PortSettings = [exit_status, {line, 16384}, use_stdio, stderr_to_stdout, hide, eof, binary],
Port = open_port({spawn, Command}, PortSettings).

open_port with spawn starts an external program specified in a tuple.

For external programs, PATH is searched (or an equivalent method is used to find programs, depending on the OS). This is done by invoking the shell on certain platforms. The first space-separated token of the command is considered as the name of the executable (or driver). This makes this option unsuitable for running programs with spaces in filenames or directory names.

The insecure analog in Elixir is Port.open with :spawn as a first parameter.

Port.open({:spawn, "cat /etc/passwd"}, [:binary])
flush()

spawn_executable

Command = "ls",
PortSettings = [exit_status, {line, 16384}, use_stdio, stderr_to_stdout, hide, eof, binary],
Port = open_port({spawn_executable, Command}, PortSettings).

{spawn_executable, FileName} works like {spawn, FileName}, but only runs external executables. FileName in its whole is used as the name of the executable, including any spaces. If arguments are to be passed, the PortSettings args can be used.

The shell is usually not invoked to start the program, it is executed directly. PATH (or equivalent) is not searched. To find a program in PATH to execute, use os:find_executable/1.

The secure analog in Elixir is Port.open with :spawn_executable as a first parameter, and any arguments passed using the :args option.

path = System.find_executable("cat")
port = Port.open({:spawn_executable, path}, [:binary, args: ["/etc/passwd"]])
flush()

Also in Elixir, you can use System.cmd/2,3:

System.cmd("cat", ["/etc/passwd"])

spawn_driver

It works like {spawn, Command}, but demands the first (space-separated) token of the command to be the name of a loaded driver. If no driver with that name is loaded, a badarg error is raised.

Code execution

erl_eval module

The erl_eval module functions allow you to execute Erlang code. You have to use expressions with functions mentioned above to execute code in OS.

The following are two expressions that execute ls in the operating system.

Expression = {call,1,{remote,1,{atom,1,os},{atom,1,cmd}},[{string,1,"ls"}]}.

{ok, Tokens, _} = erl_scan:string("os:cmd(\"ls\")."),
{ok, Expressions} = erl_parse:parse_exprs(Tokens).

expr

erl_eval:expr(Expression, []).

exprs

erl_eval:exprs(Expressions, []).

expr_list

erl_eval:expr_list(Expressions, []).

Similar Elixir functions in the Code module are :eval_string, :eval_file, and :eval_quoted.

Code.eval_string(":os.cmd(:\"cat /etc/passwd\")", [])

There are also :eval_string and :eval_file in Embedded Elixir.