123 lines
4.5 KiB
Go
123 lines
4.5 KiB
Go
// Copyright 2015 Matthew Holt and The Caddy Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
//go:build windows
|
|
|
|
package fastcgi
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/caddyserver/caddy/v2"
|
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// On Windows the path matchers additionally fold backslashes into separators and
|
|
// trim per-component trailing dots and spaces (see caddyhttp.NormalizedPath),
|
|
// so the transport must do the same or the dot-segment bypass simply reappears
|
|
// spelled with backslashes. Backslash dot segments are rejected outright because
|
|
// the "%" path matchers read them as "%2f..%2f", which they do not resolve.
|
|
func TestBuildEnvWindowsSeparatorsCannotSelectAnotherScript(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
newRequest := func(target string) *http.Request {
|
|
r := httptest.NewRequest(http.MethodGet, target, nil)
|
|
ctx := context.WithValue(r.Context(), caddy.ReplacerCtxKey, caddy.NewReplacer())
|
|
ctx = context.WithValue(ctx, caddyhttp.VarsCtxKey, map[string]any{})
|
|
ctx = context.WithValue(ctx, caddyhttp.OriginalRequestCtxKey, *r)
|
|
return r.WithContext(ctx)
|
|
}
|
|
|
|
for _, tc := range []struct {
|
|
name string
|
|
target string
|
|
wantErr bool
|
|
expectedScriptName string
|
|
expectedPathInfo string
|
|
}{
|
|
{"backslash dot segments", `/admin.php\..\other.php`, true, "", ""},
|
|
{"backslash, two levels", `/admin.php\..\..\other.php`, true, "", ""},
|
|
{"mixed separators", `/admin.php\../other.php`, true, "", ""},
|
|
{"escaped backslash dot segments", "/admin.php%5C..%5Cother.php", true, "", ""},
|
|
{"backslash separator", `/dir\other.php\a`, false, "/dir/other.php", "/a"},
|
|
{"escaped backslash separator", "/dir%5Cother.php%5Ca", false, "/dir/other.php", "/a"},
|
|
// Per-component trailing dot/space folding is Windows-only and is not
|
|
// something CleanPath does, so this case fails unless the transport
|
|
// applies the same normalization the matchers do.
|
|
{"trailing dot in a path-info component", "/other.php/a./b", false, "/other.php", "/a/b"},
|
|
{"trailing space in a path-info component", "/other.php/a%20/b", false, "/other.php", "/a/b"},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
tr := Transport{Root: `C:\srv`, SplitPath: []string{".php"}}
|
|
env, err := tr.buildEnv(newRequest(tc.target))
|
|
if tc.wantErr {
|
|
var herr caddyhttp.HandlerError
|
|
require.ErrorAs(t, err, &herr)
|
|
assert.Equal(t, http.StatusBadRequest, herr.StatusCode)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, tc.expectedScriptName, env["SCRIPT_NAME"],
|
|
"SCRIPT_NAME must come from the normalized, cleaned path")
|
|
assert.Equal(t, tc.expectedPathInfo, env["PATH_INFO"],
|
|
"PATH_INFO must come from the normalized, cleaned path")
|
|
assert.NotContains(t, env["SCRIPT_FILENAME"], "admin.php",
|
|
"a pre-normalization path component must never become the executed script")
|
|
})
|
|
}
|
|
}
|
|
|
|
// An 8.3 alias ("/UPLOAD~1.PHP" for "uploadedpayload.phpx") or an alternate
|
|
// data stream ("a.txt:b.php") reaches a non-PHP file with a script-looking path.
|
|
func TestBuildEnvWindowsScriptAliases(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
for _, tc := range []struct {
|
|
target string
|
|
wantErr bool
|
|
}{
|
|
{"/UPLOAD~1.PHP", true},
|
|
{"/PROTEC~1/index.php", true},
|
|
{"/a.txt:b.php", true},
|
|
{"/a.txt%3Ab.php", true},
|
|
{"/index.php/urn:isbn:0451450523", false},
|
|
{"/index.php/PROTEC~1", false},
|
|
{"/index.php", false},
|
|
} {
|
|
t.Run(tc.target, func(t *testing.T) {
|
|
r := httptest.NewRequest(http.MethodGet, tc.target, nil)
|
|
ctx := context.WithValue(r.Context(), caddy.ReplacerCtxKey, caddy.NewReplacer())
|
|
ctx = context.WithValue(ctx, caddyhttp.VarsCtxKey, map[string]any{})
|
|
ctx = context.WithValue(ctx, caddyhttp.OriginalRequestCtxKey, *r)
|
|
|
|
tr := Transport{Root: `C:\srv`, SplitPath: []string{".php"}}
|
|
_, err := tr.buildEnv(r.WithContext(ctx))
|
|
if !tc.wantErr {
|
|
require.NoError(t, err)
|
|
return
|
|
}
|
|
|
|
var herr caddyhttp.HandlerError
|
|
require.ErrorAs(t, err, &herr)
|
|
assert.Equal(t, http.StatusBadRequest, herr.StatusCode)
|
|
})
|
|
}
|
|
}
|