Skip to content

codetabs Unauthenticated SSRF Reads Internal Network HTTP Response Headers #45

@360AlphaLab

Description

@360AlphaLab

Vulnerability Report: codetabs Unauthenticated SSRF Reads Internal Network HTTP Response Headers

1. Product Introduction

codetabs is a multi-functional Web API service developed in Go, providing interfaces for HTTP header retrieval, proxying, geolocation, random data generation, code statistics, and more. The /v1/headers/ endpoint fetches the response headers of a target site on behalf of the user.

2. Vulnerability Description

Attribute Value
Vulnerability Type CWE-918: Server-Side Request Forgery (SSRF)
CVSS 3.1 8.6 High (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N)
Vulnerable File project/codetabs/headers/headers.go (22-76)
Prerequisites No login required, no additional privileges needed
Entry Point Unauthenticated public endpoint GET /v1/headers/?domain=<url>
  • Core Impact: An attacker can cause the server to actively access internal network or cloud metadata addresses and relay the returned HTTP response headers back to the attacker.
  • Environmental Constraints: The target deployment environment must be able to access the specified internal network address or metadata address from the server side.
  • Default Trigger Condition: This condition is satisfied in the default code path; the endpoint is publicly registered with no target address validation.

3. Affected Scope

  • Affected Versions
    • The version corresponding to the current codebase, see var version = "0.9.4a"
    • All versions that retain the same /v1/headers/ implementation logic without adding target restrictions
  • Unaffected Versions
    • Versions that add protocol, host, IP range, and redirect target validation to the domain parameter
  • Trigger Conditions and Defaults
    • Endpoint open by default: Yes
    • Authentication required: No
    • Target address must be reachable: Yes
    • Supports returning target response headers to the requester: Yes

4. Vulnerability Details

4.1 Code Audit Analysis

The route is registered at mux.HandleFunc("/v1/headers/", mw(headers.Router, "headers", c)). The corresponding middleware mw() only performs additional ban checks for the proxy service; the headers endpoint has no authentication or target validation logic whatsoever.

In headers.Router(), the code directly reads user input from query parameters:

r.ParseForm()
hr.domain = r.Form.Get("domain")
if hr.domain == "" || len(path) != 2 {
    u.BadRequest(w, r)
    return
}
hr.doHeadersRequest(w, r)

Corresponding locations: r.ParseForm(), hr.domain = r.Form.Get("domain"), hr.doHeadersRequest(w, r).

Subsequently in doHeadersRequest(), the server directly concatenates this value into a curl command and executes it:

const curl = "curl -fsSI "
for !notMoreRedirections && count < 10 {
    rawData, err := u.GenericCommandSH(curl + hr.domain)
    if err != nil {
        ...
        return
    }
    parseHeadString(string(rawData), &hr.headers)
    if hr.headers[count]["Location"] == "" {
        notMoreRedirections = true
    } else {
        hr.domain = hr.headers[count]["Location"]
    }
    count++
}
u.SendJSONToClient(w, hr.headers, 200)

Key locations:

There are no restrictions on any of the following:

  • Whether the address is a public IP
  • Whether the address is a loopback address
  • Whether the address is an RFC1918 private network address
  • Whether the address is a link-local address, such as 169.254.169.254
  • Whether following redirects to internal network targets is permitted

Therefore, an attacker can craft the following request:

GET /v1/headers/?domain=http://127.0.0.1:80 HTTP/1.1
Host: target

The server will initiate a request to 127.0.0.1:80, parse the response headers, and return them to the attacker as JSON. Even without exploiting any shell metacharacters, this chain itself already satisfies the conditions for SSRF.

4.2 PoC Construction Approach

The design intent of this endpoint is to fetch remote site response headers on behalf of the user, so the domain parameter naturally enters the server-side network access logic. There are only two key points for the PoC:

  1. Choose a domain parameter value controlled by the attacker to make the server access a local or internal network address.
  2. Check whether the response contains HTTP headers returned by the target address.

To prove this is SSRF rather than an ordinary frontend request, the PoC directly requests the target site's /v1/headers/ endpoint with domain set to:

  • http://127.0.0.1:80
  • http://169.254.169.254/latest/meta-data/
  • http://192.168.1.1:80

As long as the response contains the response headers corresponding to these target addresses, it proves the request was initiated by the server and the results were relayed back — no command injection or additional authentication is required.

5. Vulnerability Reproduction

5.1 Environment Setup

5.2 Reproduction Steps

Step 1: Access the suspicious endpoint and confirm parameter controllability

Access http://127.0.0.1:3000/v1/headers/?domain=http://127.0.0.1:80. The page returns a JSON result, indicating that the endpoint accepts an externally supplied domain parameter and processes the target address.

Step1-Access Endpoint

Step 2: Change domain to an internal network address

Set the domain parameter to http://127.0.0.1:80 or another internal network address and access again. The response will contain the HTTP status line and response header fields from the target address, indicating that the server has initiated a request to that address.

Step2-Set Internal Target

Step 3: Observe the returned result to confirm SSRF

The JSON returned by the page contains the response headers returned by the target service, such as Server, Content-Type, Location, or status line fields. This content was not obtained by the browser directly requesting the internal network target, but was fetched by the server on behalf of the client and relayed back.

Step3-Confirm Response Headers

5.3 Result Verification

Verification Item Result
Request GET /v1/headers/?domain=http://127.0.0.1:80 Endpoint accepts the parameter and returns JSON
Does the server actively access 127.0.0.1 Yes, the target's response headers are visible in the returned result
Is there any target address restriction No, no whitelist, denylist, or private network restriction found in the code
Does it support following redirects Yes, up to 10 times, see count < 10
Does it return results to the attacker Yes, see u.SendJSONToClient(w, hr.headers, 200)

5.4 Attack Chain Diagram

Attacker
  ↓
Access /v1/headers/?domain=http://127.0.0.1:80
  ↓
Server reads domain parameter
  ↓
Server executes curl request to target address
  ↓
Parses HTTP response headers returned by target
  ↓
Serializes response headers as JSON and returns
  ↓
SSRF confirmed ✓

6. POC

# Directly verify whether the server accesses the local loopback address and returns response headers
curl "http://127.0.0.1:3000/v1/headers/?domain=http://127.0.0.1:80"
# Verify whether the cloud metadata address can be accessed by the server
curl "http://127.0.0.1:3000/v1/headers/?domain=http://169.254.169.254/latest/meta-data/"
# Use the Python PoC in the repository for batch verification
python poc_codetabs_headers_ssrf.py --base-url http://127.0.0.1:3000 --domain http://127.0.0.1:80

7. Remediation Recommendations

  1. Do not use user input directly as the server-side request target
    Fully parse the domain parameter, allow only http and https schemes, and reject bare hostnames, non-standard schemes, and malformed input.

  2. Add target address access control
    After resolving the domain name, validate the final IP and reject the following address ranges:

    • 127.0.0.0/8
    • 10.0.0.0/8
    • 172.16.0.0/12
    • 192.168.0.0/16
    • 169.254.0.0/16
    • ::1/128
    • fc00::/7
    • fe80::/10
  3. Re-validate redirect targets
    The current logic reads Location and continues the request. Every redirect target must be re-validated for protocol, domain, and IP — validation cannot be limited to the initial address only.

  4. Do not execute network requests via shell command concatenation
    Replace u.GenericCommandSH() with Go's native net/http client to avoid simultaneously introducing SSRF and potential command injection risks.

  5. Add an outbound access whitelist
    If the business only needs to request a limited set of target sites, configure an explicit whitelist that only allows access to trusted domain names or fixed services.

  6. Fix Example

parsed, err := url.ParseRequestURI(domain)
if err != nil {
    return error
}
if parsed.Scheme != "http" && parsed.Scheme != "https" {
    return error
}

ips, err := net.LookupIP(parsed.Hostname())
if err != nil {
    return error
}
for _, ip := range ips {
    if isPrivateOrLoopback(ip) {
        return error
    }
}

Vulnerability Submission Evaluation Results

Vulnerability Chain Exists: Yes

Submission Determination

Attribute Value
Vulnerability Type A10: Server-Side Request Forgery
Exploitation Method Unauthenticated
CVSS 3.1 8.6 (High)
Submit Yes

Determination Basis

Based on 4.5 A10 — Server-Side Request Forgery (SSRF) and 4.5.1 SSRF Direct Impact Determination:

  • "Access to internal core services (Redis/MySQL/admin panel) with response echo should be submitted", see 120
  • "SSRF initiated via proxy/import functionality with response echo should be submitted", see 123

Additionally, the SSRF-specific determination in the decision flow states:

  • "Able to access internal services and return data → Submit", see 210-223

Detailed Explanation

This vulnerability is an unauthenticated SSRF. An attacker can instruct the server to access internal network or cloud metadata addresses and have the returned HTTP headers echoed directly in the response. This is not a blind SSRF limited to port scanning — it already has the capability to read data returned by internal services, satisfying the high-severity SSRF criteria for submission.

Next Steps

Continue generating the complete vulnerability report.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions