Sessions are a very complicated part of PHP, and it is no surprise that the smarter website attacks are carried out on sessions.
Most of these attacks involve one user impersonating another, or, more to the point, "hijacking" the other user's session
data. As most experienced developers know, exploiting session data is an easy way into most low-to-medium security
level websites--after all, that is how most website login systems work.
From a hacker's point of view, there should only be a few ways to get in if you think about it carefully. However, they do--because most website developers do not really think about how they are setting things up. Ever used the session variable $_SESSION['user']? Ever wanted to use that but haven't gotten around to it? If you answered yes to one or more of the previous questions, this article is for you.
The most important part of an attempted security breach is obviously identifying the Session Identifier. This is the user part of $_SESSION['user']. In general there are three common ways of identifying a session identifier:
Prediction has to do with guessing the session identifier. Smart developers will use PHP's native Session Mechanism to set the session identifier, as this is so totally random there is hardly a chance anyone could guess what it is. On the other hand,
my first guess would be $_SESSION['user']. I bet I'd score at least one in six.
The most common type of session attack is Capture, and there are many different approaches. Most websites or applications use either $_GET or cookies in order to store session information. There have been a few browser security breaches with regard
to cookies, but these have been mostly Internet Explorer, and cookies are slightly less exposed than GET variables, thus as a website or application developer you might want to ensure better security by building your session identifiers into cookies.
The simplest method of getting a valid session identifier is by using Fixation. It is not very difficult to defend against, but if you have built a website or applacation that has nothing more than session_start() as its session handler, you are in trouble. To demonstrate fixation we use the following script:
When you go to the script with your browser, you should see a 1 on the screen. Every time you refresh the page this should increment accordingly. To demonstrate session fixation, first make sure that you delete your cookies and session variables, then go to this script with ?PHPSESSID=1234 in the url. Next, with a completely different browser, go to the url again with ?PHPSESSID=1234 appended. You will notice that you do not see 1 output on your first visit, but it continues the session you previously initiated.
Why is this a problem? Most session fixation attacks simply use a link or a protocol-level redirect to send a user to a remote site with a session identifier appended to the URL. The user likely won't notice, since the site will behave exactly the same. Because the attacker chose the session identifier, it is already known, and this can be used to
launch impersonation attacks such as session hijacking.
A simplistic attack like this is usually easy to prevent. The best way is to regenerate the session identifier if one does not exist for a specific user, as shown in fig2 below.
The problem with such a simplistic defense is that an attacker can simply initialize a session for a particular session identifier, and then use that identifier to launch the attack. To protect against this, remember that a session attack means nothing
unless the user is logged in and has more priveleges than any other user. So, if we regenerate the session identifier only when there is a change in the user's permission level--for example, after verifying a username and password, we will have practically eliminated the risk of a successful session fixation attack.
The most common session attack, session hijacking, refers to all attacks that attempt to gain access to another user's session.
As with session fixation, if your session mechanism only consists of session_start(), you are in hot water, although it isn't as simple. Rather than focusing on how to keep the session identifier from being captured, we focus on complicating the session capture. The goal is to complicate impersonation, since every complication increases security. To do this, we examine what we need to do to successfully hijack a session. In each scenario we will assume that the session identifier has been compromised.
It is not smart to rely on anything at the TCP/IP level, such as an IP address, because these are lower level protocols that are not intended to accommodate activities taking place at the HTTP level. A single user can potentially have a different
IP address for each request, and multiple users can potentially have the same IP address.
Only the Host header is required by HTTP/1.1, so it seems silly to rely on anything else. However, consistency is really all we need, because we're only interested in complicating impersonation without causing trouble for our real users.
Imagine that the previous request is followed by a request with a different User-Agent:
Now an attacker must not only present a valid session identifier, but also the proper User-Agent header associated with the session. This complicates things slightly, which is good for security.
Is there a way to improve on this? We know that the most common way to obtain cookie values is by exploiting a vulnerable browser such as Internet Explorer. These exploits involve the victim visiting the attacker's site, so the attacker will be able to
obtain the correct User-Agent header. Something additional is necessary to protect against this situation.
Imagine if we required the user to pass the MD5 of the User-Agent in each request. An attacker could no longer just recreate the headers that the victim's requests contain, but it would also be necessary to pass this extra bit of information. While guessing
the construction of this particular token isn't too difficult, we can complicate such guesswork by simply adding an extra bit of randomness to the way we construct the token:
Keeping in mind that we're passing the session identifier in a cookie, and this already requires that an attack be used to compromise this cookie (and likely all HTTP headers as well), we should pass this fingerprint as a URL variable. This must be in all URLs as if it were the session identifier, because both should be required in order for a session to be automatically continued (in addition to all checks passing).
In order to make sure that our real users aren't treated like criminals, simply prompt for a password if a check fails. If there is an error that incorrectly suspects a user of an impersonation attack, prompting for a password before continuing is the least offensive way to handle the situation.
There are many different ways of complicating impersonation and protecting your applications from session hijacking. Doing more than just session_start() is a good step in the right direction! Always remember that making things difficult for criminals or vandals is always the aim of security.