| @@ -33,10 +33,12 @@ | ||
| 33 | 33 | $state = $request->get_param('state'); |
| 34 | 34 | $site_url = $this->getDomain(get_site_url()); |
| 35 | 35 | $state_domain = $this->getDomain($state); |
| 36 | 36 | |
| 37 | - if ($site_url !== $state_domain) { | |
| 38 | - return new WP_Error('404', 'Invalid redirect URL: ' . $state_domain); | |
| 37 | + // the refused domain is caller-supplied; echoing it back reflects attacker | |
| 38 | + // input into the response of a public endpoint | |
| 39 | + if ('' === $state_domain || $site_url !== $state_domain) { | |
| 40 | + return new WP_Error('404', 'Invalid redirect URL'); | |
| 39 | 41 | } |
| 40 | 42 | |
| 41 | 43 | $params = $request->get_params(); |
| 42 | 44 | unset($params['rest_route'], $params['state']); |
| @@ -49,9 +51,14 @@ | ||
| 49 | 51 | } |
| 50 | 52 | |
| 51 | 53 | private function getDomain($url) |
| 52 | 54 | { |
| 53 | - $parsed_url = wp_parse_url($url); | |
| 55 | + // these endpoints are public: a missing or non-URL state must not raise | |
| 56 | + // notices, it must simply fail the same-origin comparison | |
| 57 | + $parsed_url = is_string($url) && '' !== $url ? wp_parse_url($url) : false; | |
| 58 | + if (!is_array($parsed_url) || empty($parsed_url['scheme']) || empty($parsed_url['host'])) { | |
| 59 | + return ''; | |
| 60 | + } | |
| 54 | 61 | $domain = $parsed_url['scheme'] . '://' . $parsed_url['host']; |
| 55 | 62 | $domain .= empty($parsed_url['port']) ? null : ':' . $parsed_url['port']; |
| 56 | 63 | return $domain; |
| 57 | 64 | } |