PHP Session 反序列化:防御加固、审计套路与总结

一、防御清单

按"性价比"排序:

1.1 根除"格式错位"(最重要)

Session 反序列化能成立的前提是写与读的 serialize_handler 不一致。只要全链路统一,就断掉了错位解析的物理基础。

  • 统一 session.serialize_handler:能全站统一就统一(推荐统一为 php_serialize,它格式最规范、不存在逐键 | 分隔的歧义);各入口/各 SAPI/CLI 与 Web 的 php.ini 要一致;

  • 不要在部分页面用 ini_set('session.serialize_handler', ...) 临时改,改了就极易和别的页面错位;

  • 更换 session 存储后端(files→Redis 等)时,确认旧数据已清空,避免旧格式文件被新后端读取。

1.2 不要让"用户可控数据"进 session,或至少别原样进

业务上少把请求参数直接 $_SESSION['x'] = $_POST['x'];。若必须存,做校验/白名单/转义,尤其警惕 | 与序列化特征串。

1.3 收敛"任意文件包含"面

Session 反序列化的两条实战链路最终都依赖"能写文件 + 能 include 文件"。堵住 LFI/RFI、对 include 路径做白名单,能同时废掉写马链路与大部分探测。

1.4 会话层面加固

  • session.use_strict_mode = 1:不接受服务器不认识的 session id,增加"自定 id 预测文件名"的难度;

  • 严格控制 session 文件目录权限(只允许运行用户读写),别放在 web 可访问/可下载的目录;

  • session.save_path 不要指向 web 根目录之类能被直接 URL 访问的地方。

1.5 upload_progress 加固(如果确实用不到)

复制代码
session.upload_progress.enabled = Off
; 或至少
session.upload_progress.cleanup = On    ; 确保上传结束立即清
session.upload_progress.freq = 1%
session.upload_progress.min_freq = 1

用不到进度条就干脆关掉,能删掉一个"不需要业务漏洞的 session 写点"。

1.6 代码层纵深

  • 反序列化对象做允许类白名单 (PHP 7.4 支持在 session_start() 里配?------不对,那是 unserialize()allowed_classes 选项;Session 解码默认不过滤类。若可行,用自定义 session handler / 反序列化前做类白名单校验,能挡住未知类注入);

  • 及时升级 PHP,跟进官方 session 相关修复;

  • 安全编码规范里明确"禁止把不可信内容无差别写入 session"这条红线。

一句话记住防御核心:统一 handler 格式 + 用户输入不进 session + 别给文件包含 + 关 upload_progress。


二、审计视角:怎么快速发现这种洞

复制代码
Step 1  找 session 的"写"与"读"分别用什么 handler
        grep serialize_handler / ini_set / session_start(['serialize_handler'
        看不同页面是否一致 —— 不一致 = 有戏
​
Step 2  找"可控数据能否进 session 文件"
        a. $_SESSION[...] = $_REQUEST/$_POST/$_GET/可拼接串 ...
        b. 无业务写点 → 看 upload_progress.enabled 是否为 On(能否用上传写点)
​
Step 3  判断写点内容能否承载 '|' + 序列化对象(有没有过滤/转义 '|')
​
Step 4  找可利用类 / POP 链(危险 __destruct/__wakeup/__toString/...,能 autoload)
         注意 php_serialize 写 + php 读 时对象类必须真实存在才会触发
​
Step 5  构造 payload → 写点提交 → 触发读取 → 验证

CTF 题面上通常给两条线索帮你走完 Step 1:

  • 一个文件里 ini_set('session.serialize_handler', 'php_serialize') 存了可控值;

  • 另一个文件/入口只有默认的 session_start()。只要把两者用同一个 PHPSESSID 串起来,就是完整攻击。


三、CTF 常见题型套路(怎么认出来、突破口在哪)

出题套路 特征线索 突破口
处理器错位(最经典) 页面 A 用 php_serialize 存用户可控值,页面 B 默认 session_start;提示词常出现 "php_serialize" 构造 |O:... 注入对象
upload_progress 注入 给了上传点但没明显业务写 session;或 hint 提 upload_progress 文件名/PHP_SESSION_UPLOAD_PROGRESS 字段塞 payload
upload_progress + LFI 存在 include $_GET[file] 写马 + include sess_<id> getshell
反序列化 POP 链考察 给了好几个类/危险函数 sink 定位 + 串链
组合题(全链路) 一个点触发反序列化,要求最终 RCE 从"注入对象"到"代码执行"要自己串:写点→错位→POP→sink

做题建议顺序(不管多花哨的题都适用):

  1. 先读源码,确认 session 相关配置与写点(别一上来就找 POP 链,先确认入口成不成立);

  2. 用本地同版本 PHP 把 payload 打在本地 session 上,xxd 看文件字节对不对、能不能触发;

  3. 再对着远程提交,利用失败时先回来查"文件字节是否真写对了"。


四、版本与配置差异速查

关注点 说明
php_serialize 处理器 PHP ≥ 5.5.4
session.upload_progress.* PHP ≥ 5.4,默认 enabled=On
解码失败行为 PHP ≥ 7 报 Failed to decode session object. Session has been destroyed. 并清空
__unserialize() 优先于 __wakeup() PHP ≥ 7.4
session id 合法字符 A-Z a-z 0-9 - ,(实测,含下划线会被拒)
session 默认文件路径 Debian/Ubuntu /var/lib/php/sessions;一般发行版 /tmp;可用 session.save_path
session 文件命名 sess_<session_id>

五、推荐资源(官方为主)

  • PHP 官方手册(中文):

    • Session 配置说明:https://www.php.net/manual/zh/session.configuration.php

    • session.serialize_handlerhttps://www.php.net/manual/zh/session.configuration.php#ini.session.serialize-handler

    • 会话上传进度:https://www.php.net/manual/zh/session.upload-progress.php

    • session_start()https://www.php.net/manual/zh/function.session-start.php

  • 魔术方法、绕过、POP 链两篇。

  • 关于"错位注入 / | 分隔"的经典思路,可以搜中文社区关键字:session 反序列化 php_serialize php 竖线注入upload_progress session 写马 LFI,结合多篇 writeup 对照理解(不同作者对"落盘时机"的描述有出入,以自己实测为准)。

  • 练习环境:本地 php -S + Docker 自建(不要用带非法字符的 session id;跑完记得清理 php -S 后台进程)。


六、最后一张图:全系列知识怎么串成记忆

复制代码
看一个请求/一个页面  →  handler 谁写的?谁读的?
                          │ 不一致
                          ▼
            能把可控字符串(含 | )弄进 session 文件吗?
               业务写点? / upload_progress? / LFI 触发?
                          │ 能
                          ▼
            文件里第一个 | 之后的字节 = 你的对象序列化
                          ▼
             php_var_unserialize 创建对象 → 魔术方法 → POP 链 → sink(RCE)
相关推荐
中科三方2 小时前
DNS拨测到底在拨测什么?
开发语言·github·php·dns·云拨测
企业通信技术笔记2 小时前
企业IM内外网隔离部署:DNS、证书、WebSocket与跨网接口设计
websocket·网络协议·架构·php·信息与通信
szarron3 小时前
RF Demo Kit|NanoVNA 射频演示测试板完整上手教程,滤波器、衰减器、SOLT 校准学习板
开发语言·人工智能·学习·php·射频工程·频谱仪
Bruce_Liuxiaowei4 小时前
数字签名与“公钥制作者之谜“:信任从何而来
网络·安全·web安全
sbjdhjd6 小时前
从 assert 动态调用报错到临时文件链路:PHP 5.6 与 7.3 授权靶场中的短 payload 复盘 | (17字符绕过)
安全·网络安全·数据挖掘·开源·云计算·php·字符绕过
sbjdhjd6 小时前
从 7 字符文件名拼接到二维数组取值:PHP 无参函数限制下的受控靶场复盘 | (7字符绕过)
网络·nginx·安全·网络安全·云计算·php·apache
fengkai45457 小时前
七、华为云网络综合实验 & 运维类及其他服务总结
服务器·开发语言·php
PHP实战开发录7 小时前
PHP日志写满磁盘后接口为什么异常
开发语言·系统架构·php·开发