| @@ -187,8 +187,14 @@ | ||
| 187 | 187 | if (empty($trimmed_dest)) { |
| 188 | 188 | $validation_errors[] = 'Destination URL is required for this redirect type.'; |
| 189 | 189 | } elseif (!$this->is_valid_url($trimmed_dest)) { |
| 190 | 190 | $validation_errors[] = 'Invalid destination URL format. URLs should start with / for relative paths or be complete URLs.'; |
| 191 | + } elseif (!Metasync_Redirection_Validator::is_safe_destination_syntax($trimmed_dest)) { | |
| 192 | + // Browsers treat backslashes as path separators, so '/\evil.com' | |
| 193 | + // passes wp_validate_redirect yet navigates off-site; a leading | |
| 194 | + // '//' is a protocol-relative host. Neither is a valid internal | |
| 195 | + // destination regardless of the external-redirects setting. | |
| 196 | + $validation_errors[] = 'Destination URL contains invalid characters.'; | |
| 191 | 197 | } elseif (!get_option('metasync_allow_external_redirects', 0) && wp_validate_redirect($trimmed_dest, '') !== $trimmed_dest) { |
| 192 | 198 | $validation_errors[] = 'Destination URL must point to this site. External redirect destinations are not allowed. Enable "Allow External Redirects" in the Redirections settings to permit off-site redirects.'; |
| 193 | 199 | } |
| 194 | 200 | } |
| @@ -234,12 +240,13 @@ | ||
| 234 | 240 | $this->safe_redirect(admin_url('admin.php?page=' . Metasync_Admin::$page_slug . '-redirections')); |
| 235 | 241 | return; |
| 236 | 242 | } |
| 237 | 243 | |
| 238 | - // Reject patterns with nested quantifiers that could cause ReDoS | |
| 244 | + // Reject patterns with nested quantification that could cause ReDoS, | |
| 245 | + // including alternation blowup ('((a|a)*)*') the older guard missed. | |
| 239 | 246 | $raw_check = preg_replace('/^\S(.*)\S[a-zA-Z]*$/', '$1', $regex_pattern); |
| 240 | - if (preg_match('/(\([^)]*[+*][^)]*\))[+*?{]|(\[[^\]]*\])[+*][+*?{]/', $raw_check)) { | |
| 241 | - set_transient('metasync_redirection_error_' . $uid, 'Regex pattern contains potentially unsafe nested quantifiers.', 45); | |
| 247 | + if (!Metasync_Redirection_Validator::is_regex_safe($raw_check)) { | |
| 248 | + set_transient('metasync_redirection_error_' . $uid, 'Regex pattern is too long or contains potentially unsafe nested quantifiers (including nested alternation).', 45); | |
| 242 | 249 | $this->safe_redirect(admin_url('admin.php?page=' . Metasync_Admin::$page_slug . '-redirections')); |
| 243 | 250 | return; |
| 244 | 251 | } |
| 245 | 252 | |
| @@ -272,8 +279,9 @@ | ||
| 272 | 279 | |
| 273 | 280 | // Loop detection: refuse to persist a chain that would resolve back to any source |
| 274 | 281 | if (!in_array($redirect_type, [410, 451])) { |
| 275 | 282 | require_once dirname(__FILE__, 2) . '/redirections/class-metasync-redirection.php'; |
| 283 | + require_once dirname(__FILE__, 2) . '/redirections/class-metasync-redirection-validator.php'; | |
| 276 | 284 | $redirection_helper = new Metasync_Redirection($this->db_redirection); |
| 277 | 285 | foreach (array_keys($sources_from) as $source_url) { |
| 278 | 286 | $loop_error = $redirection_helper->validate_no_loop($source_url, $destination_url); |
| 279 | 287 | if ($loop_error !== null) { |