Welcome to the second half of my two-part blog on Understanding Session Management. In part 1, we covered what was session management and started digging into some possible attack types associated with this vulnerability. Here we will continue to look into other associated attack types.
4. Cross-Site Request Forgery (CSRF) – Severity: High
“Cross-Site Request Forgery (CSRF) is an attack that forces an end user to execute unwanted actions on a web application in which they’re currently authenticated.” – OWASP
How does CSRF happen? A victim signs in an application and then clicks a link on a phishing email or on a web page hosted by an attacker.
Test 1 – HTTP GET method (from WebGoat)
<a href="http://bank.com/transfer?account_number_from=123456789&account_number_to=987654321&amount=100000">View my Pictures!</a>
This assumes the victim has logged into bank.com before clicking the link. An attacker may be able to capture the URL string and take advantage of it.
My comments: This is a bad example. It explains what the CSRF is about, but it implies that the GET method can be used to change data. This violates the HTTP standard. Programmers should differentiate the GET method from the POST method. However, I do understand there are cases where the GET method is the only choice.
Test 2 – HTTP POST Method
Assume there is an HTML form in a web page:
<form action="/transfer" method="post">
From Account:<br>
<input type="text" name="account_number_from" /><br>
To Account:<br>
<input type="text" name="account_number_to" /><br><br>
Amount:<br>
<input type="text" name="amount"><br><br>
<input type="submit" name="submit" value="Submit />
</form>
Based upon the above form, we can construct the following HTML document to exploit the CSRF vulnerability.
<html>
<body>
<p>Put nice words here to induce users to click!</p>
<script>history.pushState('','','/')</script>
<form action="https://complete_path/transfer" method="post">
<input name="account_number_from" value="123456789" type="hidden" />
<input name="account_number_to" value="987654321" type="hidden" />
<input name="amount" value="100000" type="hidden" />
<input type="submit" name="submit" value="Submit" />
</form>
</body>
</html>
Note: Please be aware the HTTP PUT and DELETE methods can also be used to perform the CSRF attack.
Remediation
Generally speaking, a web application should provide a second secure token (besides the session ID) to be used to validate that the change request is indeed from a valid user. Setting the SameSite attribute of cookies can help in this case (see Test Case 1).
If the second secure token is implemented, what is the scope of the second token? Same token for all sessions? I did see this incident before. Same token for all pages in the application? This is not a good implementation. Same token for all forms on a page? This is not a bad choice. Using different tokens for every form on the same page? This is probably the best choice.
Another important question is where the second token is stored? A hidden field in the form works. A custom HTTP header to store the second token works too. But a custom cookie to store the second token is not a good choice – this approach is offered by a popular web development framework.
Reference
5. Missing Session Validation – Severity: High
When it comes to a complicated application with many different roles, menu options, form submissions, and hyperlinks, developers can easily miss the authentication of one or two endpoints. This type of issue should be identified through manual code review.
Penetration testing tools crawl applications in order to identify all the endpoints before they can perform security testing. Unfortunately, the resulting list of endpoints can hardly be complete. A tedious pen testing approach is to manually write down the endpoints while browsing all the features inside the application. Then repeat the above process for different roles. This manual approach can result in a more complete list of the endpoints. However, it may still be incomplete. The best strategy to produce a comprehensive list of endpoints is to construct the list during the software development phase as part of the thread modeling effort.
Once the list of endpoints is available, the security test is to access the endpoints without login. It is very possible that you can get in the application if the application is complicated.
Another test case is to login as a user with a certain role or roles. Then access endpoints that are prohibited for the signed-in user.
6. The session ID is valid after closing the browser – Severity: High or Medium, depending on the criticality of the application.
Unless the application is designed to have persistent login or the “remember-me” feature, the logout function should always invalidate the session ID. If invalidating the session ID does not resolve the issue, this behavior can be a cookie attribute issue. A session cookie should not have “Expires” or “Max-Age” attributes. These two attributes are used for persistent cookies, not for session cookies.
In some occasions, the session cookie may still be valid without the Expires and Max-Age attributes. In that case, you will need to look into the caching issue. The recommended cache settings are as follows.
For HTTP/1.1, use the following settings.
Cache-Control: must-revalidate, no-cache, max-age=0, s-maxage=0
For HTTP/1.0, use the following settings.
Cache-Control: must-revalidate, no-cache, no-store
Pragma: no-cache
Expires: 0
Please also be aware that the “remember-me” feature is not recommended for a production system.
Reference
7. Session ID is valid after logout – Severity: High or Medium, depending on the criticality of the application.
After a user logs off, an attacker may use the “Back” and optional “Refresh” (F5) buttons to get inside the application without being authenticated. Furthermore, the attacker may be able to gain access to usernames and passwords.
A semantic question is whether the back button refers to “going back to history” or “retrieving back from caches?” This is an interesting article published in 2013 and is still relevant to the topic: https://madhatted.com/2013/6/16/you-do-not-understand-browser-history.
There are a couple of test cases here. One is to go to the history tab and retrieve the sensitive information after logout. The other test case is to jump to the sensitive page directly by manipulating the URL string. The former test case is to test the history. The later test case is to test the cache settings.
Regardless, the proper setting for sensitive pages, such as the login page, should include the following setting.
Cache-Control: must-revalidate, …
Reference
8. Improper Use of Web Storage – Severity: High, Medium, or Low, depending on the values stored
Cookies or session IDs are automatically added to all requests and can be accessed by both the server and client. Web storage falls exclusively under the purview of client-side scripting and is not automatically forwarded to other sites. This is good.
There are two types of web storage objects: localStorage and sessionStorage. The data stored in sessionStorage is only available within the browser tab or window session. It’s designed to store data in a single web session.
The data stored in localStorage can be made available between page requests and even between browser sessions. This means data is still available when the browser is closed and reopened.
Please be aware that sensitive information stored in both storages can be manipulated by attackers. A solution to this issue is to encrypt the sensitive information and may implement signatures on top of encrypted data. Please also make sure to clean the localStorage after logout. The sessionStorage will be removed automatically, but localStorage will not.
While web Storage is useful for storing smaller amounts of data, it is less useful for storing larger amounts of structured data. IndexedDB provides such a solution. We will not go into the discussion of indexDB here.
Reference
- https://web.dev/storage-for-the-web/
- https://www.w3.org/TR/webstorage/
- https://www.w3.org/TR/IndexedDB/
9. Not Enforcing Session Timeout – Severity: Medium or Low
All applications should implement an idle or inactivity timeout for sessions. Session idle timeout should be set to 15 to 60 minutes for most applications. In addition, session timeout must be enforced server-side. If the session timeout is implemented at the client-side, attackers can continue using the session to interact with the server directly.
Another important timeout that seems to be neglected by a lot of applications is the session lifespan timeout or absolute timeout, which means when the lifespan timeout is reached, the system will terminate the session regardless of whether the session is active or idle. In other words, users will have to re-login to the application. This also means the session idle timeout can be extended up to the end of the lifespan timeout. Setting the lifespan timeout to 12 to 24 hours seems to be reasonable for most applications. However, you should follow your company’s policies and standards for these two timeout values.
Reference
10. Session Puzzling – Severity: High or Medium
The term session puzzling was invented by Shay Chen in 2012. Basically, the vulnerability occurs when an application uses the same session ID for more than one purpose. “An attacker can potentially access pages in an order unanticipated by the developers so that the session variable is set in one context and then used in another.” – OWASP
The concept of session puzzling is difficult to understand based on Chen’s original paper. Later he published a few scenarios to clarify the issue. One scenario Chen presented is as follows.
- Login as user1
- Open the registration tab and register as “admin”
The registration process will fail because “admin” already exists. - Go back to the user1 tab, refresh the screen
User1 now becomes the “admin”
The vulnerabilities caused by session puzzling are code implementation flaws. Besides using different session IDs for different contexts, the best way to resolve this type of issues are (1) perform manual code review and (2) use a well-known or popular development framework and do not invent the session management capabilities yourself.
Reference
- https://github.com/OWASP/wstg/blob/master/document/4-Web_Application_Security_Testing/06-Session_Management_Testing/08-Testing_for_Session_Puzzling.md
- http://sectooladdict.blogspot.com/2011/09/session-puzzling-and-session-race.html – Six youtube videos
Code Review
Here is the code snippet that handles the two-factor authentication. The code has a high severity vulnerability issue, and no static analysis tools can identify the problem. I am not sure whether dynamic scanning tools can do the job.
Can you identify the problem? If you are the attacker, how would you take advantage of the vulnerability?
try {
UserContext uc = UserUtils.authenticate(user, pass);
HttpSession sid = request.getSession(false);
if (sid == null) {
sid = newSession(request);
}
setSessionContext(uc, sid); // populate full session details
// of the authenticated user
if (uc.requires2FA()) { // Two factor authentication
// required
/* redirect to 2FA authentication page */
} else { /* redirect to users home page */ }
}
catch (Authentication Exception e) { /* redirect to login */ }
Conclusion
Protecting the session-id is challenging, to say the least. Developers not only have to ensure no logic errors in their code but also verify the configuration settings in the development framework and the web server. Furthermore, they have to know how browsers handle the session IDs to avoid falling into the pitfalls of the browser’s weakness.