一个不起眼的码执输入框,一段被忽略的行攻代码注释,都可能成为攻击者撬动整个服务器的码执支点。PHP代码执行攻击,行攻说白了就是码执攻击者让你服务器上的PHP解释器,去执行他写的行攻代码。这不是码执什么高深莫测的魔法,而是行攻实打实的逻辑漏洞利用。
最常见的码执入口是`eval`、`assert`这类危险函数。行攻开发者图省事,码执把用户输入直接拼进字符串再交给`eval`处理。行攻比如某后台的码执模板功能,允许管理员输入一段“表达式”来计算价格,行攻开发者直接写了`eval('$price = '.$input.';')`。码执攻击者只需要输入`1;phpinfo();//`,服务器就乖乖执行了`phpinfo()`。更狠一点,输入`1;system('whoami');//`,直接拿到系统权限。这种问题在老旧CMS和二次开发项目里简直遍地都是。
还有一种隐蔽的路径——`preg_replace`的`/e`修饰符。这个修饰符在PHP 5.5之前是默认存在的,它会让替换的第二个参数当作PHP代码执行。很多老代码喜欢这么写:`preg_replace("/test/e", $user_input, $content)`。攻击者构造一个精心设计的字符串,就能让`$user_input`里的恶意代码跑起来。这种攻击最恶心的地方在于,它藏得深,代码审计时如果不熟悉这个特性,根本看不出来。
反序列化漏洞也是重灾区。PHP的`unserialize`函数在处理用户可控的序列化字符串时,会触发对象中的魔术方法,比如`__destruct`、`__wakeup`。攻击者可以构造一个恶意对象链,利用现有类的方法来执行任意代码。有个真实案例,某电商系统的日志功能把用户会话序列化后存文件,读取时直接反序列化。攻击者伪造了一个包含`system`调用的对象,通过日志文件注入,最终拿到了数据库权限。这类攻击拼的是对代码库的熟悉程度,攻击者往往花大量时间梳理每个类的魔术方法。
文件包含漏洞也能演变成代码执行。本地文件包含配合上传功能,攻击者先上传一个包含PHP代码的图片文件,再通过`include`包含它,图片里的代码就执行了。远程文件包含更直接,如果代码里允许包含外部URL,攻击者直接在自己的服务器上放一个包含恶意代码的txt文件,然后让目标服务器去包含。这种攻击在PHP配置开启`allow_url_include`时特别容易得手,很多开发者根本不知道这个开关默认是关闭的。
防御这些攻击,核心思路就一条:永远不要信任用户输入。具体操作上,禁用危险函数是最直接的,在`php.ini`里把`eval`、`assert`、`system`、`exec`这些函数加入`disable_functions`列表。但禁用不等于安全,很多框架内部自己会用到这些函数,一刀切会导致功能瘫痪。更靠谱的做法是输入过滤和输出编码,对进入危险函数的参数做严格的白名单校验。
代码审计时,优先搜索`eval`、`assert`、`preg_replace`、`unserialize`、`include`这些关键词,逐个分析参数是否可控。还要留意用户上传的文件类型检查,别让PHP文件混进去。对于反序列化,尽量不用`unserialize`处理外部数据,实在要用就加个`allowed_classes`限制。
说实话,PHP代码执行攻击之所以屡禁不止,很大程度是因为历史包袱太重。老项目里堆满了十年前写的代码,那时候安全意识淡薄,各种危险写法泛滥。今天修好一个洞,明天可能从另一个角落冒出来。安全是个持续对抗的过程,没有一劳永逸的银弹。唯一能做的,就是保持警惕,把每次攻击都当成一次学习机会,不断加固自己的代码。