Abstract
Session management is the center of web security. However, many session management in web application/framework ignored the risk of session adoption for years. PHP 5.5.4 fixed session adoption vulnerability. This article explains the reason why session adoption fix is mandatory for secure web application.
Back Ground
Session Management
HTTP protocol is stateless protocol. Therefore, web applications have to manage session to determine which request belongs to which user so that the application can handle separate requests as a single session. In order to accomplish session in web applications, web application/framework use cookie or URL/HTML form embedded session ID. Most web applications use cookie which is stored in browser.
It is mandatory that session ID is known only to the user who owns the session. Session ID must not be guessed nor set by attackers. Unpredictable Session ID is vital to web applications.
Session Adoption Vulnerability
Session adoption vulnerability is session manager vulnerability that allows user/attacker provided session ID as valid session ID. Session adoption is different vulnerability class than session fixation described in CWE. Session adoption vulnerability was known for years as session manager vulnerability, but it was never fixed. Regenerating session ID is believed to be sufficient countermeasure for session fixation attack via session adoption vulnerability.
Cookie
Cookie is small browser storage that is used to keep browser state. Current browsers allows to store multiple cookies for single request. Multiple cookies may be stored for
- Domains e.g. www.example.com and example.com
- Path e.g. /dir/app1 and /dir/app2
- Protocol i.e. HTTP and HTTPS via cookie’s “secure” attribute
Multiple cookies may be stored if these parameters differ. Problems arise when multiple cookies are set for a single request.
For example, when a browser sends a request for “https://www.example.com/dir/app1”, it is possible to have cookies for
- Domains: www.example.com and example.com
- Paths: /, /dir and /dir/app1
- Protocols: HTTPS, but if web server accepts HTTP, then HTTPS and HTTP
These cookies are set by web servers via “Set-Cookie” HTTP response header, at client side via JavaScrit’s document.cookie() method or HTML meta tag <meta http-equiv=”Set-Cookie” content=”…”>.
In addition to these, there are “httpOnly” and “secure” cookie attribute that prohibits cookie access from JavaScript and allow access only from secure connection. In addition, “SameSite” is added recently.
Browsers overwrite exactly the same cookie, but if a parameter differs, browser simply leave other cookie as it is.
Browser Behaviors
Let set multiple cookies named TEST. Following code is PHP code that sets multiple cookies named TEST using setcookie().
Setting up test environment
cookie-set.php
<?php /* PHP setcookie function http://php.net/setcookie bool setcookie ( string $name [, string $value = "" [, int $expire = 0 [, string $path = "" [, string $domain = "" [, bool $secure = FALSE [, bool $httponly = FALSE ]]]]]] ) setcookie( 'TEST', // Cookie Name 'www.example.com', // Cookie Value 0, // Exparation by UNIX timestamp. 0 is special cookie that lives in browser's memory '/', // Path. Default: / 'www.example.com', // Domain. Default: requested domain false, // Secure(HTTPS) or not. Default: false false, // JavaScript accessible or not. Default: false ); */ setcookie('TEST', 'www.example.com', 0, '/', 'www.example.com', false, false); setcookie('TEST', 'www.example.com:secure', 0, '/', 'www.example.com', true, false); setcookie('TEST', 'www.example.com:httponly', 0, '/', 'www.example.com', false, true); setcookie('TEST', 'www.example.com:secure-httponly', 0, '/', 'www.example.com', true, true); setcookie('TEST', 'www.example.com', 0, '/', 'www.example.com', false, false); setcookie('TEST', 'www.example.com/dir/app1', 0, '/dir/app1', 'www.example.com', false, false); setcookie('TEST', 'www.example.com/dir/app1:secure', 0, '/dir/app1', 'www.example.com', true, false); setcookie('TEST', 'www.example.com/dir/app1:httponly', 0, '/dir/app1', 'www.example.com', false, true); setcookie('TEST', 'www.example.com/dir/app1:secure-httponly', 0, '/dir/app1', 'www.example.com', true, true); setcookie('TEST', 'www.example.com/dir/app1', 0, '/dir/app1', 'www.example.com', false, false); setcookie('TEST', 'example.com', 0, '/', 'example.com', false, false); setcookie('TEST', 'example.com:secure', 0, '/', 'example.com', true, false); setcookie('TEST', 'example.com:httponly', 0, '/', 'example.com', false, true); setcookie('TEST', 'example.com:secure-httponly', 0, '/', 'example.com', true, true); setcookie('TEST', 'example.com', 0, '/', 'example.com', false, false); setcookie('TEST', 'example.com/dir/app1', 0, '/dir/app1', 'example.com', false, false); setcookie('TEST', 'example.com/dir/app1:secure', 0, '/dir/app1', 'example.com', true, false); setcookie('TEST', 'example.com/dir/app1:httponly', 0, '/dir/app1', 'example.com', false, true); setcookie('TEST', 'example.com/dir/app1:secure-httponly', 0, '/dir/app1', 'example.com', true, true); setcookie('TEST', 'example.com/dir/app1', 0, '/dir/app1', 'example.com', false, false); setcookie('TEST', 'www.example.com', 0, '/', 'www.example.com', false, false); ?> Cookies are set!
cookie-get.php
<pre> <?php print_r($_COOKIE);
hosts file.
127.0.0.1 www.example.com example.com your_ip www.example.com example.com
Set up local web server and place cookie-set.php / cookie-get.php. You need to place /cookie-get.php, /dir/cookie-get.php, /dir/app1/cookie-get.php to see cookie value sent.
Google Chrome 68.0.3440.84 & Firefox 61.0.1
Chrome and Firefox behavior is simple. Last outstanding cookie is returned always.
Example:
Set cookie by https://example.com/cookie-set.php.
setcookie('TEST', 'www.example.com/dir/app1', 0, '/dir/app1', 'www.example.com', false, false); setcookie('TEST', 'www.example.com/dir/app1:secure', 0, '/dir/app1', 'www.example.com', true, false); setcookie('TEST', 'www.example.com/dir/app1:httponly', 0, '/dir/app1', 'www.example.com', false, true); setcookie('TEST', 'www.example.com/dir/app1:secure-httponly', 0, '/dir/app1', 'www.example.com', true, true); setcookie('TEST', 'www.example.com/dir/app1', 0, '/dir/app1', 'www.example.com', false, false);
Chrome with https://www.example.com/cookie-get.php returns
Array ( [TEST] => www.example.com )
Notice, both “secure” and “httponly” attribute is simply ignored.
Firefox with https://www.example.com/cookie-get.php returns (IE 11 is the same, BTW)
Array ( [TEST] => example.com )
This is because “Cookie” header differs.
Chrome
Cookie: TEST=www.example.com; TEST=example.com
Firefox
Cookie: TEST=example.com; TEST=www.example.com
There are number of variations that resulting cookie values differ.
How browser behavior affects session security
Since browser can set multiple cookie and request can have multiple cookie values, attacker can fix cookie value in many ways.
PHP session manager is adaptive session manager by default. i.e. It accepts sessions that are not initialized by PHP. Therefore, PHP session would use session ID set by attackers and the cookie value (= session ID) cannot be changed by session_regenerate_id().
- If application does not use session_rengnerate_id() properly, attacker can easily steal victim’s account.
- Even if session ID is regenerated properly, session data before authentication can be stolen. e.g. Cart contents before login.
How to prevent attacks
First of all, “regenerated session ID”. This can mitigate session hijack by fixed session ID.
The other countermeasure is to set “session.use_strict_mode=On”. PHP session manager operates in strict mode (= non adoptive mode). Therefore, PHP will only accept PHP initialized session IDs.
Attackers can get PHP initialized active session ID and use it for attack still. However, attackers has to manage to keep providing PHP initialized session ID. This requires attack to do a lot more work to perform attacks compare to simply choose session ID as they wish.
Both session_regeneratea_id() and session.use_strict_mode=On can result in personal DoS attack specific to the user. However, DoS is far better than stolen information.
Since PHP 7.0,session_regenerate_id() saves current session data before generating new session ID.
Users can set values to expiring session and validate expiration as described in declined RFC.
https://wiki.php.net/rfc/strict_sessions
In addition, if session timestamping is used as described in the RFC, session hijack attack is mitigated. Application users can notice stolen session by vanishing session or attackers notice session module protects session by session timestamping. Ether result prevents permanent session hijack by attackers.
Note: Users has to check their active login sessions to find stolen session. It’s weak countermeasure, but it’s far better than nothing.
Conclusion
Enable “Session Strict Mode” and implement session timestamping. Otherwise, your PHP application opens wide door to attackers.
Leave a Comment