- 后端
- Web框架
【免费下载链接】CodeIgniter4
Open Source PHP Framework (originally from EllisLab)
CodeIgniter 4.7.4(2026 年 7 月 7 日发布)是一次以安全加固与稳定性修正为核心的维护版本,重点修复了 HTTPS 检测信任代理绕过、批量删除 SQL 注入、上传文件路径穿越、上传校验扩展名绕过四类高危问题,并同步修复了查询构造器、Transformer、加密、Session、验证规则等模块的十余个缺陷。本文以官方发布说明为骨架,结合仓库源码逐一解析每个修复的成因、补丁行为与防御建议,帮助你在升级后正确配置(尤其是Config\App::$proxyIPs与上传校验规则),彻底理解本次变更的影响范围。
安全修复:四个安全公告逐一拆解
4.7.4 共修复了四个安全公告涉及的问题(GHSA-7wmf-pw8j-mc78、GHSA-c9w5-rwh3-7pm9、GHSA-hhmc-q9hp-r662、GHSA-mmj4-63m4-r6h5),全部属于"攻击者可主动触发的输入处理缺陷",以下按模块分别说明。
1. HTTPS 检测不再轻信客户端头:IncomingRequest::isSecure()
问题:此前IncomingRequest::isSecure()对X-Forwarded-Proto、Front-End-Https两个客户端可伪造的请求头过于信任。攻击者直接发送X-Forwarded-Proto: https即可让应用误判当前连接为 HTTPS,从而绕过依赖"必须走 HTTPS"的强制跳转或安全策略。
修复后的判定逻辑(源码见 system/HTTP/IncomingRequest.php):
- 先检查真实服务器变量
HTTPS(来自$_SERVER['HTTPS']),非空且不等于off时直接判定安全; - 只有当请求确实来自
Config\App::$proxyIPs中配置的受信代理时(isFromTrustedProxy(),实现见 system/HTTP/RequestTrait.php),才允许读取X-Forwarded-Proto与Front-End-Https头; - 否则一律返回
false。
isFromTrustedProxy()会比较REMOTE_ADDR与$proxyIPs的键名,支持单个 IP 与 CIDR 子网(含 IPv4/IPv6),因此配置必须落在代理层:
// app/Config/App.php —— $proxyIPs 配置示例 public array $proxyIPs = [ '10.0.1.200' => 'X-Forwarded-For', // 单个代理 IP '192.168.5.0/24' => 'X-Real-IP', // CIDR 子网 ];配置见 app/Config/App.php。要点:只有真实反向代理的 IP 才应加入白名单,且代理需自行剥离/覆盖客户端伪造的转发头;若直接暴露在公网且未配置$proxyIPs,则X-Forwarded-Proto等头将不再被信任,这是本次修复的预期行为。
2. 批量删除 SQL 注入:deleteBatch()的 WHERE 绑定逃逸
问题:当deleteBatch()与where()条件组合使用时,WHERE 子句中的绑定值在替换进最终 SQL 时忽略了逃逸标记(escape标志),既不转义也不加引号,形成 SQL 注入通道。漏洞触发路径位于 system/Database/BaseBuilder.php 的deleteBatch()与批量 SQL 骨架构建逻辑。
修复方式:新增convertWhereBindsForBatch()(system/Database/BaseBuilder.php),在构建批量 SQL 时将QBWhere中的每个绑定值按原始绑定携带的escape标志统一走$this->db->escape()处理(数组值则生成(v1,v2)形式),使 WHERE 绑定的转义方式与普通delete()完全一致:
// 修复前(概念):绑定值原样拼入 WHERE id = :w0: // 修复后:绑定值经 escape() 转义后替换 WHERE id = 'escaped_value'对升级用户而言,无需修改业务代码——只需确保批量删除仍通过 Query Builder 绑定传参,不要手写拼接 SQL。
3. 上传文件路径穿越:UploadedFile::move()默认文件名消毒
问题:UploadedFile::move($targetPath)在未传第二个参数(文件名)时,直接使用客户端原始文件名作为落盘名。攻击者可将文件名构造为../../public/shell.php,利用路径穿越把文件写出目标目录、直达可执行目录。
修复后行为(源码见 system/HTTP/Files/UploadedFile.php):
- 省略第二个参数时,文件名必须经过
sanitize_filename()(security helper)清洗,剥离路径穿越序列与危险字符; - 显式传入第二个参数
$name时不做消毒,文件名合法性由调用方负责(方法注释明确提示$name不会被净化)。
同时,$targetPath参数本身也不会被消毒(UploadedFile.php 的 docblock 已明确警告),因此目标路径必须由应用代码控制,绝不能用不可信输入拼接。
4. 上传校验扩展名绕过:is_image与mime_in规则升级
问题:is_image与mime_in两条上传校验规则此前只依据内容推导的 MIME 类型判定。攻击者把.php文件前面拼上图片魔数(magic bytes),即可骗过校验,同时保留磁盘上的原始危险扩展名。
修复后行为(实现见 system/Validation/StrictRules/FileRules.php):
is_image:当客户端文件名扩展名非空时,若该扩展名不是图片扩展名(通过Mimes::guessTypeFromExtension()反查),直接拒绝上传(hasInvalidImageClientExtension(),见 FileRules.php);mime_in:当客户端扩展名非空时,要求其与内容检测出的扩展名一致(hasMismatchedClientExtension(),FileRules.php),即采用与ext_in相同的"扩展名-内容一致"判定;- 两条规则还额外通过
hasUnsafeClientFilename()(FileRules.php)拒绝尾点文件名、以及"最终扩展名前带 PHP 扩展名"的双扩展名攻击(如shell.php.jpg); - 无扩展名的上传(如 JavaScript Blob 上传)仍然放行,因为校验依据的是文件内容而非文件名。
实际使用示例:
$this->validate([ 'avatar' => 'uploaded[avatar]|is_image[avatar]|mime_in[avatar,image/jpg,image/jpeg,image/png]|max_size[avatar,2048]', ]);缺陷修复:按模块分类的十余项变更
API / Transformer:fields与include参数不再泄漏到嵌套 Transformer
根请求的?fields=...与?include=...查询参数此前会渗透进include*()方法内部创建的嵌套 Transformer,导致子资源字段过滤错误、出现意外的 include,甚至触发无限递归。修复后的关键逻辑在 system/API/BaseTransformer.php:仅当请求是显式传入的IncomingRequest或self::$depth === 0(根层级)时才解析查询参数,嵌套实例不再重复读取请求参数。include*()返回的嵌套 Transformer 应在transformMany()中独立处理,参见 BaseTransformer.php。
命令生成:make:model --return entity保留子命名空间
make:model生成关联 Entity 时丢失了子命名空间(如Admin)。修复在 system/Commands/Generators/ModelGenerator.php:当--return entity时,从类名推导出的 Entity 类会保留完整的子命名空间(如Admin\User),避免生成的类落在错误的命名空间导致自动加载失败。命令选项见 ModelGenerator.php。
Common:env()处理非字符串值、esc()编码传播修复
env()此前在遇到非字符串值时(如 CLI 下$_SERVER['argc']为 int、$_SERVER['argv']为数组)会因strtolower()抛出TypeError。修复后在类型转换前先判断is_string(),非字符串原样返回(system/Common.php);esc()修复了编码参数($encoding)的传播问题并消除引用泄漏:数组递归转义时按引用遍历,且Escaper实例按编码缓存复用(system/Common.php),保证非 UTF-8 场景下转义前后的编码往返一致。
命令:spark lang:find不再覆盖已有翻译
lang:find --show-new此前把框架或其他命名空间(如system/Language中的Errors.*)已提供的翻译键当作"新键",误列到--show-new输出中,并在无--show-new时把未翻译占位符写进app/Language,覆盖现有翻译。命令定义见 system/Commands/Translation/LocalizationFinder.php,用法:
# 只查看新增待翻译键,不写文件 php spark lang:find --show-new # 指定语言与扫描目录后写入 php spark lang:find --locale zh-CN --dir Controllers配置:BaseService::injectMock()统一小写化
injectMock()此前未一致应用strtolower(),导致 Service 注册名与 Mock 注入名大小写不一致时,测试中的 Mock 无法命中。修复后注册、解析路径统一小写化(system/Config/BaseService.php)。
数据库:updateBatch()禁止与where()组合、XML 导出保留0值
- 此前
updateBatch()在已有where()条件时仍可调用,但语义并不被支持,产生不可预期的 SQL。现在一旦检测到QBWhere非空,直接抛出DatabaseException,并给出替代建议(使用updateBatch($data, $constraints)、onConstraint()或把约束字段放入批量数据),见 system/Database/BaseBuilder.php; BaseUtils::getXMLFromResult()生成的 XML 导出此前会把数值0/ 字符串'0'当作空值丢弃。修复后仅null与''被转为空标签,0正常保留(system/Database/BaseUtils.php)。
加密:SodiumHandler异常类型与blockSize处理
system/Encryption/Handlers/SodiumHandler.php 修复了三类问题:
- 运行时仅覆盖
blockSize而未传key时的处理; - 非法密钥长度不再泄漏原生 Sodium 错误;
- 解密时
blockSize不匹配时,SodiumException被统一转换为EncryptionException(如forAuthenticationFailed()),避免框架向应用层抛出第三方异常类型。
过滤器:InvalidChars检查数组键
InvalidChars过滤器此前只检查数组值,未检查字符串类型的数组键中的非法 UTF-8 与控制字符。修复后checkEncoding()与checkControl()在遍历数组时对键同样执行校验(system/Filters/InvalidChars.php),使该过滤器对整棵输入树做到无死角覆盖。
HTTP:getPostGet()/getGetPost()支持索引数组
此前向IncomingRequest::getPostGet()或getGetPost()传入索引数组(如['username', 'email'])会抛出TypeError。修复后两者均支持数组索引,逐个按键取 POST/GET 回退值并组装为关联数组返回(system/HTTP/IncomingRequest.php)。
HTTP:User Agent 正确识别 Safari 浏览器版本
User Agent 库此前把 Safari 的WebKit 内核版本误报为浏览器版本。修复在 system/HTTP/UserAgent.php:当识别到 Safari 时,改为从 UA 中的Version/...令牌解析真实浏览器版本。
Model:objectToRawArray()尊重$recursive参数
BaseModel::objectToRawArray($object, $onlyChanged, $recursive)此前忽略了$recursive,内部 Entity 不会被递归展开为数组。修复后该方法将$recursive原样传递给Entity::toRawArray()(system/BaseModel.php),含嵌套 Entity 的模型数据转换现在行为符合文档约定。
安全:CheckPhpIni容忍ini_get_all()返回null
当ini_get_all()对某个配置指令返回null时,CheckPhpIni可能抛出类型错误。修复后通过显式断言与?? ''兜底,保证指令缺失时输出disabled而非异常(system/Security/CheckPhpIni.php)。
Session:RedisHandler尊重锁重试配置
RedisHandler获取会话锁时此前无视配置的$lockMaxRetries与$lockRetryInterval。修复后构造函数从Config\Session读取这两个值(system/Session/Handlers/RedisHandler.php),重试循环按lockRetryInterval微秒间隔执行、最多lockMaxRetries次后放弃并报错(RedisHandler.php)。
测试:MockInputOutput不再破坏StreamFilterTrait的过滤器
同一测试中同时使用MockInputOutput与StreamFilterTrait时,前者的流过滤器拆装会连带拆除 trait 注册的过滤器,导致tearDown()等阶段产生的 CLI 输出不再被捕获而泄漏到控制台。修复后MockInputOutput在挂载自身过滤器前快照共享CITestStreamFilter的状态,结束后恢复(system/Test/Mock/MockInputOutput.php),保证两者可安全共存。
验证:required_without数组点号逻辑修正
required_without使用点号数组语法(如required_without[items.0.email])时,此前存在两个问题:早期continue退出导致忽略后续字段;缺失键触发Undefined array key警告。修复后的逻辑(system/Validation/Rules.php)在遍历其他字段时:非点号键直接查$data顶层;点号键经dot_array_search()解析,并用?? null安全取值,缺失时正确返回false。
升级建议与验证路径
- 立即升级到 4.7.4,四个安全修复均为攻击面较大的输入处理问题;
- 若使用反向代理,务必配置 app/Config/App.php 的
$proxyIPs,否则 HTTPS 检测将回退为仅信任$_SERVER['HTTPS']; - 检查所有
UploadedFile::move()调用:若依赖默认文件名,现在会自动消毒;若传了自定义$name,请确认该名称来自可信来源; - 对
is_image/mime_in校验失败的既有上传业务(如带非标准扩展名的图片),需调整允许的扩展名/MIME 列表或改用无扩展名上传; - 涉及
updateBatch()的代码,若与where()组合使用,请按异常提示改写为constraints或onConstraint()方式。
完整的缺陷修复清单可查阅仓库根目录的 CHANGELOG.md 与各版本的发布说明(changelogs 目录),相关的回归测试覆盖可在 tests/system 目录中按模块(Database、HTTP、Validation、Encryption 等)进一步追踪。
- 后端
- Web框架
【免费下载链接】CodeIgniter4
Open Source PHP Framework (originally from EllisLab)
相关推荐
CodeIgniter 4.7.5 更新详解:setJSON()/setXML() 类型契约调整与 18 项缺陷修复全解析
CodeIgniter 4.7.5 更新详解:setJSON /setXML 类型契约调整与 18 项缺陷修复全解析 本篇技术指南以 CodeIgniter 4
后端Web框架CodeIgniter 4.5.8 版本解析:HTTP 头校验安全修复与 affectedRows、CSRF 两项缺陷修复
CodeIgniter 4.5.8 版本解析:HTTP 头校验安全修复与 affectedRows、CSRF 两项缺陷修复 本文围绕 CodeIgniter4
后端Web框架Neon安全公告:问题修复与安全更新通知
Neon安全公告:问题修复与安全更新通知 📢 重要安全更新 Neon团队近期发现并修复了多个安全问题,这些问题可能影响Neon Serverless Post
数据库后端分布式数据库云原生
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考