git.iko.soy/typeable/oauth2-proxy
on master help sign in
Merge commit from fork
bdfde72Jan Larwig
CHANGELOG.md+1
@@ -14,6 +14,7 @@
14- [GHSA-5hvv-m4w4-gf6v](https://github.com/oauth2-proxy/oauth2-proxy/security/advisories/GHSA-5hvv-m4w4-gf6v) fix: health check user-agent authentication bypass (@tuunit)
14- [GHSA-5hvv-m4w4-gf6v](https://github.com/oauth2-proxy/oauth2-proxy/security/advisories/GHSA-5hvv-m4w4-gf6v) fix: health check user-agent authentication bypass (@tuunit)
15- [GHSA-7x63-xv5r-3p2x](https://github.com/oauth2-proxy/oauth2-proxy/security/advisories/GHSA-7x63-xv5r-3p2x) fix: authentication bypass via X-Forwarded-Uri header spoofing (@tuunit)
15- [GHSA-7x63-xv5r-3p2x](https://github.com/oauth2-proxy/oauth2-proxy/security/advisories/GHSA-7x63-xv5r-3p2x) fix: authentication bypass via X-Forwarded-Uri header spoofing (@tuunit)
16- [GHSA-c5c4-8r6x-56w3](https://github.com/oauth2-proxy/oauth2-proxy/security/advisories/GHSA-c5c4-8r6x-56w3) fix: email validation bypass via malformed multi-@ email claims (@tuunit)
16- [GHSA-c5c4-8r6x-56w3](https://github.com/oauth2-proxy/oauth2-proxy/security/advisories/GHSA-c5c4-8r6x-56w3) fix: email validation bypass via malformed multi-@ email claims (@tuunit)
17+- [GHSA-pxq7-h93f-9jrg](https://github.com/oauth2-proxy/oauth2-proxy/security/advisories/GHSA-pxq7-h93f-9jrg) fix: fragment evaluation as part of the allowed routes (@tuunit)
17
18
18# V7.15.1
19# V7.15.1
19
20
docs/docs/configuration/overview.md+2−2
@@ -218,8 +218,8 @@ When `--reverse-proxy` is enabled, configure `--trusted-proxy-ip` to the IPs or
218| flag: `--trusted-proxy-ip`<br/>toml: `trusted_proxy_ips` | string \| list | list of IPs or CIDR ranges allowed to supply `X-Forwarded-*` headers when `--reverse-proxy` is enabled. If not set, OAuth2 Proxy preserves backwards compatibility by trusting all source IPs (`0.0.0.0/0`, `::/0`) and logs a warning at startup. Configure this to your reverse proxy addresses to prevent forwarded header spoofing. | `"0.0.0.0/0", "::/0"` |
218| flag: `--trusted-proxy-ip`<br/>toml: `trusted_proxy_ips` | string \| list | list of IPs or CIDR ranges allowed to supply `X-Forwarded-*` headers when `--reverse-proxy` is enabled. If not set, OAuth2 Proxy preserves backwards compatibility by trusting all source IPs (`0.0.0.0/0`, `::/0`) and logs a warning at startup. Configure this to your reverse proxy addresses to prevent forwarded header spoofing. | `"0.0.0.0/0", "::/0"` |
219| flag: `--signature-key`<br/>toml: `signature_key` | string | GAP-Signature request signature key (algorithm:secretkey) | |
219| flag: `--signature-key`<br/>toml: `signature_key` | string | GAP-Signature request signature key (algorithm:secretkey) | |
220| flag: `--skip-auth-preflight`<br/>toml: `skip_auth_preflight` | bool | will skip authentication for OPTIONS requests | false |
220| flag: `--skip-auth-preflight`<br/>toml: `skip_auth_preflight` | bool | will skip authentication for OPTIONS requests | false |
221−| flag: `--skip-auth-regex`<br/>toml: `skip_auth_regex` | string \| list | (DEPRECATED for `--skip-auth-route`) bypass authentication for requests paths that match (may be given multiple times) | |
221+| flag: `--skip-auth-regex`<br/>toml: `skip_auth_regex` | string \| list | (DEPRECATED for `--skip-auth-route`) bypass authentication for requests paths that match (may be given multiple times). Path matching is performed against the normalized path only; fragment identifiers (`#`) and their URL-encoded form (`%23`) are stripped before evaluation. | |
222−| flag: `--skip-auth-route`<br/>toml: `skip_auth_routes` | string \| list | bypass authentication for requests that match the method & path. Format: method=path_regex OR method!=path_regex. For all methods: path_regex OR !=path_regex | |
222+| flag: `--skip-auth-route`<br/>toml: `skip_auth_routes` | string \| list | bypass authentication for requests that match the method & path. Format: method=path_regex OR method!=path_regex. For all methods: path_regex OR !=path_regex. Path matching is performed against the normalized path only; fragment identifiers (`#`) and their URL-encoded form (`%23`) are stripped before evaluation. | |
223| flag: `--skip-jwt-bearer-tokens`<br/>toml: `skip_jwt_bearer_tokens` | bool | will skip requests that have verified JWT bearer tokens (the token must have [`aud`](https://en.wikipedia.org/wiki/JSON_Web_Token#Standard_fields) that matches this client id or one of the extras from `extra-jwt-issuers`) | false |
223| flag: `--skip-jwt-bearer-tokens`<br/>toml: `skip_jwt_bearer_tokens` | bool | will skip requests that have verified JWT bearer tokens (the token must have [`aud`](https://en.wikipedia.org/wiki/JSON_Web_Token#Standard_fields) that matches this client id or one of the extras from `extra-jwt-issuers`) | false |
224| flag: `--skip-provider-button`<br/>toml: `skip_provider_button` | bool | will skip sign-in-page to directly reach the next step: oauth/start | false |
224| flag: `--skip-provider-button`<br/>toml: `skip_provider_button` | bool | will skip sign-in-page to directly reach the next step: oauth/start | false |
225| flag: `--ssl-insecure-skip-verify`<br/>toml: `ssl_insecure_skip_verify` | bool | skip validation of certificates presented when using HTTPS providers | false |
225| flag: `--ssl-insecure-skip-verify`<br/>toml: `ssl_insecure_skip_verify` | bool | skip validation of certificates presented when using HTTPS providers | false |
oauthproxy_test.go+52
@@ -2679,9 +2679,11 @@ func TestAllowedRequest(t *testing.T) {
2679 }
2679 }
2680 opts.SkipAuthRegex = []string{
2680 opts.SkipAuthRegex = []string{
2681 "^/skip/auth/regex$",
2681 "^/skip/auth/regex$",
2682+ "^/public/.*/endpoint$",
2682 }
2683 }
2683 opts.SkipAuthRoutes = []string{
2684 opts.SkipAuthRoutes = []string{
2684 "GET=^/skip/auth/routes/get",
2685 "GET=^/skip/auth/routes/get",
2686+ "^/foo/.*/bar$",
2685 }
2687 }
2686 err := validation.Validate(opts)
2688 err := validation.Validate(opts)
2687 assert.NoError(t, err)
2689 assert.NoError(t, err)
@@ -2714,6 +2716,18 @@ func TestAllowedRequest(t *testing.T) {
2714 url: "/wrong/denied",
2716 url: "/wrong/denied",
2715 allowed: false,
2717 allowed: false,
2716 },
2718 },
2719+ {
2720+ name: "Regex allowed with fragment-free path",
2721+ method: "GET",
2722+ url: "/public/legit/endpoint",
2723+ allowed: true,
2724+ },
2725+ {
2726+ name: "Regex denied when path contains encoded fragment suffix",
2727+ method: "GET",
2728+ url: "/public/secret%23/endpoint",
2729+ allowed: false,
2730+ },
2717 {
2731 {
2718 name: "Route allowed",
2732 name: "Route allowed",
2719 method: "GET",
2733 method: "GET",
@@ -2738,6 +2752,18 @@ func TestAllowedRequest(t *testing.T) {
2738 url: "/skip/auth/routes/wrong/path",
2752 url: "/skip/auth/routes/wrong/path",
2739 allowed: false,
2753 allowed: false,
2740 },
2754 },
2755+ {
2756+ name: "Route allowed with fragment-free path",
2757+ method: "GET",
2758+ url: "/foo/public/bar",
2759+ allowed: true,
2760+ },
2761+ {
2762+ name: "Route denied when path contains encoded fragment suffix",
2763+ method: "GET",
2764+ url: "/foo/secret%23/bar",
2765+ allowed: false,
2766+ },
2741 }
2767 }
2742
2768
2743 for _, tc := range testCases {
2769 for _, tc := range testCases {
@@ -2778,9 +2804,11 @@ func TestAllowedRequestWithForwardedUriHeader(t *testing.T) {
2778 }
2804 }
2779 opts.SkipAuthRegex = []string{
2805 opts.SkipAuthRegex = []string{
2780 "^/skip/auth/regex$",
2806 "^/skip/auth/regex$",
2807+ "^/public/.*/endpoint$",
2781 }
2808 }
2782 opts.SkipAuthRoutes = []string{
2809 opts.SkipAuthRoutes = []string{
2783 "GET=^/skip/auth/routes/get",
2810 "GET=^/skip/auth/routes/get",
2811+ "^/foo/.*/bar$",
2784 }
2812 }
2785 err := validation.Validate(opts)
2813 err := validation.Validate(opts)
2786 assert.NoError(t, err)
2814 assert.NoError(t, err)
@@ -2813,6 +2841,18 @@ func TestAllowedRequestWithForwardedUriHeader(t *testing.T) {
2813 url: "/wrong/denied",
2841 url: "/wrong/denied",
2814 allowed: false,
2842 allowed: false,
2815 },
2843 },
2844+ {
2845+ name: "Regex allowed with fragment-free path",
2846+ method: "GET",
2847+ url: "/public/legit/endpoint",
2848+ allowed: true,
2849+ },
2850+ {
2851+ name: "Regex denied when X-Forwarded-Uri contains an encoded fragment suffix",
2852+ method: "GET",
2853+ url: "/public/secret%23/endpoint",
2854+ allowed: false,
2855+ },
2816 {
2856 {
2817 name: "Route allowed",
2857 name: "Route allowed",
2818 method: "GET",
2858 method: "GET",
@@ -2837,6 +2877,18 @@ func TestAllowedRequestWithForwardedUriHeader(t *testing.T) {
2837 url: "/skip/auth/routes/wrong/path",
2877 url: "/skip/auth/routes/wrong/path",
2838 allowed: false,
2878 allowed: false,
2839 },
2879 },
2880+ {
2881+ name: "Route allowed with fragment-free path",
2882+ method: "GET",
2883+ url: "/foo/public/bar",
2884+ allowed: true,
2885+ },
2886+ {
2887+ name: "Route denied when X-Forwarded-Uri contains an encoded fragment suffix",
2888+ method: "GET",
2889+ url: "/foo/secret%23/bar",
2890+ allowed: false,
2891+ },
2840 }
2892 }
2841
2893
2842 for _, tc := range testCases {
2894 for _, tc := range testCases {
pkg/requests/util/util.go+15−3
@@ -47,16 +47,28 @@ func GetRequestURI(req *http.Request) string {
47
47
48// GetRequestPath returns the request URI or X-Forwarded-Uri if present and the
48// GetRequestPath returns the request URI or X-Forwarded-Uri if present and the
49// request came from a trusted reverse proxy but always strips the query
49// request came from a trusted reverse proxy but always strips the query
50−// parameters and only returns the pure path.
50+// parameters and fragment suffixes and only returns the pure path.
51func GetRequestPath(req *http.Request) string {
51func GetRequestPath(req *http.Request) string {
52− uri := GetRequestURI(req)
52+ uri := stripRequestFragment(GetRequestURI(req))
53
53
54 // Parse URI and return only the path component
54 // Parse URI and return only the path component
55 if parsedURL, err := url.Parse(uri); err == nil {
55 if parsedURL, err := url.Parse(uri); err == nil {
56− return parsedURL.Path
56+ return stripRequestFragment(parsedURL.Path)
57 }
57 }
58
58
59 // Fallback: strip query parameters manually
59 // Fallback: strip query parameters manually
60+ return stripRequestQuery(uri)
61+}
62+
63+func stripRequestFragment(uri string) string {
64+ if idx := strings.Index(uri, "#"); idx != -1 {
65+ return uri[:idx]
66+ }
67+
68+ return uri
69+}
70+
71+func stripRequestQuery(uri string) string {
60 if idx := strings.Index(uri, "?"); idx != -1 {
72 if idx := strings.Index(uri, "?"); idx != -1 {
61 return uri[:idx]
73 return uri[:idx]
62 }
74 }
pkg/requests/util/util_test.go+22
@@ -152,6 +152,23 @@ var _ = Describe("Util Suite", func() {
152 Expect(util.GetRequestPath(req)).To(Equal(uriNoQueryParams))
152 Expect(util.GetRequestPath(req)).To(Equal(uriNoQueryParams))
153 })
153 })
154
154
155+ It("drops fragment content from a parsed request path", func() {
156+ // Simulate net/http ParseRequestURI preserving '#' in URL.Path.
157+ req.URL.Path = "/foo/secret#/bar"
158+ req.URL.RawPath = "/foo/secret%23/bar"
159+ Expect(util.GetRequestPath(req)).To(Equal("/foo/secret"))
160+ })
161+
162+ It("drops fragment-like suffixes from encoded number signs", func() {
163+ req = httptest.NewRequest(
164+ http.MethodGet,
165+ fmt.Sprintf("%s://%s/foo/secret%%23/bar?query=param", proto, host),
166+ nil,
167+ )
168+ req = middleware.AddRequestScope(req, &middleware.RequestScope{})
169+ Expect(util.GetRequestPath(req)).To(Equal("/foo/secret"))
170+ })
171+
155 It("ignores X-Forwarded-Uri and returns the URI (without query params)", func() {
172 It("ignores X-Forwarded-Uri and returns the URI (without query params)", func() {
156 req.Header.Add("X-Forwarded-Uri", "/some/other/path?query=param")
173 req.Header.Add("X-Forwarded-Uri", "/some/other/path?query=param")
157 Expect(util.GetRequestPath(req)).To(Equal(uriNoQueryParams))
174 Expect(util.GetRequestPath(req)).To(Equal(uriNoQueryParams))
@@ -175,6 +192,11 @@ var _ = Describe("Util Suite", func() {
175 req.Header.Add("X-Forwarded-Uri", "/some/other/path?query=param")
192 req.Header.Add("X-Forwarded-Uri", "/some/other/path?query=param")
176 Expect(util.GetRequestPath(req)).To(Equal("/some/other/path"))
193 Expect(util.GetRequestPath(req)).To(Equal("/some/other/path"))
177 })
194 })
195+
196+ It("drops fragment-like suffixes from the X-Forwarded-Uri", func() {
197+ req.Header.Add("X-Forwarded-Uri", "/foo/secret%23/bar?query=param")
198+ Expect(util.GetRequestPath(req)).To(Equal("/foo/secret"))
199+ })
178 })
200 })
179 })
201 })
180
202