新手勇闯网络安全 | PHP基础(四)

新手勇闯网络安全 | PHP基础(四)

前三篇掌握了 PHP 基础语法、超全局变量和文件操作,这一篇进入安全核心:危险函数与反序列化漏洞。eval、system、include、unserialize------这些函数本身不是恶意的,但当用户输入未经校验就传入这些函数时,就会触发代码执行、命令注入、文件包含和反序列化漏洞。理解危险函数的原理,才能看懂 WebShell 是怎么来的、反序列化是怎么打穿服务器的。


一、PHP 数据类型与面向对象基础

1.1 八种基本数据类型

PHP 是弱类型语言,变量无需声明类型即可直接赋值。理解数据类型是掌握面向对象和反序列化漏洞的前提。

类型 说明 示例 gettype() 返回值
int 整数,不带小数点 $age = 18 integer
float 浮点数,带小数点 $price = 99.9 double
string 字符串,用引号包裹 $name = "admin" string
bool 布尔值,true/false $isOk = true boolean
array 数组,存储多个值 $list = [1, 2, 3] array
object 对象,类的实例 $obj = new Class() object
null 空值,什么都没有 $x = null NULL
resource 资源类型,外部资源引用 $fp = fopen() resource

1.2 类与对象

类(class)是定义对象属性和方法的抽象模板,对象(object)是类通过 new 关键字实例化后的具体实体。对象同时包含数据(属性)和行为(方法)。

php 复制代码
<?php
class Dog {
    public $name = "柴犬";  // 属性:数据
    public function bark() {  // 方法:行为
        return "汪汪!";
    }
}
$dog = new Dog();  // 实例化:创建对象
echo $dog->name;   // 访问属性:柴犬
echo $dog->bark(); // 调用方法:汪汪!
?>

面向过程强调「怎么做」(按步骤执行),面向对象强调「谁来做」(划分角色与职责)。反序列化漏洞的攻击面正是面向对象中的魔术方法。

1.3 访问修饰符

修饰符 类内部 子类 外部 安全意义
public 可访问 可访问 可访问 属性可被外部直接修改,存在安全风险
private 可访问 不可访问 不可访问 封装性最好,反序列化时仍可被控制
protected 可访问 可访问 不可访问 子类可继承,序列化时属性名带空字节前缀

二、PHP 危险函数详解

危险函数本身并非恶意,其危险性源于将未经严格校验的用户输入直接传递给这些函数。在 Web 安全领域,危险函数是代码执行、命令注入、文件包含和反序列化漏洞的核心触发点。

2.1 代码执行函数

代码执行函数可将字符串作为 PHP 代码动态解析执行,是 WebShell 最常用的载体。

函数 特点 禁用情况
eval() 动态代码执行,属于 PHP 语言构造器(Language Construct) 无法通过 disable_functions 禁用
assert() 断言执行,PHP 5.x 为函数可禁用,PHP 7.2+ 改为语言构造器 PHP 7.2+ 无法禁用,PHP 8.0 不再执行字符串参数

关键知识点:eval() 不是标准函数,而是语言构造器,因此无法通过 php.ini 的 disable_functions 指令禁用。这是 eval() 成为 WebShell 首选载体的核心原因。安全编码规范中应严格禁止将用户可控输入传入 eval()。

2.2 命令执行函数

命令执行函数直接调用操作系统命令,可导致远程命令执行(RCE)漏洞。

函数 功能说明 返回值特性 风险等级
system() 执行外部系统命令并直接输出结果 返回命令输出的最后一行 严重
exec() 执行外部系统命令 返回命令输出的最后一行(需传入数组获取完整输出) 严重
shell_exec() 通过 Shell 环境执行命令 以字符串形式返回命令的完整输出 严重
passthru() 执行外部命令并直接输出原始数据(含二进制) 无返回值,直接输出到浏览器 严重
popen() 打开进程文件指针,建立管道通信 返回文件指针,需配合 fread() 读取输出 高
proc_open() 执行命令并建立可双向通信的进程管道 返回进程资源句柄,支持标准输入/输出/错误流 高

2.3 文件包含函数

文件包含函数可将指定文件的内容作为 PHP 代码执行,是本地文件包含(LFI)和远程文件包含(RFI)漏洞的触发点。

函数 特点 错误处理
include() 包含并执行文件 警告,脚本继续执行
require() 包含并执行文件 致命错误,脚本终止
include_once() 只包含一次 警告,脚本继续执行
require_once() 只包含一次 致命错误,脚本终止

文件包含漏洞分类:

漏洞类型 说明 利用条件 危害
LFI(本地文件包含) 包含服务器本地文件 无特殊配置要求 读取敏感文件、日志注入 GetShell
RFI(远程文件包含) 包含远程服务器上的恶意文件 allow_url_include=On(PHP 5.2 后默认关闭) 直接包含恶意 PHP 脚本,获取服务器权限

2.4 反序列化函数

unserialize() 函数将序列化字符串还原为 PHP 对象。若反序列化的字符串用户可控,攻击者可构造恶意序列化数据,触发魔术方法实现远程代码执行。

php 复制代码
<?php
$data = serialize($obj);    // 序列化:对象→字符串
$obj2 = unserialize($data); // 反序列化:字符串→对象(触发 __wakeup)
?>

2.5 危险函数速查表

函数类别 函数名称 对应漏洞类型 危害等级 防御建议
代码执行 eval() 代码注入 / RCE 严重 避免使用;如必须使用则实施严格白名单校验
代码执行 assert() 代码注入 / RCE 严重 PHP 7.2+ 已废弃为函数;避免在生产环境使用
命令执行 system() 命令注入 / RCE 严重 使用 escapeshellarg()/escapeshellcmd() 过滤输入
命令执行 exec() 命令注入 / RCE 严重 同上;限制可执行命令范围
命令执行 shell_exec() 命令注入 / RCE 严重 同上;优先使用内置函数替代外部命令
命令执行 passthru() 命令注入 / RCE 严重 同上;注意二进制输出可能绕过检测
命令执行 popen() 命令注入 高 严格校验命令参数;最小权限运行
命令执行 proc_open() 命令注入 高 同上;监控进程创建行为
文件包含 include() LFI / RFI 严重 关闭 allow_url_include;实施白名单校验
文件包含 require() LFI / RFI 严重 同上;错误处理中避免泄露路径信息
反序列化 unserialize() 对象注入 / RCE 严重 避免反序列化用户可控输入;使用 JSON 替代

三、文件上传漏洞原理与防护

3.1 WebShell 工作原理

WebShell 是一段运行在目标服务器上的恶意脚本,攻击者通过 HTTP/HTTPS 协议向其发送命令,WebShell 执行命令后将结果返回。通信过程通常使用 POST 请求,参数名即为连接密码。

3.2 文件上传漏洞成因

文件上传漏洞的核心成因是:服务器对上传文件未做充分校验,导致攻击者可以上传恶意脚本文件,并通过 URL 访问执行。

常见上传绕过技巧(了解原理,用于防护):

绕过类型 原理 对应防护
扩展名绕过 使用 .php5、.phtml、.phar 等特殊后缀 使用白名单校验,仅允许指定后缀
MIME 类型绕过 将 Content-Type 改为 image/jpeg 校验文件内容(文件头魔数),不仅依赖 MIME
文件头绕过 在 PHP 代码前添加 GIF89a 校验文件内容完整性,不仅检查文件头
解析漏洞 利用 Apache AddHandler、Nginx CGI 解析漏洞 配置服务器正确解析规则,上传目录禁执行

3.3 防护方案

防护层级 具体措施
后缀名校验 使用白名单校验,仅允许指定后缀(如 jpg、png、gif),禁止黑名单方式
文件类型校验 校验文件内容(文件头魔数),不仅依赖 MIME 类型
文件名处理 上传文件重命名为随机文件名,禁止使用原始文件名
存储目录 上传目录禁止执行脚本,配置服务器将该目录解析为纯静态文件
权限控制 上传目录设置最小权限,禁止写入可执行文件

3.4 disable_functions 绕过原理

当目标服务器通过 php.ini 的 disable_functions 禁用了命令执行函数时,可能的绕过技术分为三大类:

绕过类型 技术原理 说明
函数替代类 利用未被禁用的同类功能函数 如 popen()/proc_open() 管道执行
环境劫持类 通过环境变量机制劫持系统函数调用 如 LD_PRELOAD 劫持
漏洞利用类 借助组件漏洞执行命令 如 ImageMagick 命令注入、Apache mod_cgi

防护建议:不仅要禁用命令执行函数,还应限制 PHP 进程权限、禁用危险扩展、定期更新组件版本。


四、魔术方法详解

魔术方法是 PHP 中以两个下划线(__)开头的特殊方法,在特定条件下自动触发,无需手动调用。反序列化漏洞的核心利用面正是魔术方法的自动触发机制。

魔术方法 触发时机 反序列化中的作用 利用风险
__construct() new 创建对象时 反序列化过程不调用构造函数 低
__destruct() 对象销毁时(脚本结束/unset) 反序列化对象生命周期结束时自动调用 高
__wakeup() unserialize() 反序列化完成时 反序列化后立即触发,是首要利用点 高
__sleep() serialize() 序列化之前 控制哪些属性被序列化,影响 POP 链构造 中
__toString() 对象被当作字符串使用时(echo/拼接) 在输出或字符串操作场景中触发 高
__call() 调用对象中不存在的方法时 在方法调用链(POP 链)中作为中转触发 中
__callStatic() 调用类中不存在的静态方法时 静态方法调用链中触发 中
__get() 访问对象中不存在的属性时 属性访问链中触发,常用于 POP 链构造 中
__set() 设置对象中不存在的属性时 属性赋值链中触发 低
__isset() 对不存在属性调用 isset()/empty() 时 属性检查链中触发 低
__unset() 对不存在属性调用 unset() 时 属性销毁链中触发 低

关键知识点:反序列化过程中,__construct() 不会被调用,但 __wakeup() 和 __destruct() 都会被自动执行。这是反序列化漏洞能够利用的核心机制------攻击者无需调用任何方法,只需构造恶意序列化字符串,PHP 引擎会自动触发危险的魔术方法。


五、序列化与反序列化基础

5.1 序列化格式

serialize() 将 PHP 变量(包括对象)转换为可存储或传输的字符串格式,unserialize() 将字符串还原为 PHP 变量。

基本类型序列化示例:

类型标识 对应类型 格式示例 说明
i integer i:60; 整数序列化
d double d:3.14; 浮点数序列化
s string s:5:"hello"; 字符串长度+内容
b boolean b:1; 1=true, 0=false
a array a:2:{...} 元素数量+键值对
O object O:4:"User":1:{...} 类名长度+类名+属性数量
N null N; 无附加数据

5.2 对象序列化结构解析

以一个简单对象的序列化字符串为例,逐字段解析结构:

css 复制代码
O:4:"Hack":1:{s:3:"cmd";s:6:"whoami";}
字段 值 含义
类型标识 O Object,表示这是一个对象
类名长度 4 类名 Hack 的字符长度
类名 "Hack" 对象所属的类名
属性数量 1 该对象包含 1 个属性
属性名类型 s string,属性名为字符串类型
属性名长度 3 属性名 cmd 的长度
属性名 "cmd" 属性名称
属性值类型 s string,属性值为字符串类型
属性值长度 6 属性值 whoami 的长度
属性值 "whoami" 属性的具体值

六、反序列化漏洞原理

6.1 漏洞本质

反序列化漏洞(PHP Object Injection)的本质是:应用信任了用户提供的序列化数据,未做校验即反序列化,导致攻击者构造的恶意对象中的魔术方法被自动执行。

漏洞公式:

反序列化漏洞 = 魔术方法中存在危险操作 + 反序列化数据用户可控 + 危险函数可执行命令

三个条件同时满足即可构成 RCE 漏洞。

6.2 攻击流程

步骤 操作 说明
1. 侦察 识别目标存在 unserialize() 且参数用户可控 通过代码审计或黑盒测试发现反序列化入口
2. 构造 Payload 本地创建恶意对象,设置危险属性,serialize() 生成字符串 构造包含危险操作的序列化数据
3. URL 编码 对 payload 进行 URL 编码,避免特殊字符被过滤 urlencode() 处理后附加到 URL 参数
4. 发送请求 访问目标页面,传入编码后的 payload 服务器接收并反序列化恶意数据
5. 获取结果 服务器自动触发 __wakeup → 危险函数,返回命令执行结果 输出命令执行结果

6.3 POP 链利用技术

POP(Property-Oriented Programming)链是反序列化漏洞利用的核心技术。在真实应用中,直接存在 __wakeup → system() 这种危险类的情况很少,攻击者需要利用目标应用中已存在的类的魔术方法调用链(Gadget Chain),将原本无害的对象操作串联为完整的恶意代码执行路径。

POP 链构造原理:

环节 说明 示例
起点(Sink) 最终执行危险操作的方法 __destruct 中调用 file_put_contents 写文件
中转(Gadget) 连接各魔术方法的调用链 __toString → __call → __get → 危险方法
属性控制 通过序列化字符串控制对象属性值 将文件名属性设为恶意路径,内容属性设为恶意代码
触发入口 反序列化后自动触发的第一个魔术方法 __wakeup() 或 __destruct()

phpggc(PHP Generic Gadget Chains)是主流的 POP 链利用工具,内置了 Laravel、Symfony、ThinkPHP、CodeIgniter、WordPress 等主流 PHP 框架和 CMS 的已验证 POP 链。


七、漏洞修复方案

7.1 反序列化漏洞修复

修复方法 实现方式 安全等级 适用场景
禁用反序列化 使用 json_decode() 替代 unserialize() 最高 新开发项目,JSON 不会触发魔术方法
白名单限制 unserialize($data, ['allowed_classes'=>['User','Product']]) 高 PHP 7.0+,只允许反序列化指定类
魔术方法安全 __wakeup/__destruct 中不调用危险函数 中 已有代码重构,移除危险操作
输入校验 对序列化数据进行签名或加密校验 中 需要保留反序列化功能的场景
WAF 防护 部署 WAF 拦截恶意序列化 payload 低 应急防护,不能替代代码修复

安全修复代码示例:

php 复制代码
<?php
// 安全修复方案 1:使用 JSON 替代
$data = json_decode($_GET['data'], true); // JSON 不触发魔术方法

// 安全修复方案 2:白名单限制(PHP 7.0+)
$obj = unserialize($data, [
    'allowed_classes' => ['User', 'Product'] // 只允许这两个类
]);

// 安全修复方案 3:魔术方法中移除危险操作
public function __wakeup() {
    // 移除 system()、exec()、file_put_contents() 等危险函数
    $this->init(); // 只执行安全的初始化操作
}
?>

7.2 通用防护公式

危险函数 + 用户可控输入 + 无校验 = 远程代码执行(RCE)

最小权限 + 输入校验 + 白名单限制 + 危险函数禁用 = 安全


八、知识小结

知识模块 核心概念 漏洞类型 防御要点
危险函数 eval/assert/system/exec/include/unserialize 代码执行/命令注入/文件包含/反序列化 输入校验/白名单/禁用危险函数
文件上传 扩展名校验/MIME校验/文件头检测/目录禁执行 文件上传漏洞 → GetShell 白名单后缀/文件内容检测/随机命名
面向对象 类/对象/属性/方法/访问修饰符 --- 封装性/最小权限原则
魔术方法 __wakeup/__destruct/__toString/__call/__get 反序列化漏洞触发点 魔术方法中禁止危险操作
反序列化漏洞 POP 链/Gadget Chain/phpggc 对象注入 → RCE JSON 替代/白名单限制/输入签名

从 eval 到 unserialize,从命令执行到反序列化,PHP 危险函数的本质都是「将不可信的用户输入当作代码或命令执行」。安全的核心原则只有一条:永远不要信任用户输入,所有传入危险函数的数据必须经过严格校验。理解了原理,才能在代码审计中发现漏洞,在防护中堵住漏洞。

相关推荐
ThinkerQAQ_1 小时前
并发编程(十):BlockingQueue 与 Channel 的实现——从 Runtime 到 CPU
后端
海岳云舟1 小时前
程序常驻后台运行的原理和方法
后端·全栈
彩雲演文1 小时前
Proteus仿真LCD12864B不显示/黄屏?别急着改接线,真凶是BusyCheck死循环(51单片机实战排查)
后端
Rain的Java大神之路2 小时前
Java 三层架构,90% 的人只答对一半:Controller 不是瘦,Service 不是透传,Dao 不是只写 SQL
java·后端·面试
小羊没烦恼!2 小时前
浅谈SQL Server中的事务日志(二)----事务日志在修改数据时的角色
前端·数据库·后端·python·web安全·oracle
IT_陈寒3 小时前
一文搞懂Vite的核心概念
前端·人工智能·后端
一个简单的TTS开发者3 小时前
中文TTS工具有哪些-8款主流方案横向对比
人工智能·后端·python·indextts
高频因子挖掘机4 小时前
量化回测应该保存不复权数据吗?从数据口径到收益计算的工程实践
后端·github·api
高频因子挖掘机5 小时前
实时行情为什么不应该逐只股票请求?批量查询与标的池管理实践
后端·github·api