Double encoding

Double encoding is the act of encoding data twice in a row using the same encoding scheme. It is usually used as an attack technique to bypass authorization schemes or security filters that intercept user input. In double encoding attacks against security filters, characters of the payload that are treated as illegal by those filters are replaced with their double-encoded form.

Double URI-encoding is a special type of double encoding in which data is URI-encoded twice in a row. It has been used to bypass authorization schemes and security filters against code injection, directory traversal, cross-site scripting (XSS) and SQL injection.

Description

In double encoding, data is encoded twice in a row using the same encoding scheme, that is, double-encoded form of data X is Encode(Encode(X)) where Encode is an encoding function.

Double encoding is usually used as an attack technique to bypass authorization schemes or security filters that intercept user input. In double encoding attacks against security filters, characters of the payload that are treated as illegal by those filters are replaced with their double-encoded form. Security filters might treat data X and its encoded form as illegal. However, it is still possible for Encode(Encode(X)), which is the double-encoded form of data X, to not to be treated as illegal by security filters and hence pass through them, but later on, the target system might use the double-decoded form of Encode(Encode(X)), which is X, something that the filters would have been treated as illegal.

Double URI-encoding

Double URI-encoding, also referred to as double percent-encoding, is a special type of double encoding in which data is URI-encoded twice in a row. In other words, double-URI-encoded form of data X is URI-encode(URI-encode(X)). For example for calculating double-URI-encoded form of <, first < is URI-encoded as %3C which then in turn is URI-encoded as %253C, that is, double-URI-encode(<) = URI-encode(URI-encode(<)) = URI-encode(%3C) = %253C. As another example, for calculating double-URI-encoded form of ../, first ../ is URI-encoded as %2E%2E%2F which then in turn is URI-encoded as %252E%252E%252F, that is, double-URI-encode(../) = URI-encode(URI-encode(../)) = URI-encode(%2E%2E%2F) = %252E%252E%252F.

Double URI-encoding is usually used as an attack technique against web applications and web browsers to bypass authorization schemes and security filters that intercept user input. For example because . and its URI-encoded form %2E are used in some directory traversal attacks, they are usually treated as illegal by security filters. However, it is still possible for %252E, which is the double-URI-encoded form of ., to not to be treated as illegal by security filters and hence pass through them, but later on, when the target system is building the path related to the directory traversal attack it might use the double-URI-decoded form of %252E, which is ., something that the filters would have been treated as illegal.

Double URI-encoding attacks have been used to bypass authorization schemes and security filters against code injection, directory traversal, XSS and SQL injection.

Prevention

Decoding some user input twice using the same decoding scheme, once before a security measure and once afterwards, may allow double encoding attacks to bypass that security measure. Thus, to prevent double encoding attacks, all decoding operations on user input should occur before authorization schemes and security filters that intercept user input.

Examples

PHP

In PHP programming language, data items in $_GET and $_REQUEST are sufficiently URI-decoded and thus programmers should avoid calling the urldecode function on them. Calling the urldecode function on data that has been read from $_GET or $_REQUEST causes the data to be URI-decoded once more than it should and hence may open possibility for double URI-encoding attacks.

Directory traversal

In the following PHP program, the value of $_GET["file"] is used to build the path of the file to be sent to the user. This opens the possibility for directory traversal attacks that incorporate their payload into the HTTP GET parameter file. As a security filter against directory traversal attacks, this program searches the value it reads from $_GET["file"] for directory traversal sequences and exits if it finds one. However, after this filter, the program URI-decodes the data that it has read from $_GET["file"], which makes it vulnerable to double URI-encoding attacks.

<?php
/* Note that $_GET is already URI-decoded */
$path = $_GET["file"];

/* Security filter */
/* Exit if user input contains directory traversal sequence */
if (strstr($path, "../") or strstr($path,  "..\\"))
{
    exit("Directory traversal attempt detected.");
}

/* URI-decode user input once again */
$path = urldecode($path);

/* Build file path to be sent using user input */
echo htmlentities(file_get_contents("uploads/" . $path));

This filter successfully blocks payloads such as

../../../../etc/passwd

and its URI-encoded form

%2E%2E%2F%2E%2E%2F%2E%2E%2F%2E%2E%2Fetc%2Fpasswd

However, when the aformentioned payload is doubly-encoded as

%252E%252E%252F%252E%252E%252F%252E%252E%252F%252E%252E%252Fetc%252Fpasswd

it will bypass this filter, since the value of $_GET["file"] will be the singly-encoded version. When the payload is encoded once, it does not contain any directory traversal sequence and thus passes through the filter unchanged. Once it is given to the urldecode function, it will become ../../../../etc/passwd, resulting in a successful attack.

Cross-site scripting

In the following PHP program, the value of $_GET["name"] is used to build a message to be shown to the user. This opens the possibility for cross-site scripting (XSS) attacks that incorporate their payload into the HTTP GET parameter name. As a security filter against XSS attacks, this program sanitizes the value it reads from $_GET["name"] via the htmlentities function. However, after this filter, the program URI-decodes the data that it has read from $_GET["name"], which makes it vulnerable to double URI-encoding attacks.

<?php
/* Note that $_GET is already URI-decoded */
$name = $_GET["name"];

/* Security filter */
/* Sanitize user input via htmlentity */
$name = htmlentities($name);

/* URI-decode user input once again */
$name = urldecode($name);

/* Build message to be shown using user input */
echo "Hello " . $name;

This filter successfully blocks payloads such as

<script>alert(123)</script>

and its URI-encoded form

%3Cscript%3Ealert%28123%29%3C%2Fscript%3E

However, when the payload is doubly-encoded as

%253Cscript%253Ealert%2528123%2529%253C%252Fscript%253E

it will bypass this filter, since the value of $_GET["name"] will be the singly-encoded form. The singly-encoded form does not contain any illegal characters and will passes through the htmlentities function unchanged. Then, once the urldecode function decodes it a second time, it will become <script>alert(123)</script> which results in a successful attack.

Sources

  • CAPEC (2022). "CAPEC-120: Double Encoding". capec.mitre.org. 3.7. Retrieved 23 July 2022.
  • CWE (2022). "CWE-174: Double Decoding of the Same Data". cwe.mitre.org. 4.8. Retrieved 23 July 2022.
  • Imperva (2022). "Double URL Encoding". docs.imperva.com. Retrieved 23 July 2022.
  • OWASP (2022). "Double Encoding". owasp.org. Retrieved 23 July 2022.
  • PHP (2022). "urldecode". php.net. Retrieved 23 July 2022.
  • PortSwigger (2022). "Obfuscating attacks using encodings". portswigger.net. Obfuscation via double URL encoding. Retrieved 23 July 2022.
  • Prasad, Prakhar (2016). "Double encoding". Mastering Modern Web Penetration Testing. Packt Publishing. pp. 11–14. ISBN 978-1785284588.

References

Uses material from the Wikipedia article Double encoding, released under the CC BY-SA 4.0 license.