-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Prevent SSO Redirects to other origins #33853
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,6 +27,7 @@ import ( | |
| "net/url" | ||
| "regexp" | ||
| "strconv" | ||
| "strings" | ||
|
|
||
| "github.com/gravitational/roundtrip" | ||
| "github.com/gravitational/trace" | ||
|
|
@@ -249,14 +250,24 @@ func RewritePaths(next http.Handler, rewrites ...RewritePair) http.Handler { | |
| }) | ||
| } | ||
|
|
||
| // SafeRedirect performs a relative redirect to the URI part of the provided redirect URL | ||
| func SafeRedirect(w http.ResponseWriter, r *http.Request, redirectURL string) error { | ||
| // OriginLocalRedirectURI will take an incoming URL including optionally the host and schema and provide the URI | ||
| // associated with the URL. Additionally, it will ensure that the URI does not include any techniques potentially | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On first read I was very confused by what "the URI associated with the URL" meant. Would it make more sense to call this function something like
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unclear to me if
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, yeah I see that it's doing quite a bit more than that. I wonder if
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No strong preference on my end. I put it in |
||
| // used to redirect to a different origin. | ||
| func OriginLocalRedirectURI(redirectURL string) (string, error) { | ||
| parsedURL, err := url.Parse(redirectURL) | ||
| if err != nil { | ||
| return trace.Wrap(err) | ||
| return "", trace.Wrap(err) | ||
| } else if parsedURL.IsAbs() && (parsedURL.Scheme != "http" && parsedURL.Scheme != "https") { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is actually not redundant. It's possible for a non-absolute url to be provided, one which has no scheme. Removing this will cause some of the existing test cases to fail.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah my mistake |
||
| return "", trace.BadParameter("Invalid scheme: %s", parsedURL.Scheme) | ||
| } | ||
| http.Redirect(w, r, parsedURL.RequestURI(), http.StatusFound) | ||
| return nil | ||
|
|
||
| resultURI := parsedURL.RequestURI() | ||
| if len(resultURI) > 1 && resultURI[0] == '/' && resultURI[1] == '/' { | ||
|
jentfoo marked this conversation as resolved.
Outdated
|
||
| return "", trace.BadParameter("Invalid double slash redirect") | ||
| } else if strings.Contains(resultURI, "@") { | ||
| return "", trace.BadParameter("Basic Auth not allowed in redirect") | ||
| } | ||
| return resultURI, nil | ||
| } | ||
|
|
||
| // ResponseStatusRecorder is an http.ResponseWriter that records the response status code. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -407,3 +407,59 @@ func TestSetRedirectPageContentSecurityPolicy(t *testing.T) { | |
| require.Contains(t, actualCsp, expectedCspSubString) | ||
| } | ||
| } | ||
|
|
||
| func TestOriginLocalRedirectURI(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| testCases := []struct { | ||
| name string | ||
| input string | ||
| expected string | ||
| errCheck require.ErrorAssertionFunc | ||
| }{ | ||
| { | ||
| name: "empty", | ||
| input: "", | ||
| expected: "/", | ||
| errCheck: require.NoError, | ||
| }, | ||
| { | ||
| name: "simple path", | ||
| input: "/foo", | ||
| expected: "/foo", | ||
| errCheck: require.NoError, | ||
| }, | ||
| { | ||
| name: "host only", | ||
| input: "https://localhost", | ||
| expected: "/", | ||
| errCheck: require.NoError, | ||
| }, | ||
| { | ||
| name: "host and simple path", | ||
| input: "https://localhost/bar", | ||
| expected: "/bar", | ||
| errCheck: require.NoError, | ||
| }, | ||
| { | ||
| name: "double slash redirect with host", | ||
| input: "https://localhost//goteleport.com/", | ||
| expected: "", | ||
| errCheck: require.Error, | ||
| }, | ||
| { | ||
| name: "basic auth redirect with host", | ||
| input: "https://localhost/@goteleport.com/", | ||
| expected: "", | ||
| errCheck: require.Error, | ||
| }, | ||
|
jentfoo marked this conversation as resolved.
|
||
| } | ||
|
|
||
| for _, tc := range testCases { | ||
| t.Run(tc.name, func(t *testing.T) { | ||
| result, err := OriginLocalRedirectURI(tc.input) | ||
| require.Equal(t, tc.expected, result) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it is not usual for the non-error parameter to be relevant when there is an error result. I'd suggest moving this into the non-error if block, and remove the if you want the returned URI to be the empty string in error cases, I'd document that in the function's doc comment. |
||
| tc.errCheck(t, err) | ||
| }) | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.