Kuboid
Open Luck·Kuboid.in
Security BSides2025
Open in YouTube ↗

Don't Trust, Verify! How I Found a CSRF Bug Hiding in Plain Sight

Security BSides San Francisco66 views29:1810 months ago

The speaker demonstrates a Cross-Site Request Forgery (CSRF) vulnerability in the Go programming language's standard library, specifically within the 'net/http/httptest' package. This vulnerability arises because the 'httptest.NewRequest' function can generate requests with incomplete URL structures that bypass security checks in production environments. The talk highlights the importance of validating assumptions about differences between test and production environments and introduces Fetch Metadata request headers as a more robust defense mechanism. The speaker provides a demonstration of the vulnerability and discusses the implications of using dual-purpose APIs in security-sensitive code.

The Hidden Danger in Go Test Utilities: How httptest.NewRequest Can Bypass CSRF Protections

TLDR: A subtle behavior in Go’s net/http/httptest package allows developers to generate malformed request objects that bypass security middleware in production. By failing to populate the URL scheme in test environments, developers often write unit tests that pass while leaving their applications vulnerable to Cross-Site Request Forgery (CSRF). Security researchers and developers should audit their test suites for these "Franken-requests" and transition to more robust defenses like Fetch Metadata request headers.

Testing is the bedrock of secure development, but sometimes the tools we use to verify our code introduce the very flaws we are trying to prevent. During a recent review of authentication middleware, I stumbled upon a classic case of a "Swiss-Cheese" failure where a series of seemingly benign assumptions about the Go standard library created a wide-open path for Cross-Site Request Forgery (CSRF).

The issue centers on how we write unit tests for HTTP middleware. Specifically, the httptest.NewRequest function in Go is designed to make it easy to simulate incoming traffic. However, it is a dual-purpose utility that behaves differently depending on whether you are testing a client or a server. When you use it to simulate a server request, it parses the URI from the request line. If you provide an incomplete URL, the resulting request object may lack critical metadata, such as the URL scheme.

The Anatomy of the Bypass

Many CSRF protection libraries, including the popular gorilla/csrf middleware, rely on origin checks to verify that a request is legitimate. These checks often inspect the request’s URL scheme to ensure it matches the expected https protocol. The logic is straightforward: if the scheme is missing or incorrect, the middleware should reject the request.

The vulnerability, tracked as CVE-2025-24358, exists because the httptest.NewRequest function does not always populate the URL.Scheme field for server-side requests. If your middleware is written to only perform origin validation when the scheme is https, and your test suite uses a request object where that field is empty, your tests will effectively skip the security check.

Consider this snippet of problematic test code:

// This test passes because the scheme is empty, 
// causing the middleware to skip the origin check.
req := httptest.NewRequest("POST", "/transfer", nil)
w := httptest.NewRecorder()
middleware.ServeHTTP(w, req)

Because the URL.Scheme is not set, the middleware assumes the request is not subject to the https origin check and allows it to proceed. You get a green light in your CI/CD pipeline, but you have zero actual coverage for the security logic you intended to test. In production, the real http.Request object will have a populated scheme, but the middleware’s logic remains flawed because it was never properly exercised during development.

Why This Matters for Pentesters

For those of us on the offensive side, this is a goldmine. When auditing an application, don't just look at the production code. Look at the test files in the repository. If you see developers using httptest.NewRequest with relative paths, there is a high probability that their security middleware is not being tested against the edge cases that matter.

During an engagement, I look for discrepancies between how the application handles requests in a test environment versus production. If the middleware relies on r.URL.Scheme to make security decisions, I immediately test how it behaves when that field is manipulated or missing. If the application is behind a reverse proxy that strips or modifies headers, the gap between the developer's mental model and the actual runtime environment widens, often leading to a bypass.

A Better Way: Fetch Metadata

The industry is moving away from relying on brittle, stateful tokens for CSRF protection. The modern, more robust approach is to use Fetch Metadata Request Headers. These headers, such as Sec-Fetch-Site, Sec-Fetch-Mode, and Sec-Fetch-Dest, are automatically set by the browser and provide the server with the exact context of the request.

Instead of guessing whether a request is cross-site based on a potentially spoofed or missing token, the server can simply inspect the Sec-Fetch-Site header. If it is set to cross-site, the server can reject the request immediately. This approach is significantly harder to bypass because the browser, not the application code, is responsible for setting the metadata.

Implementing this is simple and removes the need for complex, error-prone middleware configurations:

// A simple check for Fetch Metadata
if r.Header.Get("Sec-Fetch-Site") != "same-origin" {
    http.Error(w, "cross-origin request forbidden", http.StatusForbidden)
    return
}

This method is now widely supported across all major browsers. It eliminates the "double-submit" cookie pattern that has plagued web security for decades and provides a clear, declarative way to enforce security boundaries.

Moving Forward

We need to stop treating unit tests as a box-ticking exercise. If your tests are passing because your security controls are being bypassed by the test harness itself, you are not secure. Audit your middleware, check how your test utilities populate request objects, and start looking at Fetch Metadata as a replacement for legacy CSRF patterns.

The next time you are staring at a codebase, ask yourself if the security controls are actually being exercised or if they are just being bypassed by the very tests that are supposed to protect them. The answer is usually in the httptest setup.

Talk Type
talk
Difficulty
intermediate
Category
web security
Has Demo Has Code Tool Released


BSidesSF 2025

94 talks · 2025
Browse conference →
Premium Security Audit

We break your app before they do.

Professional penetration testing and vulnerability assessments by the Kuboid Secure Layer team. Securing your infrastructure at every layer.

Get in Touch
Official Security Partner
kuboid.in