Home

Unexpected Code Execution

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:

  def index(conn, %{"type" => type}) do
    from p in String.to_atom(type), select: p.title
    ...
  end

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 , 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:

  1. Generate a Phoenix project with mix phx.new example.
  2. Now, update the PageController to look like the following:
defmodule ExampleWeb.PageController do
  use ExampleWeb, :controller
  import Ecto.Query, only: [from: 2]

  def index(conn, %{"type" => type}) do
    from u in String.to_atom(type), select: u.title
    render conn, "index.html"
  end
end
  1. Run the server and make a request to “http://localhost:4000/?type=Elixir.ExampleWeb.PageController”. Note the error.
  2. Now, define __schema__/1 in the PageController:
defmodule ExampleWeb.PageController do
  ...

  def __schema__(_) do
    IO.puts "Hello, world!"
  end
end
  1. 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:

  1. Go to the root of your Example application, and make the necessary directories: mkdir -p _build/dev/lib/example/ebin/assets/static/images
  2. 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
  1. This module is only temporary. Run the Phoenix application to force a build: mix phx.server.
  2. Stop the server, then move the newly created beam file to your assets directory: mv _build/dev/lib/example/ebin/assets/static/images/arbitrary_rce.png assets/static/images/.
  3. Delete the module definition from your PageController, then start your app.
  4. Now go to “http://localhost:4000/?type=assets/static/images/arbitrary_rce.png%00”.
  5. Check your logs for the “REMOTE CODE EXECUTION” output!

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 :)

Updates The null byte issues are addressed in OTP 21, and, as of OTP 20.3, module names can no longer contain directory separators. If you’ve updated, you are now safe from this method of code execution!

  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. 

  2. Elixir (via Erlang) has a problem with null bytes. If you want to watch me talk about it, you can see that here: ElixirConf 2017 - Plugging the Security Holes in Your Phoenix Application