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.
= System.find_executable("cat")
path = Port.open({:spawn_executable, path}, [:binary, args: ["/etc/passwd"]])
port 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.