Feb 22 2008
[lang_de]PHP-Sicherheit: Session-Fixierung[/lang_de][lang_en]php security: session fixation[/lang_en]
[lang_de]
PHP ist eine äußerst beliebte Programmiersprache. Wie jede Sprache hat sie natürlich ihre Vor- und Nachteile. Oft wird dabei die mangelhafte Sicherheit genannt. Das liegt jedoch nach meiner Meinung eher an schlechten Kenntnissen der meisten Programmierer. Ein solcher Punkt ist die sogenannte Session-Fixierung.
Problemsituation
In PHP ist es einfach, eine Session zu starten. Das geht häufig über
session_start();
Dabei wird geschaut, ob als Cookie oder als URL-Parameter (je nach Servereinstellung) eine Session-ID vorhanden ist. Ist sie das, wird die Session mit einer entsprechenden ID geladen. War sie bisher leer, so wird sie neu initialisiert. Ist hingegen keine ID vorhanden, so wird eine zufällige Session-ID verwendet.
Angriffszenario
Stellen wir uns einen Hacker vor, der einen Admin-Zugriff zu einem Shop haben will. Die einfachste Möglichkeit, an diesen zu gelangen, besteht darin, den Admin direkt danach zu fragen. So wäre folgende Mail an einen Shop-Inhaber denkbar:
Sehr geehrter Herr [Name],
ich habe heute Ihren Shop entdeckt und dabei ist mir aufgefallen, dass Ihr Shop eine Sicherheitslücke aufweist. Wenn Sie auf folgende URL klicken (manipulierte URL) und sich anschließend an Ihren Shop anmelden, sehen Sie, dass sich jeder Benutzer als Admin anmelden kann.
Der Shop-Besitzer, der natürlich Angst um seine Investition hat, wird ohne Nachdenken auf den Link klicken. Doch was tut dieser Link? Er leitet direkt an den Shop weiter, übergibt jedoch per Cookie oder per URL-Parameter eine festgelegte Session-ID. Den Namen der Session-ID hat der Hacker zuvor natürlich ausgekundschaftet, indem er einfach den Shop verwendete.
Das Problem ist jetzt, dass keine neue Session-ID erzeugt wird, da ja eine angegeben wurde. Der Shop-Besitzer muss sich nun lediglich anmelden und schon kann der Hacker als Admin tätig werden (er weiß die Session-ID ja).
Problemlösung
Die Lösung des Problems ist denkbar einfach: man muss beim Laden der Session lediglich prüfen, ob ein spezieller Wert schon angelegt wurde:
session_start();
if (!isset($_SESSION["init"])) {
//Session ist neu – neue ID auf jeden Fall vergeben
session_regenerate_id();
$_SESSION["init"]=true;
}
Sehr einfach und löst das Problem. Man muss es nur wissen.
[Update]
Nach Diskussion in den Kommentaren mit Erich folgender Hinweis: Natürlich ist es möglich, dass der Angreifer eine Session öffnet und diese offen hält. Diese Session-Daten könnte dann an einen Benutzer mit erhöhten Rechten mit Hilfe eines manipulierten Links gegeben werden. Klickt dieser darauf und meldet er sich an, so hab ich als Hacker die neuen Anmelde-Daten.
Im Prinzip übernimmt also der Benutzer die korrekte Session des Hackers, womit wir einen Fall von Session-Hijacking haben. Dies kann man mit oben beschriebenen Script nicht verhindern. Es gibt mehrere Möglichkeiten, das zu verhindern:
- Ich verwende session_regenerate_id nach dem Anmelden. Damit stell ich sicher, dass ich auf jeden Fall eine neue ID verwende und der Hacker nicht mehr darauf zugreifen kann. Die alten Session-Daten werden dabei übernommen. Das Problem ist jedoch, dass je nach Session-Inhalt Daten übernommen werden, die weiterhin kritisch sind. Diese Lösung funktioniert deshalb nur, wenn ich auch darauf achte, dass die Session übernommen sein könnte. Wenn ich z.B. meine UserID in der Session speichere, muss ich berücksichtigen, dass die falsch sein könnte. Das Verfahren stellt also lediglich sicher, dass der Hacker keinen direkten Zugriff bekommt, weil er eine andere Session hat. Sie stellt aber nicht sicher, dass dadurch das System sicherer wird.
- Die meiner Meinung nach bessere Möglichkeit besteht darin, sich wirklich gegen Session-Hijacking komplett zu schützen. Einen 100%-Schutz gibt es dabei nicht. Es hat sich aber bewährt, einen Fingerabruck vom Benutzer zu erstellen. Dazu sammelt man in einem Array erstmal Daten über den Benutzer und bildet darüber einen Hash-Wert.
$fingerprintArray = array($_SERVER['HTTP_USER_AGENT'],substr($_SERVER["REMOTE_ADDR"],0,7),…);
$fingerprint = md5(serialize($fingerprintArray));Wichtig ist dabei darauf zu achten, dass die Remote-Adresse nicht komplett benutzt wird, weil sie sich bei vielen Anbietern ständig ändert.
Vergleicht man nun bei jeder Abfrage diesen Fingerabdruck, kann man recht sicher sein, dass der Benutzer korrekt identifiziert wurde. Heißt also: anderer Fingerabdruck? Session sofort löschen.
Sicher sollte man sein, indem man beide Techniken kombiniert.
[/lang_de]
[lang_en]
PHP is a loved programming language. But of course there are good and bad features as in any other programming language, too. Security can be heared to be one of the bad features. But in my opinion, the reason is not php but users who don’t know the problems. One problem is the so called session fixation
problem situation
It is very easy in PHP to start a session:
session_start();
If you start a session, the server looks if there is a cookie or a url param with a session id. If there is a session id, it will be used. If the session was not used before, it is created with this id. If there is no id, it will be generated randomly.
attack scenario
If you are a hacker and you want to have the admin access to a shop, what can you do. The simliest way is to ask the shop owner of course. So you write an email like this
Dear [Name],
I have found your shop and I have seen, that every user can get an admin access. I think this is a big security risk! If you click at this url (manupulated URL) and if you login, you will see that there is a big security hole.
The shop owner does not know anything about security and programming. So he will click the url to see the problem and to save his investment. But what does the url do? It only redirects the user to the shop and sets a cookie or a url param with a fixed session id. The name of the id is known by using the shop as costumer.
The problem is, that the session id is not regenerated. If the shop admin logs in, the hacker has an admin access.
problem solution
The solution of this problem is very simple. You only have to check if the session was used before. If not, you must regenerate the id for yourself:
session_start();
if (!isset($_SESSION["init"])) {
//session is new – regenerate the id!
session_regenerate_id();
$_SESSION["init"]=true;
}
Very easy, isn’t it?
[Update]
After disussing with Erich following notice: Of course, it is possible, that a hacker creates a valid session and that he can keep this session active. He can give a manipulated link to an user with higher rights. If this user would use the link and he would log on in the system, the hacker will have more rights.
In this case, the user uses a valid session, which is not his session. So, we have a special case of session hijacking. The above described script is NOT able to handle this. We can solve this problem like this:
- Use session_regenerate_id after login, so that the user has surely a new session id. Old session data will remain, what is necessary in some systems. The problem is, that also critical session data will remain. So you have to be aware, that there can be wrong data (e.g. wrong user id) in your session. This solution will make sure, that the hacker gets no privileged access. But it does not make sure, that your application is secure at all.
- In my opinion, it would be better to include a session hijacking protection. You can create a fingerprint of your user and you have to check this fingerprint at every connection. So collect information about your user and save it to the session as hash value:
$fingerprintArray = array($_SERVER['HTTP_USER_AGENT'],substr($_SERVER["REMOTE_ADDR"],0,7),…);
$fingerprint = md5(serialize($fingerprintArray));Important: don’t use the complete REMOTE_ADDR because it could change with every request.
If the fingerprint is different to the stored fingerprint: delete the session.
The most secure way is to use both methods, surely.
[/lang_en]
