Skip to content

Conversation

@retrymp3
Copy link

@retrymp3 retrymp3 commented Nov 21, 2025

Summary

This PR fixes a Server-Side Request Forgery (SSRF) vulnerability in Valdi's HTTP client implementation. The vulnerability allows applications built with Valdi to make HTTP requests to arbitrary URLs, including internal/localhost services, cloud metadata endpoints, and private network resources, without any URL validation.

Vulnerability Details

Type: Server-Side Request Forgery (SSRF)
Severity: High
Component: HTTPRequestManagerModuleFactory.cpp

Root Cause

The vulnerability exists because user-provided URLs from JavaScript/TypeScript are passed directly to native HTTP request managers without any validation. The vulnerable code flow:

  1. TypeScript Layer (HTTPClient.ts): User input flows into HTTPClient.perform() method

    • URL is constructed via makeURL() function: HTTPClient.ts:7-22
    • No validation is performed on the URL
  2. Native Module Binding (HTTPRequestManagerModuleFactory.cpp:50-51): URL extracted directly:

    auto url = requestObject.getMapValue("url").toStringBox();
  3. Request Execution (HTTPRequestManagerModuleFactory.cpp:94-97): URL passed directly to platform HTTP libraries:

    auto cancellable = requestManager->performRequest(
        snap::valdi_core::HTTPRequest(std::move(url), ...), ...);

Missing Security Controls

The framework lacks:

  • Scheme validation - Allows non-HTTP/HTTPS protocols
  • IP address filtering - Allows localhost, private IPs, cloud metadata IPs
  • Hostname validation - No allowlist/blocklist mechanism
  • URL sanitization - No parsing or normalization

Impact

Critical Impact: Cloud Metadata Service Access

  • If a Valdi application runs on cloud infrastructure (AWS EC2, GCP, Azure), attackers can access metadata services
  • Example: http://169.254.169.254/latest/meta-data/iam/security-credentials/
  • Result: Complete cloud account compromise, data breach, resource hijacking

High Impact: Internal Service Access

  • Attackers can access internal services accessible from the device running the Valdi application
  • Localhost services: http://localhost:8080, http://127.0.0.1:3306
  • Internal network services: http://192.168.1.1/admin, http://10.0.0.1:8080
  • Result: Sensitive data exposure, unauthorized access to internal systems

Medium-High Impact: Network Reconnaissance

  • Attackers can perform internal network scanning and service discovery
  • Result: Enables attackers to map internal networks and identify additional attack targets

Affected Applications

All applications built with Valdi that use the valdi_http module are vulnerable, including:

  • Production applications at Snap (Valdi has been used in production for 8 years)
  • Any third-party applications using Valdi
  • Enterprise applications deployed on cloud infrastructure
  • Applications running on devices with access to internal networks

Solution

This PR adds URL validation to prevent SSRF attacks by:

  1. Adding isUrlAllowed() function in HTTPRequestManagerUtils that validates URLs before requests are made

  2. Blocking dangerous URLs:

    • Localhost and loopback addresses (localhost, 127.0.0.1, ::1)
    • Private IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
    • Cloud metadata service IPs (169.254.169.254, metadata.google.internal)
    • Non-HTTP/HTTPS schemes (only allows http:// and https://)
  3. Integrating validation into HTTPRequestManagerModuleFactory::loadModule() to validate URLs before making requests

Changes Made

Files Modified

  1. valdi/src/valdi/runtime/Utils/HTTPRequestManagerUtils.hpp

    • Added isUrlAllowed() static method declaration
    • Added include for StringBox
  2. valdi/src/valdi/runtime/Utils/HTTPRequestManagerUtils.cpp

    • Implemented isUrlAllowed() function with comprehensive URL validation
    • Extracts host part from URL
    • Validates scheme, hostname, and IP addresses
    • Blocks localhost, private IPs, and cloud metadata endpoints
  3. src/valdi_modules/src/cpp/valdi_http/HTTPRequestManagerModuleFactory.cpp

    • Added URL validation check after URL extraction (line 51)
    • Returns error if URL is blocked
    • Prevents SSRF attacks before requests are made

Code Changes

Before (Vulnerable):

auto url = requestObject.getMapValue("url").toStringBox();
// No validation - URL used directly
auto cancellable = requestManager->performRequest(...);

After (Fixed):

auto url = requestObject.getMapValue("url").toStringBox();

// Validate URL to prevent SSRF attacks
if (!HTTPRequestManagerUtils::isUrlAllowed(url)) {
    callContext.getExceptionTracker().onError(
        Error("URL not allowed: blocked localhost, private IP, or cloud metadata"));
    return Value::undefined();
}

auto cancellable = requestManager->performRequest(...);

Testing

The validation function blocks:

  • http://localhost:8080 - Blocked
  • http://127.0.0.1:3306 - Blocked
  • http://192.168.1.1/admin - Blocked
  • http://10.0.0.1:8080 - Blocked
  • http://169.254.169.254/latest/meta-data/ - Blocked
  • http://metadata.google.internal/... - Blocked
  • file:///etc/passwd - Blocked (non-HTTP scheme)
  • https://example.com - Allowed
  • http://api.example.com/v1/data - Allowed

Security Considerations

  • Backward Compatibility: The fix maintains backward compatibility for legitimate use cases (public HTTP/HTTPS URLs)
  • Performance: URL validation is lightweight and performed before network requests
  • False Positives: The validation is designed to minimize false positives by properly parsing the host part of URLs

References

Responsible Disclosure

This vulnerability was discovered during security research. This PR provides a fix while maintaining backward compatibility for legitimate use cases. I reported through hackerone and I was asked to open a PR for this by bugtriage-jay as this is not covered under Snapchat's bug bounty scope.


Note: This is a security fix addressing a framework-level vulnerability that affects all Valdi applications using the HTTP client.

Fixes #63

}

if (hostPart == "localhost" ||
hostPart == "127.0.0.1" ||
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i hit 127.0.0.2 and the entire function is bypassed

}

if (hostPart == "metadata.google.internal" ||
hostPart == "169.254.169.254") {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should block the entire /16 range


if (hostPart == "localhost" ||
hostPart == "127.0.0.1" ||
hostPart == "::1" ||
Copy link

@indexds indexds Nov 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

many many many ways to hit ipv6 loopback without explicitly typing ::1, you want to reverse this and only allow the 2000::/3 range

@indexds
Copy link

indexds commented Nov 22, 2025

you're not blocking 0.0.0.0/8 either

Updated error message for blocked URLs in HTTP request validation.
Added utility functions for URL decoding, IP normalization, and host extraction. Improved URL validation by integrating IP checks and refactoring existing logic.
@retrymp3
Copy link
Author

you're not blocking 0.0.0.0/8 either

Hey thank you for the comments, added a lot more checks to prevent edge cases. But this is still largely following a blacklisting approach, not ideal. But in this case it is a since it is a framework used by many applications, not a single app. And applications might need to call diverse or unpredictable APIs. So a whitelist would require every application to pre configure allowed domains, which is I don't think is entirely practical. For specific applications with known endpoints they would have to implement their own whitelist approach. But this fix would provide a strong default security by blocking known SSRF vectors.

Please let me know if there are any other edge cases that you found.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SSRF Vulnerability in HTTPRequestManager

2 participants