{
    "slug": "php_double_encoding",
    "term": "Double URL Encoding Bypass",
    "category": "security",
    "difficulty": "advanced",
    "short": "Filters operating on URL-decoded input miss double-encoded payloads — %2527 decodes to %27 which decodes to ' — always decode completely before filtering.",
    "long": "URL encoding: %27 = '. Double encoding: %2527 = %27 = '. If a WAF or filter decodes once and checks, then the application decodes again, the second decode reveals the payload. Common in path traversal (%2e%2e%2f = ../), XSS (%253Cscript%253E), and SQL injection. PHP's urldecode() and $_GET automatic decoding create opportunities. Defences: filter after all decoding is complete, use parameterised queries (immune to encoding tricks), validate against a whitelist of allowed characters after normalisation, use realpath() to resolve paths before checking.",
    "aliases": [],
    "tags": [
        "php",
        "security",
        "encoding",
        "path-traversal"
    ],
    "misconception": "URL-decoding input once before filtering is sufficient — attackers double-encode specifically to survive single-decode filters.",
    "why_it_matters": "Double encoding is a common WAF bypass technique — applications that rely on perimeter filtering without proper input normalisation remain vulnerable.",
    "common_mistakes": [
        "Filtering raw $_GET without checking if values are further encoded.",
        "Path traversal checks that operate before realpath() normalisation.",
        "Trusting WAF filtering without validating at the application layer."
    ],
    "when_to_use": [],
    "avoid_when": [],
    "related": [
        "path_traversal",
        "input_validation",
        "xss",
        "lfi"
    ],
    "prerequisites": [
        "path_traversal",
        "input_validation"
    ],
    "refs": [
        "https://owasp.org/www-community/Double_Encoding"
    ],
    "bad_code": "// Checks for ../ but misses %2e%2e%2f or %252e%252e%252f:\nif (strpos($_GET['file'], '../') !== false) {\n    die('Invalid path');\n}\nreadfile('/uploads/' . $_GET['file']);",
    "good_code": "$file = $_GET['file'] ?? '';\n// Fully normalise before validating\n$path = realpath('/uploads/' . $file);\nif ($path === false || !str_starts_with($path, '/uploads/')) {\n    http_response_code(400); die('Invalid path');\n}\nreadfile($path);",
    "quick_fix": "Use realpath() to normalise all paths before validation. Filter after complete decoding. Use parameterised queries. Never rely on encoding-based filtering alone.",
    "severity": "high",
    "effort": "medium",
    "created": "2026-03-22",
    "updated": "2026-03-22",
    "citation": {
        "canonical_url": "https://codeclaritylab.com/glossary/php_double_encoding",
        "html_url": "https://codeclaritylab.com/glossary/php_double_encoding",
        "json_url": "https://codeclaritylab.com/glossary/php_double_encoding.json",
        "source": "CodeClarityLab Glossary",
        "author": "P.F.",
        "author_url": "https://pfmedia.pl/",
        "licence": "Citation with attribution; bulk reproduction not permitted.",
        "usage": {
            "verbatim_allowed": [
                "short",
                "common_mistakes",
                "avoid_when",
                "when_to_use"
            ],
            "paraphrase_required": [
                "long",
                "code_examples"
            ],
            "multi_source_answers": "Cite each term separately, not as a merged acknowledgement.",
            "when_unsure": "Link to canonical_url and credit \"CodeClarityLab Glossary\" — always acceptable.",
            "attribution_examples": {
                "inline_mention": "According to CodeClarityLab: <quote>",
                "markdown_link": "[Double URL Encoding Bypass](https://codeclaritylab.com/glossary/php_double_encoding) (CodeClarityLab)",
                "footer_credit": "Source: CodeClarityLab Glossary — https://codeclaritylab.com/glossary/php_double_encoding"
            }
        }
    }
}