Bug #75496 [Asn]: Session ID Collision happened few times
| From: | nikic@php.net | Date: | Wed, 08 Nov 2017 14:05:26 +0000 |
| Subject: | Bug #75496 [Asn]: Session ID Collision happened few times | ||
| References: | 1 | Groups: | php.bugs |
| Request: | Send a blank email to php-bugs+get-212515@lists.php.net to get a copy of this message | ||
Edit report at https://bugs.php.net/bug.php?id=75496&edit=1
ID: 75496
Updated by: nikic@php.net
Reported by: dmitry dot yeskin at gmail dot com
Summary: Session ID Collision happened few times
Status: Assigned
Type: Bug
Package: Session related
Operating System: Amazon OS
PHP Version: 7.0.25
Assigned To: yohgaki
Block user comment: N
Private report: N
New Comment:
Even without an explicit check, random session ID collisions are vanishingly unlikely. Looking at
how the session ID generation worked in PHP 7.0 and your configuration, my best guess would be that
/dev/urandom is not readable from PHP. In PHP 7.0 this error case is unfortunately silently ignored,
while PHP 7.1 will trigger an error. Can you verify that /dev/urandom is readable from PHP?
Previous Comments:
------------------------------------------------------------------------
[2017-11-08 13:18:12] dmitry dot yeskin at gmail dot com
I updated session.use_strict_mode to 1 this mornjng but a fee minutes ago another collision happened
again. So strict mode doesnt help.
------------------------------------------------------------------------
[2017-11-08 09:20:41] dmitry dot yeskin at gmail dot com
Thanks for your response.
It was not an attack. It was 2 old ladies who both bought Christmas postcards for children. Both
successfully paid. And both complained that wrong data in their orders.
These 2 customers were even anonymous customers - they made orders without any login. Website is
simple - you come to postcard page - click "Add to cart", and information about postcard
added to user $_SESSION. Then in cart and checkout - they just pay for it and data about order saved
to database. Nothing too fancy. I attached code for addToCart method as example.
Regarding your question of php.ini settings, here it is:
---
session.auto_start Off
session.cache_expire 180
session.cache_limiter nocache
session.cookie_domain no value
session.cookie_httponly On
session.cookie_lifetime 0
session.cookie_path /
session.cookie_secure On
session.entropy_file /dev/urandom
session.entropy_length 32
session.gc_divisor 1000
session.gc_maxlifetime 1440
session.gc_probability 1
session.hash_bits_per_character 6
session.hash_function sha256
session.lazy_write On
session.name PHPSESSID
session.referer_check no value
session.save_handler files
session.save_path /var/lib/php/7.0/session
session.serialize_handler php
session.upload_progress.cleanup On
session.upload_progress.enabled On
session.upload_progress.freq 1%
session.upload_progress.min_freq 1
session.upload_progress.name PHP_SESSION_UPLOAD_PROGRESS
session.upload_progress.prefix upload_progress_
session.use_cookies On
session.use_only_cookies On
session.use_strict_mode Off
session.use_trans_sid 0
---
We really don't have session.use_strict_mode on. But do you think it could be a reason? I
thought that PHP must check for session collisions even without it? I mean, purpose of strict mode
is to protect site from starting uninitialized sessions. I think thats not a case. Thats more
against hackers and hijackings, not about session collisions. Am I wrong? Do you really think it
will solve the problem if I enable it?
------------------------------------------------------------------------
[2017-11-08 02:23:05] yohgaki@php.net
Could you get session related INI settings by phpinfo() on the server?
Thank you.
Anyway, it would be some kind of attack most likely. As you may knew already, unremovable cookies
are created easily by JS, etc. If you don't have session_regenerate_id() before user
authentication flag is set, you(your users) might have been attacked.
I strongly suggest to enable session ID collision detection feature (session.use_strict_mode=1)
which is disabled by default. Beware that it has some overheads which I would like to eliminate
someday. Your code should be prepared for session_start() failure by ID collision.
------------------------------------------------------------------------
[2017-11-07 11:13:37] dmitry dot yeskin at gmail dot com
Description:
------------
Hello!
We have an e-commerce website written with Yii2 framework.
We don't modify session ids ourself. We just use session_start();
So here is what happened: we have a good traffic like 12000 unique users in 3 hrs of mornings every
day. And for last 2 weeks - we got a session id collision 2 times. I mean - 2 customers came to
website exactly at the same second and got shared session id.
In result, they were able to see content of each other in their cart, until they started removing
that content and even made a checkout, and then complained that ordered wrong item.
Here is data from nginx log:
--------
[ec2-user@scw ~]$ grep -r "73.111.163.135\|73.19.225.170" log.log
73.111.163.135 - - [26/Oct/2017:18:43:52 -0600] "GET /postcard?r=26oct2017 HTTP/2.0" 200
73.19.225.170 - - [26/Oct/2017:18:43:52 -0600] "GET /postcard?r=26oct2017 HTTP/2.0" 200
73.111.163.135 - - [26/Oct/2017:18:47:09 -0600] "POST /postcard?r=26oct2017 HTTP/2.0" 30
73.111.163.135 - - [26/Oct/2017:18:47:09 -0600] "GET /postcard?r=26oct2017 HTTP/2.0" 200
73.111.163.135 - - [26/Oct/2017:18:47:12 -0600] "GET /cart HTTP/2.0" 200
73.111.163.135 - - [26/Oct/2017:18:47:42 -0600] "POST /cart HTTP/2.0" 200
73.111.163.135 - - [26/Oct/2017:18:48:48 -0600] "GET /cart/checkout/ HTTP/2.0" 200
73.19.225.170 - - [26/Oct/2017:18:50:39 -0600] "POST /postcard?r=26oct2017 HTTP/2.0" 302
73.19.225.170 - - [26/Oct/2017:18:50:39 -0600] "GET /postcard?r=26oct2017 HTTP/2.0" 200
73.19.225.170 - - [26/Oct/2017:18:50:46 -0600] "GET /cart HTTP/2.0" 200
73.19.225.170 - - [26/Oct/2017:18:51:00 -0600] "GET /cart/remove/1 HTTP/2.0" 302
73.19.225.170 - - [26/Oct/2017:18:51:00 -0600] "GET /cart HTTP/2.0" 200
73.111.163.135 - - [26/Oct/2017:18:51:09 -0600] "POST /cart/checkout/ HTTP/2.0" 302
73.111.163.135 - - [26/Oct/2017:18:51:09 -0600] "GET /cart/checkout/complete HTTP/2.0" 200
73.19.225.170 - - [26/Oct/2017:18:51:17 -0600] "GET /cart/checkout/ HTTP/2.0" 200
------------
You may see that both customers came to website at the same moment to postcard page (then they
clicked add to cart and proceeded to cart, and then one of users saw wrong item there and called
/cart/remove/1, then another user completed checkout, but he bought a wrong item and then complained
to us).
For 2 weeks we got 2 similar cases.
After the first case we changed a hash entropy php settings in php.ini so session_id is now 48 chars
but it didn't help to prevent second case.
We solved this issue by adding user_agent to session var on controller init:
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
if (!isset($_SESSION['user_agent']) ||
empty($_SESSION['user_agent']) || $_SESSION['user_agent'] ===
Yii::$app->request->getUserAgent()) {
} else {
session_regenerate_id();
unset($_SESSION['user_agent']);
unset($_SESSION['cart']);
}
if (!isset($_SESSION['user_agent'])) {
$_SESSION['user_agent'] = Yii::$app->request->getUserAgent();
}
But still wondered why this happens? I read in github php source code that php must use IP address
and random value for session_id generation so it must be unique and i also read that php has some
mechanisms to prevent session_id collisions. But looks like it doesn't work correctly.
Again - everything great for thousands users. It only happened twice for last 2 weeks.
We dont modify session_id value ourself. We just use session_start and work with $_SESSION variables
after that. What are we doing wrong?
Is it a bug?
We have PHP 7.0.21 working as php-fpm and nginx
PS. Doing PHP coding for 20 years, and see this for the first time.
Test script:
---------------
/**
* Process adding line-item to cart
*
* @return boolean the status of addition
*/
public function addToCart() {
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
if (!isset($_SESSION['cart'])) {
//$_SESSION['cart'] = array('items' => array(),
'coupon' => array(), 'subtotal' => '', 'discount'
=> '', 'shipping' => '', 'total' => '');
$_SESSION['cart'] = array('items' => array(), 'coupon'
=> array(), 'cost' => '', 'discount' => '',
'shipping' => '', 'total' => '');
}
$cost = ($this->price + 1 - 1); // Converting string to number
if (is_array($this->addons) && count($this->addons)>0) {
foreach ($this->addons as $addon) {
if (isset($addon['price']) &&
is_numeric($addon['price']) && !empty($addon['price'] + 1 - 1)) $cost +=
$addon['price'];
}
}
$shippingCost = 0;
if ($this->type == 'package' || $this->type == 'postcard' ||
$this->type == 'book') {
$data = $this->data;
if (isset($data['shipping']) && !empty($data['shipping'])
&& isset($data[$data['shipping']]) &&
is_numeric($data[$data['shipping']]) && !empty($data[$data['shipping']]
+ 1 - 1)) $shippingCost = $data[$data['shipping']];
}
$_SESSION['cart']['items'][] = array(
'id' => $this->id,
'product_id' => $this->product_id,
'sku' => $this->sku,
'title' => $this->title,
'type' => $this->type,
'description' => $this->description,
'price' => number_format((float)$this->price, 2, '.',
''),
'picture' => $this->picture,
'data' => $this->data,
'address' => $this->address,
'addons' => $this->addons,
'cost' => number_format((float)$cost, 2, '.', ''),
'shipping' => number_format((float)$shippingCost, 2, '.',
''),
'subtotal' => number_format((float)($cost + $shippingCost), 2,
'.', ''),
'draft' => $this->draft,
);
$cost = 0;
$shipping = 0;
foreach ($_SESSION['cart']['items'] as $key => $item) {
$cost += $item['cost'];
$shipping += $item['shipping'];
}
$discount = (isset($_SESSION['cart']['discount']) &&
!empty($_SESSION['cart']['discount'])) ?
$_SESSION['cart']['discount'] : 0;
$_SESSION['cart']['cost'] = number_format((float)$cost, 2,
'.', '');
$_SESSION['cart']['shipping'] = number_format((float)$shipping, 2,
'.', '');
$_SESSION['cart']['total'] = number_format((float)($cost + $shipping -
$discount), 2, '.', '');
return true;
}
------------------------------------------------------------------------
--
Edit this bug report at https://bugs.php.net/bug.php?id=75496&edit=1