Skip to content

Commit 30ec3e6

Browse files
authored
docs: warn about underscore header spoofing in NewRequestWithContext (#2460)
## What Document that `NewRequestWithContext` does not strip request headers whose name contains an underscore. ## Why CGI maps dashes to underscores (`Foo-Bar` becomes `HTTP_FOO_BAR`), so a client-supplied `Foo_Bar` header is indistinguishable from a legitimate `Foo-Bar` in `$_SERVER` and can spoof it. This affects any such header an application or upstream proxy trusts (forwarded-for, auth, etc.). The Caddy-based server and reverse proxies like nginx (`underscores_in_headers off`) already drop these. Callers using the Go API directly must do it themselves unless they explicitly whitelist them. ## Changes - Doc comment on `NewRequestWithContext` explaining the risk and mitigation. - `ExampleServeHTTP` now drops underscore headers before building the request.
1 parent 64bb386 commit 30ec3e6

2 files changed

Lines changed: 19 additions & 0 deletions

File tree

context.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,15 @@ func newFrankenPHPContext() *frankenPHPContext {
6161
}
6262

6363
// NewRequestWithContext creates a new FrankenPHP request context.
64+
//
65+
// FrankenPHP does not strip request headers whose name contains an underscore.
66+
// Because CGI maps dashes to underscores ("Foo-Bar" becomes the HTTP_FOO_BAR
67+
// variable), a client-supplied "Foo_Bar" header is indistinguishable from the
68+
// legitimate "Foo-Bar" in $_SERVER and can spoof it. This affects any such
69+
// header an application or upstream proxy trusts (forwarded-for, auth, etc.).
70+
// Drop headers containing an underscore before calling this function, unless
71+
// you explicitly need (and whitelist) them. The Caddy-based server and reverse
72+
// proxies such as nginx (underscores_in_headers off) already do this.
6473
func NewRequestWithContext(r *http.Request, opts ...RequestOption) (*http.Request, error) {
6574
fc := newFrankenPHPContext()
6675
fc.request = r

frankenphp_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -810,6 +810,16 @@ func ExampleServeHTTP() {
810810
defer frankenphp.Shutdown()
811811

812812
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
813+
// Drop headers whose name contains an underscore: CGI maps dashes to
814+
// underscores, so "Foo_Bar" would be indistinguishable from "Foo-Bar"
815+
// in $_SERVER and could spoof any header an app or proxy trusts.
816+
// Whitelist any you genuinely need.
817+
for name := range r.Header {
818+
if strings.ContainsRune(name, '_') {
819+
delete(r.Header, name)
820+
}
821+
}
822+
813823
req, err := frankenphp.NewRequestWithContext(r, frankenphp.WithRequestDocumentRoot("/path/to/document/root", false))
814824
if err != nil {
815825
panic(err)

0 commit comments

Comments
 (0)