Contact

[February 7th, 2018] - A while back, I found a somewhat unique remote code execution attack vector for Elixir and Phoenix projects. Now, right off the bat, it’s worth noting that there is a 99.999% chance that no Elixir library or Phoenix application is unknowingly vulnerable to this issue. However, it’s fairly amusing and I thought that made it worth sharing.

So, to set the stage:

You’ve got a web application, and you let users do a couple things. Not necessarily realistic things, but good for an example. First, you let your users upload PNGs. You do a simple but sufficient validation of the image before saving it to make sure it has a .png extension and that it’s not too big. It seems secure enough. You also have a bunch of non-sensitive database tables, and all of them have a title column. You want your users to be able to dynamically query these tables and fetch all the titles.

On the attacker side, the attacker has uploaded one PNG, hello.png, and they know your dynamic query looks like this:

What is the worst thing the attacker can do here? If the title didn’t already give it away, it’s arbitrary code execution!

So, how does it work?

The first thing you might have noticed, is the call to String.to_atom. This is insecure on its own1, but it won’t cause code execution.

Try passing a known module to the from/2 function, and you will get an error to the effect of "protocol Ecto.Queryable not implemented for <MODULE>, the given module does not provide a schema." This is because, deep down, from/2 calls __schema__ on whatever module you pass as a parameter. You can test this out in the following way:

defmodule ExampleWeb.PageController do
...
def __schema__(_) do
IO.puts "Hello, world!"
end
end

Re-issue the GET request, and check your logs for the “Hello, world!” text.

We are half way to arbitrary code execution — we can execute the __schema__/1 function on any module; we are just missing the “arbitrary” part. This is where the fun part comes into play.

Did you know module names can be paths? For example, if you have a module named :”/My/Full/Path/Module”, Elixir (by way of Erlang) will attempt to load code from the file located at "/My/Full/Path/Module.beam". Better still, you can null-terminate2 the module name. So :”/My/Full/Path/Module.png\0" will load code from the file located at "/My/Full/Path/Module.png".

With this capability, an attacker can locally compile a module :”assets/static/images/hello.png\0” with a malicious __schema__ function. Then they can upload the "hello.png" file that’s been created, and make a request to "http://hostname/?type=assets/static/images/hello.png%00". Arbitrary code execution achieved.

Here are reproduction steps so you can test this yourself:

Go to the root of your Example application, and make the necessary directories: mkdir -p _build/dev/lib/example/ebin/assets/static/images

Now, create a new module in the PageController:

defmodule ExampleWeb.PageController do
...
end
defmodule :"assets/static/images/arbitrary_rce.png\0" do
def __schema__(_) do
IO.puts("\n=== REMOTE CODE EXECUTION ===\n")
end
end

This module is only temporary. Run the Phoenix application to force a build: mix phx.server.

And that’s it.

Like I said, it’s unlikely that this is actively exploitable in the wild. But, given enough time and growing popularity, we may one day see this vulnerability in a live Phoenix application!

Anyway, if you didn’t feel like actively following along, but you still want to give this a test run, you can find the example repository here: GitHub - GriffinMB/RCE_Example. And, if you are a security conscious Elixir/Phoenix developer looking to secure your latest web app, give Sobelow a whirl :)

1: In Elixir, atoms are not garbage collected. As such, if user input is passed to the String.to_atom function, it may result in memory exhaustion or exhaust the atom table.