Privilege escalation vulnerabilities allow attackers to impersonate other users, or gain permissions they should not have. These vulnerabilities occur when code makes access decisions on the back of untrusted inputs.
Many websites hold sensitive data on behalf of their users. If an attacker can exploit horizontal escalation vulnerabilities to gain access to another user’s data, you are betraying your users’ trust, which can have reputational, legal, and financial implications.
If an attacker can exploit vertical escalation vulnerabilities to gain administrative access, they can interrupt critical functions and possibly compromise your application.
Privilege escalation vulnerabilities are system flaws that grant a malicious user excessive or wrong permissions after they have authenticated themselves. (These are distinct from session hijacking vulnerabilities that allow an attacker to impersonate another user.)
Escalation vulnerabilities in websites occur when access control decisions are made on the back of untrusted input. Since HTTP is stateless protocol, websites need some mechanism of continuing the conversation with the user after login, over multiple HTTP request-response cycles. This typically means sending information in HTTP responses that will be transmitted back in subsequent requests; an attacker will try to manipulate the re-transmitted data to fool the system into giving them more power than they should.
There are three possible approaches to prevent this happening:
- Keep critical information on the server side, and only send session IDs to the client.
- Tamper-proof the data sent to the client, by using a digital signature.
- Encrypt the data sent to the client, so it is opaque to the client.
We will discuss each approach in turn.
Keeping it Server Side
The simplest approach philosophically is to not transmit sensitive data to the client-side. Typically this means only the session ID is passed back and forth between client and server, and all session-related data is kept on the server. This removes the possibility of tampering, since a malicious user never gets to see the data.
While secure, this approach puts some extra obligations on the server. Session state has to be persisted and looked up with each HTTP request. Unless you are running everything in a single process on a single server, this means writing the session state away to a data-store or shared memory. The scalability implications of this approach need to be thought through carefully.
If you want to send data back to the client-side and be sure it hasn’t been tampered with when it returns, you need to digitally sign the data. Many web frameworks allow you to encode session state, and accompany it with a digital signature which must be sent back with the data. Upon receipt of the returned data, the digital signature is recalculated. Any modifications will result in a different signature, indicating the data has been tampered with, and must be discarded.
This approach guarantees the integrity of the data, but does not make it opaque to the client. So it may not be appropriate if you are storing data about a user you don’t want them to be able to see – like credit scores or other types of ratings!
Note that with this approach, the HTTP response and request carry the entirety of the session. Be careful not to store too much data in your sessions, or the responsiveness of your site will be affected.
If you want the session state to be opaque and tamper-proof, you need to encode and encrypt the data. This introduces some computational overhead – the data will need to be decrypted with each request, and re-encrypted with each response – but should not put a great strain on your servers.
Django has a configurable session-engine, that can store sessions on the server-side in a database, in a cache, or on disk:
SESSION_ENGINE = django.contrib.sessions.backends.db SESSION_ENGINE = django.contrib.sessions.backends.cache SESSION_ENGINE = django.contrib.sessions.backends.file
It can also be configured to store session state in cookies:
SESSION_ENGINE = django.contrib.sessions.backends.signed_cookies
With this setting, cookies are signed but not encrypted. If you need to encrypt cookies, take a look at this module.
Rails 2.0 made storing session data in signed cookies the default,
while Rails 4.0 added encryption also. Session storage is fully
configurable, though, so you can keep session state on the server-side
# Store session state in the database. Rails.application.config.session_store = :active_record_store # Store session state in cookies. Rails.application.config.session_store = :cookie_store # Store session state in a memcache instance. Rails.application.config.session_store = :mem_cache_store
Cookies will be signed by default. To make sure they are encrypted, set
secret_key_base property in your environmental
Java servlets can be configured to store sessions in cookies, in-memory, on disk, or in a database. See here for instructions on how configure sessions in Apache Tomcat, and here for WebLogic instructions.
ASP.NET sessions can be stored in memory, in a separate state server, or in a database - see here for the various options.
Session persistence can also be customized, and cookies read and written easily in the following manner:
// Write a cookie. Response.Cookies["TestCookie"].Value = "ok"; // Read a cookie. Request.Cookies["TestCookie"];
See here for more details about cookie storage in ASP.NET.