Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >问答首页 >在自定义SessionHandlerInterface实现中确保会话严格模式

在自定义SessionHandlerInterface实现中确保会话严格模式
EN

Stack Overflow用户
提问于 2016-12-31 12:05:41
回答 4查看 1.9K关注 0票数 12

简介

由于PHP5.5.2有一个运行时配置选项(模式),用于防止恶意客户端进行会话固定。当启用此选项并使用本地会话处理程序 (文件)时,PHP将不会接受以前在会话存储区域中不存在的任何传入会话ID,如下所示:

代码语言:javascript
运行
AI代码解释
复制
$ curl -I -H "Cookie:PHPSESSID=madeupkey;" localhost
HTTP/1.1 200 OK
Cache-Control: no-store, no-cache, must-revalidate
Connection: close
Content-type: text/html; charset=UTF-8
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Host: localhost
Pragma: no-cache
Set-Cookie: PHPSESSID=4v3lkha0emji0kk6lgl1lefsi1; path=/  <--- looky

(在禁用session.use_strict_mode之后,响应将使而不是包含一个Set-Cookie头,并在会话目录中创建一个sess_madeupkey文件)

问题

我正处于实现自定义会话处理程序的过程中,我非常希望它能够坚持严格的模式,但是接口会使它变得很困难。

当调用session_start()时,MyHandler::read($session_id)会一直被调用,但是$session_id可以是--或者是从会话cookie 获取的值,或者是--一个新的会话ID。处理程序需要知道区别,因为在前一种情况下,如果找不到会话ID,就必须引发错误。此外,根据规范,read($session_id)必须返回会话内容或空字符串(对于新会话),但是似乎没有办法在链上引发错误。

总之,我需要回答的问题是:

  1. read($session_id)的上下文中,如何区分来自HTTP请求的新创建的会话ID和会话ID之间的区别?
  2. 给定来自HTTP请求的会话ID,并假设在存储区域中找不到它,我如何向PHP发出错误信号,以便它使用新的会话ID再次调用read($session_id)
EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2017-01-09 14:32:14

更新(2017-03-19)

我最初的实现是在session_regenerate_id()上委派的,用于生成新的会话ID,并在适当时设置cookie头。从PHP7.1.2开始,不能再从会话处理程序[1]中调用此方法。体面的达布勒还报告说,这种方法在PHP5.5.9[2]中行不通。

read()方法的以下变体避免了这一缺陷,但有些混乱,因为它必须设置cookie标头本身。

代码语言:javascript
运行
AI代码解释
复制
/**
 * {@inheritdoc}
 */
public function open($save_path, $name)
{
    // $name is the desired name for the session cookie, as specified
    // in the php.ini file. Default value is 'PHPSESSID'.
    // (calling session_regenerate_id() used to take care of this)
    $this->cookieName = $name;

    // the handling of $save_path is implementation-dependent
}

/**
 * {@inheritdoc}
 */
public function read($session_id)
{
    if ($this->mustRegenerate($session_id)) {
        // Manually set a new ID for the current session
        session_id($session_id = $this->create_sid());

        // Manually set the 'Cookie: PHPSESSID=xxxxx;' header
        setcookie($this->cookieName, $session_id);
    }

    return $this->getSessionData($session_id) ?: '';
}

FWIW已知最初的实现在PHP7.0.x下工作

原始答案

结合从Dave的答案中获得的洞察力(即扩展\SessionHandler类而不是实现\SessionHandlerInterface以窥探create_sid并解决第一个障碍)和这个Rasmus Schultz会话生命周期的精细现场研究,我想出了一个非常令人满意的解决方案:它不会将自己封装在SID生成中,也不会手动设置任何cookie,也不会将桶向上插入客户端代码。为了清楚起见,只显示了相关的方法:

代码语言:javascript
运行
AI代码解释
复制
<?php

class MySessionHandler extends \SessionHandler
{
    /**
     * A collection of every SID generated by the PHP internals
     * during the current thread of execution.
     *
     * @var string[]
     */
    private $new_sessions;

    public function __construct()
    {
        $this->new_sessions = [];
    }

    /**
     * {@inheritdoc}
     */
    public function create_sid()
    {
        $id = parent::create_sid();

        // Delegates SID creation to the default
        // implementation but keeps track of new ones
        $this->new_sessions[] = $id;

        return $id;
    }

    /**
     * {@inheritdoc}
     */
    public function read($session_id)
    {
        // If the request had the session cookie set and the store doesn't have a reference
        // to this ID then the session might have expired or it might be a malicious request.
        // In either case a new ID must be generated:
        if ($this->cameFromRequest($session_id) && null === $this->getSessionData($session_id)) {
            // Regenerating the ID will call destroy(), close(), open(), create_sid() and read() in this order.
            // It will also signal the PHP internals to include the 'Set-Cookie' with the new ID in the response.
            session_regenerate_id(true);

            // Overwrite old ID with the one just created and proceed as usual
            $session_id = session_id();
        }

        return $this->getSessionData($session_id) ?: '';
    }

    /**
     * @param string $session_id
     *
     * @return bool Whether $session_id came from the HTTP request or was generated by the PHP internals
     */
    private function cameFromRequest($session_id)
    {
        // If the request had the session cookie set $session_id won't be in the $new_sessions array
        return !in_array($session_id, $this->new_sessions);
    }

    /**
     * @param string $session_id
     *
     * @return string|null The serialized session data, or null if not found
     */
    private function getSessionData($session_id)
    {
        // implementation-dependent
    }
}

注意:类忽略session.use_strict_mode选项,但始终遵循严格的行为(这实际上是我想要的)。以下是我所看到的更完整的实现中的测试结果:

代码语言:javascript
运行
AI代码解释
复制
marcel@werkbox:~$ curl -i -H "Cookie:PHPSESSID=madeupkey" localhost/tests/visit-counter.php
HTTP/1.1 200 OK
Server: nginx/1.11.6
Date: Mon, 09 Jan 2017 21:53:05 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
Set-Cookie: PHPSESSID=c34ovajv5fpjkmnvr7q5cl9ik5; path=/      <--- Success!
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache

1

marcel@werkbox:~$ curl -i -H "Cookie:PHPSESSID=c34ovajv5fpjkmnvr7q5cl9ik5" localhost/tests/visit-counter.php
HTTP/1.1 200 OK
Server: nginx/1.11.6
Date: Mon, 09 Jan 2017 21:53:14 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache

2

以及测试脚本:

代码语言:javascript
运行
AI代码解释
复制
<?php

session_set_save_handler(new MySessionHandler(), true);

session_start();

if (!isset($_SESSION['visits'])) {
    $_SESSION['visits'] = 1;
} else {
    $_SESSION['visits']++;
}

echo $_SESSION['visits'];
票数 4
EN

Stack Overflow用户

发布于 2017-03-20 02:40:49

鉴于有一个已经被接受的答案,我提供了一个尚未提及的替代方案。

从PHP 7开始,如果会话处理程序实现了一个validateId()方法,PHP将使用它来确定是否应该生成新的ID。

不幸的是,这并不适用于PHP 5,因为在PHP 5中,用户空间处理程序必须自己实现use_strict_mode=1功能。

有条捷径,但让我先回答你的直接问题.

read($session_id)的上下文中,如何区分来自HTTP请求的新创建的会话ID和会话ID之间的区别?

乍一看,这似乎会有所帮助,但这里的问题是,read()对此毫无用处。主要有以下两个原因:

  • 此时,会话已被初始化。您希望拒绝不存在的会话ID,而不是初始化它们,然后丢弃它们.
  • 读取不存在的会话ID的空数据和/或只返回新创建的ID的空数据没有区别,因此,即使您知道正在处理对不存在的会话ID的调用,这对您也没有多大帮助。

您可以从session_regenerate_id()内部调用read(),但这可能会产生意外的副作用,或者如果您预期到这些副作用,则会使您的逻辑变得非常复杂.

例如,基于文件的存储将围绕文件描述符构建,这些描述符应该在read()内部打开,但是session_regenerate_id()将直接调用write(),此时您将没有(正确的)文件描述符可写入。

给定来自HTTP请求的会话ID,并假设在存储区域中找不到它,我如何向PHP发出错误信号,以便它使用新的会话ID再次调用read($session_id)

有很长一段时间,我讨厌用户空间处理程序不能发出错误条件的信号,直到我发现您可以这样做。

事实证明,它实际上是设计用来将布尔型truefalse作为成功或失败处理的。只是PHP处理这个问题的方式有一个非常微妙的错误.

在内部,PHP分别使用0-1值来标识成功和失败,但是处理转换为truefalse用于用户空间的逻辑是错误的,实际上暴露了这个内部行为,并且没有文档化。

这在PHP 7中是固定的,但与PHP 5一样,因为这个bug非常非常老,修复后会导致大量BC中断。在这个PHP RFC中有更多的信息,其中提出了针对PHP7的修复。

因此,对于PHP 5,您实际上可以从内部会话处理程序方法中返回int(-1)来表示错误,但这对于“严格模式”的强制执行并不是很有用,因为它会导致完全不同的行为--它会发出E_WARNING并停止会话初始化。

我提到的那条捷径..。

这一点一点也不明显,实际上也很奇怪,但是ext/session并不只是读取cookie并单独处理它们--它实际上使用了$_COOKIE超级全局,这意味着您可以操纵$_COOKIE来改变会话处理程序的行为!

因此,这里有一个甚至与PHP 7向前兼容的解决方案:

代码语言:javascript
运行
AI代码解释
复制
abstract class StrictSessionHandler
{
    private $savePath;
    private $cookieName;

    public function __construct()
    {
        $this->savePath = rtrim(ini_get('session.save_path'), '\\/').DIRECTORY_SEPARATOR;

        // Same thing that gets passed to open(), it's actually the cookie name
        $this->cookieName = ini_get('session.name');

        if (PHP_VERSION_ID < 70000 && isset($_COOKIE[$this->cookieName]) && ! $this->validateId($_COOKIE[$this->cookieName])) {
            unset($_COOKIE[$this->cookieName]);
        }
    }

    public function validateId($sessionId)
    {
        return is_file($this->savePath.'sess_'.$sessionId);
    }
}

您将注意到,我使它成为一个抽象类--这只是因为我懒得在这里编写整个处理程序,除非您确实实现了SessionHandlerInterface方法,否则PHP将忽略您的处理程序--只需简单地扩展SessionHandler而不覆盖任何方法,就像根本不使用自定义处理程序一样(将执行构造函数代码,但严格的模式逻辑将保留在默认的PHP实现中)。

TL;DR:在调用$_COOKIE[ini_get('session.name')]之前检查是否有与session_start()相关的数据,如果没有,则取消cookie --这告诉PHP要表现得好像根本没有接收到会话cookie,从而触发新的会话ID生成。:)

票数 5
EN

Stack Overflow用户

发布于 2017-01-09 07:23:52

我还没有测试过这个,所以它可能有用,也可能不起作用。

可以扩展默认的SessionHandler类。该类包含接口没有的相关额外方法,即create_sid()。这在PHP生成新会话ID时调用。因此,应该可以使用它来区分新会话和攻击;如下所示:

代码语言:javascript
运行
AI代码解释
复制
class MySessionHandler extends \SessionHandler
{
    private $isNewSession = false;

    public function create_sid()
    {
        $this->isNewSession = true;

        return parent::create_sid();
    }

    public function read($id)
    {
        if ($this->dataStore->haveExistingSession($id)) {
            return $this->getSessionData($id);
        }

        if ($this->isNewSession) {
            $this->dataStore->createNewSession($id);
        }

        return '';
    }

    // ...rest of implementation
}

如果您曾经这样做过,这种方法可能需要使用另一个或两个标记来处理合法的会话ID重新生成。

关于优雅地处理错误的问题,我会尝试抛出一个异常。如果这不能产生任何有用的结果,我将在应用程序级别上这样做,方法是为会话数据本身返回一个可以检查的固定值,然后由生成新ID破坏会议在应用程序中处理它,并向用户提供一个错误。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/41411367

复制
相关文章
React技巧之检查元素是否可见
原文链接:https://bobbyhadz.com/blog/react-check-if-element-in-viewport[1]
chuckQu
2022/08/19
1.1K0
js判断元素在某个区域内是否可见(转)
getBoundingClientRect用于获得页面中某个元素的左,上,右和下分别相对浏览器视窗的位置。getBoundingClientRect是DOM元素到浏览器可视范围的距离(不包含文档卷起的部分)。
山河木马
2019/03/05
7.7K0
CSS样式更改——多列、元素是否可见、图片透明度
上篇文章主要讲述了CSS样式更改中的过渡、动画基础知识,这篇文章我们来介绍下CSS样式更改中多列、元素是否可见、图片透明度知识。。
前端皮皮
2020/11/25
1.1K0
selenium元素定位中css或者xpath不选择某一类元素
例如,下列标签中,不选择class为disable的span标签 则 这样写 dd:not(.disabled) > span 或者 dd:not(.disabled) span
小黑同学
2020/08/16
1.9K0
selenium元素定位中css或者xpath不选择某一类元素
小程序中点击子元素事件而不触发父元素的点击事件
在测试小程序的时候,发现了这样的一个bug,点击子元素事件d的时候触发父元素的点击事件,从而执行父级的点击事件,跳转到了父级的点击事件的页面了。
王小婷
2019/02/21
6.1K0
小程序中点击子元素事件而不触发父元素的点击事件
win10 uwp 弹起键盘不隐藏界面元素
本文主要讲,在我们使用手机输入的时候,会因为手机的虚拟键盘隐藏了一些界面的元素。我们有一个简单的方法让虚拟键盘不隐藏界面元素。
林德熙
2018/09/18
7150
win10 uwp 弹起键盘不隐藏界面元素
内存可见性
可见性:当一个线程修改了对象状态后,其他线程能够看到发生的状态变化。如果没有同步,这种情况就无法实现。 下面的代码说明了当多个线程在没有同步的情况下共享数据时出现的错误。在代码中,主线程和读线程都会访问共享变量ready和number。很显然代码看起来会输出42,但事实上肯可能输出0(重排序导致),甚至根本无法终止。这是因为代码中没有足够的同步机制,因此无法保证主线程写入ready和number对读线程是可见的。 public class NoVisibility{     private static b
SuperHeroes
2018/05/31
8360
【Java线程】线程安全三元素:原子性、可见性、有序性
首先大家需要思考一下何为线程安全性呢??? 《Java并发编程实战》书中给出定义:当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在调用代码中不需要任何额外的同步,这个类都能表现出正确的行为,那么这个类就是线程安全的。
沁溪源
2021/03/22
4440
光敏电阻遇上日夜切换
光敏电阻是用硫化隔或硒化隔等半导体材料制成的特殊电阻器,其工作原理是基于内光电效应。光照愈强,阻值就愈低,随着光照强度的升高,电阻值迅速降低,亮电阻值可小至1KΩ以下。光敏电阻对光线十分敏感,其在无光照时,呈高阻状态,暗电阻一般可达1.5MΩ。光敏电阻的特殊性能,随着科技的发展将得到极其广泛应用。
AomanHao
2022/01/14
4530
光敏电阻遇上日夜切换
爬虫实现csdn文章一键(批量)更换阅读类型(全部可见、粉丝可见、vip可见)
“ 动手创造美好” - bigsai
bigsai
2019/09/24
2.7K0
爬虫实现csdn文章一键(批量)更换阅读类型(全部可见、粉丝可见、vip可见)
对象可见性
此引出 Java 的一个一般设计原则——对象默认可见。如果我有一个对象的引用,就可以复制一个副本,然后将其交给另一个线程,不受任何限制。Java 中的引用其实就是类型指针,指向内存中的一个位置,而且所有线程都共用同一个地址空间,所以默认可见符合自然规律。
宇宙之一粟
2020/11/24
4780
【C语言笔记】ASCII码可见字符与不可见字符
ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。下面看一段示例代码,用于输出ASCII码:
正念君
2019/06/26
4.7K0
【C语言笔记】ASCII码可见字符与不可见字符
github设置仓库可见性 私人仓库设置他人协作/可见
GitHub.com 的所有人访问,而私有仓库只有您和您与其共享的人员可访问。 内部仓库适用于 GitHub Enterprise Cloud 并且只有企业帐户的成员可访问。 更多信息请参阅“创建内部仓库”。
种花家的奋斗兔
2020/11/13
19.3K0
github设置仓库可见性  私人仓库设置他人协作/可见
PostgreSQL vacuum可见性
2)该tuple是当前事务产生的:此时这个记录在这个事务未删除或只是被锁住或进行了delete但是delete abort了,那返回HAPTUPLE_INSERT_IN_PROGRESS;若则记录又被删除了,那返回HEAPTUPLE_DELETE_IN_PROGRESS
yzsDBA
2020/10/28
4930
PostgreSQL vacuum可见性
Volatile 可见性承诺
Java Volatile 关键字是一种轻量级的数据一致性保障机制,之所以说是轻量级的是因为 volatile 不具备原子性,它对数据一致性的保障体现在对修改过的数据进行读取的场景下(也就是数据的可见性)。比起对读操作使用互斥锁, volatile 是一种很高效的方式。因为 volatile 不会涉及到线程的上下文切换,以及操作系统对线程执行的调度运算。同时 volidate 关键字的另一个功能是解决“指令重排序问题”。
不会飞的小鸟
2020/03/17
6950
Typecho回复可见功能
最近逛博客,发现大家的博客都有回复可见的功能 回到我的博客一看发现没有,这可不得行 于是我也整了一个233,顺便分享给大家
HCG_Sky
2020/07/24
2.3K2
Unicode不可见字符
不可见字符”\u200b”为 Unicode Character ‘ZERO WIDTH SPACE’ (U+200B),可用于内容标识,不占位数。
周小董
2019/03/25
5.6K0
Unicode不可见字符
Android可见APP的不可见任务栈(TaskRecord)销毁分析
Android依托Java型虚拟机,OOM是经常遇到的问题,那么在快达到OOM的时候,系统难道不能回收部分界面来达到缩减开支的目的码?在系统内存不足的情况下,可以通过AMS及LowMemoryKiller杀优先级低的进程,来回收进程资源。但是这点对于前台OOM问题并没有多大帮助,因为每个Android应用有一个Java内存上限,比如256或者512M,而系统内存可能有6G或者8G,也就是说,一个APP的进程达到OOM的时候,可能系统内存还是很充足的,这个时候,系统如何避免OOM的呢?ios是会将不可见界面都回收,之后再恢复,Android做的并没有那么彻底,简单说:对于单栈(TaskRecord)应用,在前台的时候,所有界面都不会被回收,只有多栈情况下,系统才会回收不可见栈的Activity。注意回收的目标是不可见栈(TaskRecord)的Activity。
看书的小蜗牛
2019/02/22
1.5K0
Android可见APP的不可见任务栈(TaskRecord)销毁分析
点击加载更多

相似问题

硒python使不可见元素可见。

14

硒元素不可见

12

元素不可见,硒

12

硒元素不可见?

32

不可识别为可见的硒可见元素

27
添加站长 进交流群

领取专属 10元无门槛券

AI混元助手 在线答疑

扫码加入开发者社群
关注 腾讯云开发者公众号

洞察 腾讯核心技术

剖析业界实践案例

扫码关注腾讯云开发者公众号
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文