Сегодня обнаружили очень неприятный баг в PHP.
Коротко: при создании файла сессии PHP не проверяет на наличие уже существующего файла с таким именем :(
Длинно:
При открытии сессии PHP:
1. генерит "уникальное" имя сессии, например sess_01234567890abcdef и сохраняет файлик в папку сессий, указанных в php.ini, например в /tmp
2. пишет браузеру куку с именем PHPSESSID и значением "0123456789abcdef".
3. при приходе браузера с кукой PHPSESSID - из нее берется идентификатор сессии ("0123456789abcdef") и сопоставляется с файликом на сервере (sess_0123456789abcdef)
Наверняка, все знают идеологическую дыру в этой системе: если я утяну куку PHPSESSID с чужого браузера и подсуну в свой - сервер будет сопоставлять оба браузера с одним файлом сессии.
Так вот. Сегодня обнаружилось, что при создании сессии PHP совсем не задается вопросом "а есть ли уже сессия с таким идентификатором?". В итоге, PHP может создать сессию (и послать браузеру куку PHPSESSID) с уже имеющимся идентификатором. Немного непонятно, как так получается, по заверениям разработчиков генерируемый SESSID должен быть уникальным. Однако даже md5(microtime(true)) - всего лишь псевдоуникальность, обусловленная спецификой алгоритма хэширования...
К чему это приводит в итоге, догадаться несложно. От мелких шалостей, типа написания сообщений на форумах от чужого имени, до вполне реального воровства чужих денежных средств.
Как решается эта проблема? На самом деле, очень просто:
Мы вообще отказываемся от механизма сессий в PHP, и используем свой. Генерируем SESSID путем хэширования (лучше sha1) некоторых данных пользователя (user_id вполне подойдет) и случайных данных (например, /dev/urandom). Хранить сессии можно в файлах (обязательно проверяем созданный SESSID на уникальность ;) ), а можно - в БД. Табличка с TYPE=MEMORY вполне сойдет. Попутно можно хранить некую специфическую информацию, связанную с сессией, например, User-agent, IP и т.д. для улучшения персонализации.