Edit report at https://bugs.php.net/bug.php?id=75496&edit=1
ID: 75496
Updated by: yohgaki@php.net
Reported by: dmitry dot yeskin at gmail dot com
Summary: Session ID Collision happened few times
-Status: Open
+Status: Feedback
Type: Bug
Package: Session related
Operating System: Amazon OS
PHP Version: 7.0.25
-Assigned To:
+Assigned To: yohgaki
Block user comment: N
Private report: N
New Comment:
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.
Previous Comments:
------------------------------------------------------------------------
[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