Improper input validation using regular expressions without anchors
ID |
swift.missing_regex_anchor |
Severity |
low |
Resource |
Incorrect validation |
Language |
Swift |
Tags |
CWE:777, NIST.SP.800-53, OWASP:2021:A3 |
Description
Sanitizing untrusted input with regular expressions is a common technique for input validation. However, without proper anchors (^ for start of string, $ for end of string), the regular expression will match anywhere in the input string. This allows malicious actors to embed one of the allowed patterns in an unexpected location, potentially bypassing security checks.
Regular expressions without anchors perform partial matching, which means they will find matches anywhere within the input string. For security-critical validation (such as URL validation or hostname checking), this behavior can lead to bypasses where attackers embed valid-looking patterns within malicious content.
Rationale
The following vulnerabilities can occur when regex anchors are missing:
-
Completely missing anchors:
When a regex pattern has no anchors at all, it will match anywhere in the input string:
import Foundation
func validateRedirectUrl(_ urlString: String) -> Bool {
let components = URLComponents(string: urlString)
let redirectParam = components?.queryItems?.first(where: { $0.name == "url" })
// BAD: No anchors - matches anywhere in the string
let regex = try! Regex(#"https?://www\.example\.com"#)
if let match = redirectParam?.value?.firstMatch(of: regex) {
return true // URL is "trusted"
}
return false
}
An attacker can bypass this validation with:
http://evil.com/?x=http://www.example.com/
The regex matches the embedded http://www.example.com in the query parameter, even though the actual URL being validated points to evil.com.
-
Misleading anchor precedence:
Regular expression alternation (|) has lower precedence than anchors, which can lead to unexpected behavior:
// BAD: Only the first alternative is anchored
let pattern = "^trusted\\.com|example\\.org|secure\\.net"
let regex = try! NSRegularExpression(pattern: pattern, options: [])
// This regex is parsed as: (^trusted\.com)|(example\.org)|(secure\.net)
// Only "trusted.com" requires being at the start
// "example.org" and "secure.net" can appear anywhere
An attacker can bypass with:
evil.com/example.org
The pattern matches example.org even though it’s not at the start of the string.
-
URL validation without start anchor:
When validating URLs, missing a start anchor allows embedding the trusted domain in unexpected locations:
// BAD: No start anchor
let pattern = "https://api\\.trusted\\.com/"
if urlString.range(of: pattern, options: .regularExpression) != nil {
// URL is "trusted"
}
An attacker can bypass with:
https://evil.com/redirect?to=https://api.trusted.com/
Remediation
To prevent these vulnerabilities, always use proper anchors when validating input with regular expressions:
-
Use start and end anchors
For complete string matching, use both ^ (start) and $ (end) anchors:
// GOOD: Both anchors ensure complete match
let regex = try! Regex(#"^https?://www\.example\.com$"#)
if let match = redirectParam?.value?.firstMatch(of: regex) {
// URL is truly trusted - no bypass possible
}
-
Use start anchor for URL Validation
For URL validation, at minimum use a start anchor ^:
// GOOD: Start anchor prevents embedding
let regex = try! Regex(#"^https?://www\.example\.com"#)
if let match = urlString.firstMatch(of: regex) {
// URL genuinely starts with the trusted domain
}
-
Properly Anchor Alternatives
When using alternation, group alternatives and anchor the entire group:
// GOOD: All alternatives properly anchored
let pattern = "^(trusted\\.com|example\\.org|secure\\.net)$"
let regex = try! NSRegularExpression(pattern: pattern, options: [])
-
Use
wholeMatch()for complete matching
Swift’s wholeMatch(of:) method implicitly adds ^ and $ anchors:
// GOOD: wholeMatch implicitly requires full match
let regex = try! Regex(#"https?://www\.example\.com"#)
if let match = urlString.wholeMatch(of: regex) {
// URL must completely match - safe
}
Note: firstMatch(of:) and matches(of:) perform partial matching and require explicit anchors.
-
Alternative: Use URLComponents for URL validation
For URL validation specifically, consider using Swift’s built-in URLComponents:
// GOOD: Use proper URL parsing instead of regex
if let components = URLComponents(string: urlString),
let host = components.host,
host == "www.example.com" || host.hasSuffix(".example.com"),
components.scheme == "https" {
// URL is trusted
}
This approach is more robust than regex-based validation and handles edge cases correctly.
The following table summarizes the need for anchor in regex patterns in common Swift methods:
| Method | Requires Anchors? | Notes |
|---|---|---|
|
Yes |
Always performs partial matching |
|
Yes |
Partial matching - finds first occurrence |
|
Yes |
Partial matching - finds all occurrences |
|
No |
Implicitly anchored with |
|
Yes |
Partial matching |
|
Depends |
Use anchors for validation, not needed for replacement |
References
-
CWE-777: Regular Expression without Anchors.