# Bypassing Regex Restrictions

> Techniques to bypass regex-based input validation in web apps, including pattern manipulation, encoding tricks, and edge cases to exploit filtering weaknesses.

- **URL:** https://hackindex.io/services/web/exploitation/regex-restriction-bypass
- **Path:** HTTP/HTTPS > Exploitation
- **Up:** [Exploitation](https://hackindex.io/services/web/exploitation.md)
- **Published:** 2026-09-13
- **Updated:** 2026-09-13

Web applications use regular expressions to validate and sanitize user input: URL patterns, filename filters, IP address checks, and input validation rules. Flawed regex patterns create bypass opportunities that let an attacker pass malicious input through what appears to be a robust validation layer. The most common mistakes are anchoring errors, newline injection, incorrect flag usage, and differences between regex engines. For bypassing command injection filters specifically, see [command injection bypasses](https://hackindex.io/services/web/exploitation/command-injection-bypasses). For XSS filter evasion, see [XSS filter bypass](https://hackindex.io/services/web/exploitation/xss-filter-bypass).

Bypass techniques covered on this page:

- Anchoring issues: missing `^` or `$`, trailing newline behavior
- Newline injection: `.` does not match `\n` by default
- Multiline flag abuse: `/m` changes `^` and `$` to per-line
- `re.match` vs `re.search` vs `re.fullmatch` in Python
- PCRE vs POSIX differences
- Double URL encoding and null byte bypasses

## Anchoring Issues

The most common regex bypass. When a pattern uses `^` without `$` (or vice versa), the regex only validates part of the input. The attacker appends or prepends payload after the validated portion.

### Missing End Anchor

Pattern: `/^[a-zA-Z0-9]+/`

Intent: allow only alphanumeric input. Reality: validates that the string starts with alphanumeric but does not check what follows.

```php
# PHP example: missing $ anchor
if (preg_match('/^[a-zA-Z0-9]+/', $input)) {
    // Passes validation
    system("lookup " . $input);
}

// Bypass: input = "validuser; id"
// Regex matches "validuser" at the start, ignores "; id"
```

### Missing Start Anchor

Pattern: `/[a-zA-Z0-9]+$/`

Intent: alphanumeric only. Reality: validates the end but allows anything before it.

```php
// Bypass: input = "; cat /etc/passwd; echo validuser"
// Regex matches "validuser" at the end
```

### Dollar Sign Does Not Mean End of String

In most regex engines, `$` matches the position before a trailing newline, not strictly the end of the string. This means `/^safe$/` matches `safe\n` (with a trailing newline). In PHP:

```php
// PHP: $ matches before trailing newline
preg_match('/^[a-z]+$/', "safe\n");  // Returns 1 (match!)
preg_match('/^[a-z]+$/', "safe\nmalicious");  // Returns 0

// Fix: use \z instead of $ for true end-of-string
preg_match('/^[a-z]+\z/', "safe\n");  // Returns 0 (no match)
```

Exploitation: if an application checks a filename with `/^[a-z]+\.txt$/` and then uses it in an `include()`, submitting `safe.txt\n` passes the regex. Depending on how the language handles the null byte or newline in the file path, this may allow path traversal or inclusion of unexpected files.

## Newline Injection

The dot character (`.`) matches any character except newline (`\n`) by default in most regex engines. Injecting a newline into the input breaks the pattern, causing the regex to match only the part before the newline while the rest passes through unvalidated.

```python
# Pattern intended to block "script" anywhere in input
if not re.search(r'script', user_input):
    render_html(user_input)

# Bypass: inject a newline in the middle
# input = "scr\nipt" - regex does not find "script" as a contiguous match
# But the HTML renderer may ignore the newline and execute it
```

URL-encoded newline: `%0a` (LF) or `%0d%0a` (CRLF). When the application decodes URL encoding before applying the regex, the newline is present in the string being matched.

Another angle: if a regex validates an entire block of text but `.` does not match newlines, the regex effectively only validates the first line. Payload on line 2 goes unchecked.

## Multiline Flag Abuse

The multiline flag (`/m` in most engines, `re.MULTILINE` in Python) changes the behavior of `^` and `$`: instead of matching the start/end of the entire string, they match the start/end of each line. If a developer uses the multiline flag thinking it enables multi-line input support but their regex uses `^...$`, the pattern validates each line independently rather than the whole input.

```python
# Python: multiline flag makes ^ and $ per-line
import re

pattern = re.compile(r'^[a-z]+$', re.MULTILINE)

pattern.search("safe\n<script>alert(1)</script>")  # Matches! ("safe" on line 1)
pattern.search("<script>alert(1)</script>")  # No match

# Without MULTILINE:
re.search(r'^[a-z]+$', "safe\n<script>alert(1)</script>")  # No match
```

The attacker puts a valid value on the first line and their payload on a subsequent line. The regex matches the first line and the application processes the entire multi-line input.

## Python: match vs search vs fullmatch

Python has three regex matching functions with different behavior, and using the wrong one creates bypasses:

```python
import re

input_str = "admin' OR '1'='1"

# re.match: anchored at START only, not end
re.match(r'[a-z]+', input_str)     # Matches "admin" (ignores rest)

# re.search: finds pattern ANYWHERE in string
re.search(r'^[a-z]+$', input_str)  # No match (correct rejection)

# re.fullmatch: anchored at BOTH start and end (Python 3.4+)
re.fullmatch(r'[a-z]+', input_str) # No match (correct rejection)

# Common mistake: using re.match without $ anchor
if re.match(r'[a-z]+', input_str):  # Returns truthy match object!
    query = f"SELECT * FROM users WHERE name='{input_str}'"  # SQLi
```

`re.match` only checks the beginning of the string. Code that uses `re.match(pattern, input)` without a `$` anchor in the pattern is equivalent to a regex with `^` only. Use `re.fullmatch()` for input validation.

## PCRE vs POSIX Differences

PHP supports both PCRE (`preg_match`) and POSIX (`ereg`, deprecated) regex. Other languages have their own engine quirks:

- **PHP PCRE**: `$` matches before trailing newline by default. Use `\z` for true end-of-string. The `/D` modifier makes `$` match only at end-of-string
- **JavaScript**: `.` does not match newline. No `\z`, but `$` without `/m` does match end-of-string (except some edge cases with `\n`). The `/s` flag (dotAll) makes `.` match newlines
- **Java**: `$` matches before trailing newline by default. Use `\z` for true end. `Pattern.DOTALL` makes `.` match newlines
- **Ruby**: `^` and `$` are per-line by default (multiline is always on). Use `\A` and `\z` for string boundaries

> [!WARNING]
> Ruby's default multiline behavior is the most commonly exploited. A Rails application using `/^https?:\/\//` to validate URLs can be bypassed with `javascript:alert(1)\nhttp://` because `^` matches the start of the second line.

## URL Encoding and Double Encoding

If the regex runs on the raw input before URL decoding, encoded characters bypass character-class restrictions:

```php
Input:   test%27+OR+1%3d1--
Decoded: test' OR 1=1--

# If regex checks for single quotes on raw input:
preg_match('/\'/', $raw_input)  // No match (sees %27, not ')
# Application then URL-decodes and uses in query: SQLi
```

Double encoding works when the application decodes twice but the regex only sees the first decode:

```
Original:       ../etc/passwd
Single encoded: %2e%2e%2fetc%2fpasswd
Double encoded: %252e%252e%252fetc%252fpasswd

# WAF/regex checks after first decode: sees %2e%2e%2f (no match for ../)
# Application decodes again: sees ../etc/passwd
```

## Null Byte Injection

In older PHP versions (before 5.3.4) and some C-based regex engines, a null byte (`%00`) terminates the string for the regex engine but not for subsequent file operations:

```php
# PHP < 5.3.4
$file = $_GET['file'];  // "shell.php%00.jpg"

if (preg_match('/\.jpg$/', $file)) {
    // Matches: regex sees "shell.php\0.jpg", matches .jpg
    include($file);  // include sees "shell.php" (truncated at null byte)
}
```

Modern PHP versions fix this, but the technique still works in some compiled applications and CGI environments where C string handling truncates at null bytes.

## Backtracking Limit (ReDoS as Bypass)

PHP's PCRE engine has a backtracking limit (`pcre.backtrack_limit`, default 1000000). Crafted input that exceeds this limit causes `preg_match` to return `false` (error) rather than `0` (no match). If the application checks `if (!preg_match(...))`, a ReDoS payload causes the regex to fail, and the negative check interprets the failure as a non-match, passing the validation:

```php
// Vulnerable pattern: catastrophic backtracking
$pattern = '/^(a+)+$/';
$input = str_repeat('a', 30) . '!';

$result = preg_match($pattern, $input);
// $result = false (backtracking limit exceeded)

if (!$result) {
    // Developer intended: "no match, input is safe"
    // Reality: regex errored out, input was NOT validated
    process_input($input);
}
```

Fix: always check `preg_match() === 0` (strict comparison to integer zero) instead of `!preg_match()` to distinguish between no-match and error.

## Testing Methodology

When you find a regex-based validation during [source code review](https://hackindex.io/services/web/exploitation/source-code-review-methodology), test these bypass categories in order:

1. Check anchoring: does it have both `^` and `$` (or `\A` and `\z`)?
2. Check flags: is `/m` or `/s` set? In Ruby, `^$` is per-line by default
3. Try newline injection: `%0a` after valid input, payload on next line
4. Try trailing newline: valid input + `%0a` at the end (bypasses `$` without `\z`)
5. Check decode order: does the regex run before or after URL decoding?
6. Try double encoding: `%25xx` for each special character
7. Check `preg_match` return handling: can you trigger a backtracking error?

## References

- [Regular-Expressions.info: Anchors](https://www.regular-expressions.info/anchors.html) - Comprehensive reference on regex anchors, boundary matchers, and engine-specific behavior.
- [PortSwigger: SSRF Defense Bypass](https://portswigger.net/web-security/ssrf#circumventing-common-ssrf-defenses) - Practical examples of bypassing regex-based SSRF filters including IP encoding tricks.
