# 一次老旧PHPCMS网站入侵排查:从SQL注入到PHP后门,以及IP白名单止血

一次老旧PHPCMS网站入侵排查:从SQL注入到PHP后门,以及IP白名单止血

一次云安全告警,让我们发现官网已经被植入PHP后门。

告警中的命令包括查找SUID程序、检查sudo权限、读取网站配置,以及探测Docker套接字。起初,我们重点关注PHP容器是否被突破。但随着访问日志、应用错误日志和文件信息逐步对应,发现最关键的问题发生在更早的阶段:

攻击者利用PHPCMS下载模块的SQL注入,让运行在宿主机上的MySQL把PHP后门直接写进了网站目录。

处置时,我们一方面收紧数据库账号权限,另一方面在Nginx增加PHP访问IP白名单,只允许固定办公及业务出口访问动态入口。

本文记录攻击证据、排查过程和处置思路。域名、IP和文件名均已脱敏,不提供完整攻击请求。

一、环境:PHP在容器,数据库在宿主机

这套官网使用较老的PHPCMS,环境结构如下:

text 复制代码
公网请求
    ↓
上层Nginx
    ↓
下层Nginx
    ↓
PHP-FPM容器
    ↓
宿主机MySQL

服务器上有三个网站,其中一个经过上层Nginx转发,另外两个直接由下层Nginx对外提供服务。

网站目录挂载到PHP容器中,MySQL直接运行在宿主机上。排查发现两个重要问题:

  • 部分网站数据库账号曾拥有全局权限,包含FILE权限;
  • MySQL进程以操作系统root身份运行。

MySQL端口虽然受到防火墙限制,但攻击者不需要直接连接数据库。他可以利用网站程序已有的数据库连接,触发恶意SQL。

数据库端口没有对公网开放,不等于SQL注入无法访问数据库。

二、从云告警追到两个PHP文件

云安全告警记录了PHP进程派生Shell执行命令的过程,主要包括:

  • 读取网站数据库配置;
  • 检查sudo权限;
  • 查找SUID程序;
  • 查看进程权限;
  • 检查Docker套接字;
  • 查看容器相关信息。

这些记录证明:攻击者已经通过PHP执行系统命令,并继续寻找提权或容器逃逸机会。

但"尝试提权"不等于"提权成功"。现有证据没有证明他获得宿主机Shell或完成容器逃逸。

随后排查访问日志,发现同一IP访问了一个异常PHP文件,并连续向另一个异常PHP文件提交POST请求:

text 复制代码
site_probe.php
site_upgrade.php

这两个文件名不属于正常业务,于是到网站目录核查内容、属主和创建时间。

第一个文件只输出固定标记,用于验证PHP能否执行;第二个接收请求参数并动态执行,是一个命令执行后门。

两个文件是先从访问日志中发现,再到文件系统核实的,并非先通过目录扫描找到。

三、文件特征指向数据库导出

两份异常文件有几个共同特征:

  • 创建时间相隔不到一分钟;
  • 属主均为root;
  • 权限为0666;
  • PHP代码后面带有多组制表符和\N。

这些特征把调查方向引向MySQL文件导出。

MySQL导出查询结果时,NULL字段可以表现为\N。导出文件的操作系统身份来自MySQL进程,而不是网站PHP进程。

因此,文件属于root,不一定代表攻击者已经通过PHP提权为root,也可能是以root运行的MySQL替他创建了文件。

但单凭文件特征不能定案,还需要寻找对应请求。

四、关键攻击请求藏在分类日志里

最初查看普通访问日志,只找到了验证文件和使用后门的请求,没有看到创建文件的请求。

后来发现,Nginx对请求进行了分类记录:

text 复制代码
access.log
admin.access.log
baiduspider.log
403.log

大量携带index.php参数的攻击请求,被分流到了admin.access.log。日志轮转后,文件名又变成:

text 复制代码
admin.access.log-YYYYMMDD

如果只检查普通access.log,或者只匹配*.log,就可能漏掉关键记录。

检查完整分类日志后,前期探测、SQL注入和后门访问使用的是同一个攻击IP。

这里容易误判的一点是:记录在admin日志中,不代表请求已经通过后台认证,只是日志分流规则将它记录在这里。

五、攻击过程如何展开

结合分类访问日志、应用错误日志和文件创建时间,可以还原出以下过程:

时间阶段 攻击行为
17:03起 探测后台、搜索、会员及其他PHPCMS入口
17:13起 探测附件接口和移动端接口
17:15起 提交SQL注入测试,不断调整参数
17:40起 探测数据库版本、账号、数据库名和数据目录
17:44起 尝试提取后台管理员字段、密码哈希等信息
17:45起 尝试通过数据库读取系统及网站配置文件
17:55起 多次提交后台登录请求
18:03后 访问模板管理、模板编辑及系统设置入口
18:14:20 尝试向临时目录导出测试文件
18:14:30 向网站目录写入验证PHP
18:14:39 访问验证PHP
18:14:58 写入命令执行后门
18:14:59起 向后门提交命令

后台信息提取、登录和模板操作请求,不能仅凭HTTP状态码确认全部成功。

但两个PHP文件的写入,有明确的导出请求、实际文件内容及创建时间相互印证。

六、附件模块准备参数,下载模块触发SQL注入

攻击日志中,两个接口反复交替出现:

text 复制代码
附件接口:attachment / attachments / swfupload_json
下载接口:content / down

附件接口没有直接执行攻击SQL,而是接收参数,处理后写入加密Cookie:

php 复制代码
$arr['src'] = safe_replace(trim($_GET['src']));
$json_str = json_encode($arr);
param::set_cookie('att_json', $json_str);

实际SQL注入点位于下载控制器。核心代码简化后如下:

php 复制代码
$a_k = sys_auth($a_k, 'DECODE', $auth_key);

parse_str($a_k);
$a_k = safe_replace($a_k);

if (isset($i)) {
    $i = $id = intval($i);
}

$rs = $this->db->get_one(array('id' => $id));

这里有三个问题。

首先,parse_str()已经解码并生成局部变量。后面过滤原字符串,不会清理已经生成的$id。

其次,只有存在$i时,才把$id转成整数。直接生成$id而没有$i,就可能跳过该分支。

最后,底层查询方法直接把值拼入SQL,没有使用参数化查询。

程序虽然在入口处统一添加转义,但后续解密、解析会重新产生数据。入口转义不能自动保护后面重新解码得到的变量。

附件过滤函数还存在替换顺序问题:删除某些字符后,可能重新组合出后续解析能够识别的编码内容。

因此,根因是下载模块的SQL注入,附件模块则是攻击链的前置环节。附件与下载参数之间的完整转换细节尚未全部还原,但注入点与文件导出行为已经能够对应。

七、没有后门,也能通过网站返回数据库内容

攻击者不需要先上传程序,才能尝试获取数据库内容。

网站原有的数据库错误处理,在开启调试配置时,会把SQL和数据库错误输出到HTTP响应。

攻击者使用报错注入,将查询结果放入故意触发的数据库错误中,再借助网站原有程序返回。

text 复制代码
恶意参数
    ↓
下载接口执行SQL
    ↓
数据库产生包含部分数据的错误
    ↓
网站错误处理输出响应

因此,读取数据库和植入PHP后门是两个阶段。前者可以依靠网站现有程序完成。

本次没有保存当时的响应正文,无法逐项确认攻击者实际取得了哪些账号、配置或文件内容。

八、SQL注入如何发展成PHP后门

SQL注入成功,不代表一定能向网站写入程序。这次能够继续写文件,是因为同时具备以下条件:

  • 数据库账号拥有FILE权限;
  • 文件导出目录没有受到有效限制;
  • MySQL进程能够写入网站目录;
  • 写入位置中的PHP能够被网站执行。

攻击请求使用MySQL的INTO OUTFILE,把查询结果导出成文件。日志中的目标路径、PHP内容,与实际文件对应。

写文件的是宿主机MySQL,执行文件的是PHP容器。这个过程不需要先完成容器逃逸。

同样,攻击者尝试使用LOAD_FILE()读取文件时,涉及的是MySQL所在宿主机的文件系统。

网站绝对路径的来源则尚未最终确认。它可能存在于数据库配置、服务器文件或某次响应中,但现有日志没有给出完整证据。

九、先止血:给PHP执行入口增加IP白名单

这套官网主要向公众提供静态页面,后台管理和发布功能使用人员较少,访问出口也相对固定。因此,我们先在Nginx层限制PHP执行入口,只允许办公及必要业务出口访问。

示意配置如下,IP均为文档示例地址,不能原样用于生产:

nginx 复制代码
# 放在http作用域
geo $allow_php_access {
    default 0;

    # 示例:办公出口
    192.0.2.10/32 1;

    # 示例:发布服务出口
    198.51.100.20/32 1;
}

在实际处理PHP的location中检查:

nginx 复制代码
location ~ \.php$ {
    if ($allow_php_access = 0) {
        return 403;
    }

    # 原有FastCGI配置
}

这个规则的目的,是让非白名单请求在进入PHP-FPM之前就被拒绝。正常静态文件仍由Nginx提供。

但使用前必须核对网站路由:

  • 如果首页实际由index.php生成,限制PHP会影响首页;
  • 如果HTML请求会内部转发到PHP,也可能返回403;
  • 自定义发布接口、后台、验证码及定时任务,需要核对调用来源;
  • 如果存在其他PHP处理规则或入口,也要纳入检查。

IP白名单适合本次"公众访问静态内容、少量固定人员使用动态功能"的环境,不能直接套用到所有PHP网站。

十、两层代理下,白名单必须识别真实客户端

其中一个网站经过上层Nginx转发,另两个直接对外服务。这使IP识别成为配置中的关键点。

下层如果只看到上层代理IP,就无法区分不同外部访问者。但如果直接相信客户端自行提交的X-Forwarded-For,又可能被伪造。

实际原则是:

  1. 上层代理按明确规则传递客户端地址;
  2. 下层只信任指定上层代理提供的地址信息;
  3. 下层依据可信还原后的客户端IP执行白名单;
  4. 直接对外的网站,不能无条件信任外部传来的转发头。

不能简单地把上层代理IP当作所有PHP请求的放行依据,否则可能让所有经过该代理的访客一起被放行。

配置后,需要分别检查HTTP、HTTPS、主域名、子域名及内部转发路径,避免某个入口遗漏,也避免正常静态页面被误拦。

十一、白名单之外,还要收紧数据库权限

IP限制减少了外部触发PHP漏洞的机会,但没有修复SQL注入。

同时,我们检查了三个网站数据库账号在不同Host条目下的权限。MySQL中的以下账号是不同授权对象:

text 复制代码
业务用户@localhost
业务用户@127.0.0.1
业务用户@容器网关地址
业务用户@%

只修改其中一个,可能遗漏程序实际匹配的账号。

处置时撤销不必要的全局权限,将业务权限限制在对应数据库范围,并重点移除FILE等高风险能力。

撤销FILE权限能够阻断这次通过数据库读取、导出文件的方式,但不能阻止账号权限范围内的数据查询和修改。

后续仍需停用不再使用的下载功能,或修复参数解析、类型校验及SQL查询方式。

十二、封住入口之后,排查还没有结束

本次已确认发生过命令执行,因此不能只加白名单、删除两个文件就结束。

后续核查应覆盖:

  • 网站程序、模板和缓存中的异常修改;
  • 后台账号、角色及登录情况;
  • 数据库中的异常数据或配置;
  • 可能暴露的数据库及后台凭据;
  • 其他网站目录和共用配置;
  • MySQL运行身份与文件系统权限。

删除可疑文件前,应先保存内容、哈希、时间信息和相关日志。恢复时,也应选用经过核验的可信备份。

重建PHP容器并不会自动清理挂载的网站目录,更不会恢复外部数据库。

这次事件说明,SQL注入、数据库高权限和可执行的网站目录组合在一起,可以把数据库漏洞直接发展成PHP后门。

IP白名单为这类使用范围有限的旧后台提供了有效的止血手段;数据库最小权限则限制了漏洞的进一步影响。两者都很有价值,但仍需配合漏洞修复和入侵后的完整核查。

相关推荐
码途行者2 小时前
处理超时未支付订单:两套轻量化方案,附 PHP 极简实现
开发语言·redis·php
vx_Biye_Design2 小时前
PHP中医养生馆预约系统60841-计算机课程设计、毕业设计
vue.js·spring boot·后端·mysql·php·课程设计·express
.Bryce.4 小时前
你了解 Blackboard(黑板系统)吗?听说过基于黑板系统实现的智能体吗?
java·网络·gpt·php·agent·codex·黑板系统
yu_zhidun12 小时前
企业电脑监控软件怎么选:别只看功能清单,先看这 6 个底层能力
开发语言·电脑·php·域智盾系统
李纲明13 小时前
WordPress 白屏排查手册:关插件、换主题、开调试、查 PHP(附停手信号)
开发语言·php·wordpress·故障排查·白屏·插件冲突
泡海椒14 小时前
JQuick-SQL 快速开始:注册内存表并执行查询
数据库·windows·sql
天远API16 小时前
零信任架构实战:基于天远公安二要素政务版构建自动化不动产交易与契税核算实名核实网关
人工智能·架构·自动化·php
roman_日积跬步-终至千里16 小时前
【数据Agent论文·查询与语义|06】RAT-SQL:怎样把问题、表和字段真正关联起来?
数据库·sql·数据分析
WeiXin_DZbishe18 小时前
基于springboot装修风格选择系统的设计与开发-计算机毕设【课程设计】77964
java·vue.js·spring boot·后端·python·php·课程设计