从CVE-2018-12613漏洞剖析PHP文件包含攻击与CTF实战 1. 项目概述从一道CTF题看一个经典的Web漏洞最近在带新人复盘CTF题目正好翻到了BUUCTF平台上这道经典的[GWCTF 2019]“我有一个数据库”。这道题的核心考点是CVE-2018-12613一个在phpMyAdmin 4.8.x版本中影响深远的本地文件包含漏洞。对于刚入门Web安全或者CTF的选手来说这道题的价值远不止于拿到一个flag。它像是一个精心设计的教学案例把漏洞原理、利用链的构造、以及在实际渗透测试中如何绕过限制、扩大战果的思路都串联在了一起。今天我就以这道题为引子结合我这些年做渗透测试和代码审计的经验把这个漏洞从原理到实战利用再到防御彻底拆解一遍。无论你是想打CTF还是想深入理解文件包含漏洞的利用技巧这篇文章都会给你带来实实在在的收获。2. 漏洞核心原理深度剖析为什么index.php能被包含CVE-2018-12613的根源在于phpMyAdmin的index.php文件对传入的target参数过滤不严导致了本地文件包含。我们先别急着看利用得先弄明白它“为什么”会发生。2.1 漏洞触发点index.php中的target参数处理在phpMyAdmin 4.8.0到4.8.1版本中index.php文件的开头有这样一段关键代码。它的本意是引入一个核心的引导文件。// index.php 文件开头部分 if (! empty($_REQUEST[target]) is_string($_REQUEST[target]) ! preg_match(/^index/, $_REQUEST[target]) ! in_array($_REQUEST[target], $target_blacklist) Core::checkPageValidity($_REQUEST[target]) ) { include $_REQUEST[target]; exit; }这段代码的逻辑是如果请求中有target参数并且它是一个字符串且不以“index”开头不在黑名单$target_blacklist里并且通过了Core::checkPageValidity()函数的校验那么就直接用include语句包含这个target参数所指向的文件。问题就出在这个校验链条上。攻击者的目标就是构造一个target参数让它能够绕过所有检查最终让include语句去包含一个本不该被包含的敏感文件比如服务器的会话文件、日志文件甚至是/etc/passwd。2.2 层层突破校验函数的缺陷分析我们重点看最关键的Core::checkPageValidity()函数。这个函数位于libraries/classes/Core.php中。public static function checkPageValidity($page, array $whitelist []) { if (empty($whitelist)) { $whitelist self::$goto_whitelist; } if (! isset($page) || !is_string($page)) { return false; } // 关键点1URL解码 if (in_array($page, $whitelist)) { return true; } // 关键点2处理URL中的查询字符串?后面部分 $_page mb_substr( $page, 0, mb_strpos($page . ?, ?) ); if (in_array($_page, $whitelist)) { return true; } // 关键点3处理URL中的片段标识符#后面部分 $_page urldecode($page); $_page mb_substr( $_page, 0, mb_strpos($_page . ?, ?) ); if (in_array($_page, $whitelist)) { return true; } return false; }这个函数的设计本意是宽松的它允许target参数带上查询字符串比如db_sql.php?dbtest只要核心文件名在白名单内就行。但它犯了两个致命的错误白名单校验的位置逻辑错误它先对原始$page进行了一次in_array检查如果没通过它会尝试截取?之前的部分$_page再检查。然而它在截取?之前没有先对$page进行URL解码。这意味着如果攻击者将?字符进行URL编码变成%3f函数在第一次截取?时就会失败因为字符串里没有真实的?字符从而直接进入第三个校验逻辑块。二次URL解码与校验分离在第三个逻辑块中它先对$page进行了urldecode()然后再截取?之前的部分进行白名单校验。这里的问题是校验的对象$_page是解码并截取后的但最终函数返回true后外部include语句包含的却是原始的、未经截取的$page变量。这就产生了一个逻辑悖论我用来通过校验的“令牌”比如db_sql.php%3f和我最终包含的“载荷”比如db_sql.php?/../../../../etc/passwd可以是完全不同的东西只要它们经过这套处理流程后核心部分在白名单内即可。注意这里有一个非常重要的细节。mb_substr($page . ?, 0, mb_strpos($page . ?, ?))这行代码在$page中不存在?时mb_strpos会找到我们追加的?从而返回$page的长度然后mb_substr会截取整个$page。所以对于db_sql.php%3f/../../../xxx这样的字符串第一次截取得到的$_page就是db_sql.php%3f/../../../xxx本身它显然不在白名单里所以会走到第三次校验。2.3 漏洞利用链拼图所以一个典型的绕过Payload构造如下targetdb_sql.php%3f/../../../../etc/passwd它的利用链是这样的传入targetdb_sql.php%3f/../../../../etc/passwd。checkPageValidity函数开始校验。$page “db_sql.php%3f/../../../../etc/passwd”。第一关in_array(“db_sql.php%3f/../../../../etc/passwd”, $whitelist)不在失败。第二关截取?之前的部分。字符串中没有真实的?所以mb_strpos($page . ‘?’, ‘?’)返回的是追加的?的位置即字符串长度。$_page mb_substr($page, 0, strlen($page)) $page。in_array($page, $whitelist)还是不在失败。第三关对$page进行URL解码得到db_sql.php?/../../../../etc/passwd。然后截取?之前的部分得到$_page “db_sql.php”。in_array(“db_sql.php”, $whitelist)成功函数返回true。回到index.php由于所有检查通过执行include “db_sql.php%3f/../../../../etc/passwd”;。PHP引擎在处理include路径时会识别?为参数分隔符。但对于include/require来说?之后的部分会被视为查询字符串在包含文件时会被忽略。因此include “db_sql.php%3f/../../../../etc/passwd”实际上等价于include “db_sql.php”;但这里有一个关键技巧在PHP中?在文件路径中会被当作一个普通的字符而/是目录分隔符。所以db_sql.php?被当作一个文件名而/../../../../etc/passwd是路径回溯。由于db_sql.php?这个文件不存在PHP会沿着路径回溯最终定位到/etc/passwd文件并将其内容包含进来并在页面上显示。这就是整个漏洞最精妙也最危险的地方利用校验逻辑和实际包含逻辑的不一致性配合PHP处理包含路径的特性实现了任意文件读取。3. 靶场实战BUUCTF [GWCTF 2019] 我有一个数据库理解了原理我们到BUUCTF的靶场上手操作一遍。这道题的环境就是一个存在CVE-2018-12613漏洞的phpMyAdmin。3.1 环境启动与信息搜集访问靶场地址首先映入眼帘的就是phpMyAdmin的登录界面。我们第一步不是盲猜而是确认版本。查看页面底部版权信息或源码通常会有版本号。更直接的方法是查看引入的JS、CSS文件路径或者直接访问README、ChangeLog等文件。在本题中通过简单信息搜集即可确认是phpMyAdmin 4.8.1正好在漏洞影响范围内。即使没有明确版本我们也可以尝试漏洞利用因为特征很明显。作为攻击者我们默认没有登录凭证所以关注点在于未授权访问的入口点。index.php就是这个入口。3.2 构造与发送Payload根据上面的原理我们直接构造读取/etc/passwd的Payload验证漏洞是否存在。http://靶场地址/index.php?targetdb_sql.php%3f/../../../../etc/passwd使用浏览器访问或者用Burp Suite、cURL等工具发送这个GET请求。实操要点编码问题?必须编码为%3f这是绕过校验的关键。如果直接写?在第二关校验时就会被截断$_page会成为db_sql.php虽然也在白名单但最终包含的路径db_sql.php/../../../../etc/passwd中/前面是db_sql.php这个文件PHP会尝试打开db_sql.php目录这通常不存在会导致包含失败。路径深度../../../../的层数需要根据目标文件在Web目录下的实际位置来调整。这是一个试错过程。从/etc/passwd是绝对路径开始回溯到Web根目录通常需要4层左右。如果读取失败可以尝试增加或减少../的数量。白名单选择db_sql.php是常用的白名单文件其他如server_sql.php、tbl_sql.php等也可以尝试。核心是选择一个确定存在于$goto_whitelist中的文件。发送请求后如果页面没有显示phpMyAdmin的正常界面而是出现了/etc/passwd文件的内容包含root:x:0:0:root:/root:/bin/bash等行那么恭喜漏洞利用成功。3.3 定位与获取FlagCTF题目的flag通常不会放在/etc/passwd里。这道题的标题是“我有一个数据库”提示非常明显flag很可能就在数据库的某个地方或者与phpMyAdmin的配置、日志相关。我们的思路要扩展读取phpMyAdmin配置文件phpMyAdmin的配置文件config.inc.php中可能包含数据库连接密码甚至可能直接写有flag。尝试包含targetdb_sql.php%3f/../../../../../../var/www/html/phpmyadmin/config.inc.php路径需要根据实际情况调整常见位置如/usr/share/phpmyadmin/,/var/www/html/等。读取Web服务器日志如果题目设置了访问日志我们的请求可能被记录flag也可能被作为请求参数写入日志。可以尝试包含Apache的access.log或Nginx的access.log。targetdb_sql.php%3f/../../../../../../var/log/apache2/access.log注意包含日志文件可能导致PHP执行日志中的代码如果我们的User-Agent或Referer里被提前植入了PHP代码就能形成RCE。这是一种常见的LFI to RCE技巧。读取Session文件PHP的Session文件/tmp/sess_[sessionid]有时会存储敏感信息。但需要知道Session ID。读取题目源码尝试包含题目本身的index.php或其他php文件查看后端逻辑寻找提示。targetdb_sql.php%3f/../../../../var/www/html/index.php利用/proc目录Linux系统的/proc目录是一个虚拟文件系统包含了大量进程和系统信息。可以尝试读取/proc/self/environ包含环境变量可能泄露路径、密钥。/proc/self/fd/[数字]文件描述符可能指向已打开的文件如日志。本题的关键/proc/self/cmdline。这个文件包含了启动当前进程即php-fpm或Apache的PHP模块的完整命令。有时flag会作为命令行参数传递给PHP解释器。在本题的实际操作中经过尝试读取/proc/self/cmdline获得了关键信息http://靶场地址/index.php?targetdb_sql.php%3f/../../../../../proc/self/cmdline返回的内容可能是一串连续的字符串如php-fpm pool www...其中可能包含一个特殊的参数或路径。仔细查找可能会发现一个不寻常的路径比如/readflag。这提示我们服务器上存在一个名为readflag的可执行文件运行它就能得到flag。3.4 从LFI到RCE执行命令发现了/readflag但我们目前只有文件包含漏洞如何执行它呢这就需要将LFI升级为RCE。方法一利用PHP封装协议如果服务器没有禁用危险的PHP包装器我们可以直接使用php://filter来读取文件或者用php://input执行代码。但更直接的是用expect://或phar://不过这些通常默认不开启。方法二利用日志文件注入经典方法这是更通用的方法。前提是我们能写入一个可被包含的文件并且文件内容会被当作PHP代码执行。找到可写的日志路径。通过包含/etc/passwd和尝试常见路径我们确定了Apache日志在/var/log/apache2/access.log。将PHP代码注入日志。我们访问一个不存在的页面并在User-Agent头中携带PHP代码。GET /?php system(‘/readflag’);? HTTP/1.1 Host: 靶场地址 User-Agent: ?php system(‘/readflag’);?这个请求会被记录在access.log中日志的一行里就会包含?php system(‘/readflag’);?这段文本。包含日志文件。使用LFI漏洞去包含这个日志文件。http://靶场地址/index.php?targetdb_sql.php%3f/../../../../../../var/log/apache2/access.log当服务器执行include语句时日志文件中的?php system(‘/readflag’);?就会被当作PHP代码解析执行从而运行/readflag程序并将输出即flag返回到网页中。方法三利用/proc文件系统在本题场景下还有一个更巧妙的办法。/proc/self/fd/目录下是当前进程打开的文件描述符。如果Web服务器进程打开了某个我们可以控制的文件比如通过上传或者像stderr这样的输出流我们也许能利用它。但更直接的是我们可以尝试直接包含/proc/self/cmdline吗不行它只是文本。但我们可以尝试包含/proc/self/environ并在环境变量中注入代码不过这需要能控制环境变量如通过修改User-Agent在某些配置下会影响HTTP_USER_AGENT环境变量难度较高。在本题中通常采用日志注入法是最可靠的。发送带有恶意User-Agent的请求后再包含access.log文件即可在页面上看到/readflag命令的执行结果也就是最终的flag。实操心得在利用LFI进行日志注入时经常会遇到问题。一是日志文件权限不可读二是日志文件内容过大导致包含超时或内存不足三是PHP短标签?可能被禁用。解决方案1) 尝试其他日志路径如/var/log/nginx/access.log/var/log/httpd/access_log。2) 在注入代码时使用完整的?php ?标签。3) 如果日志文件太大可以尝试在Payload后添加大量换行符让自己的恶意代码位于日志文件末尾附近或者使用tail命令的思路但通过LFI很难直接实现。有时包含/proc/self/fd/[数字]可能指向一个较小的、实时更新的文件描述符如stderr如果该描述符被重定向到了某个文件并且我们能够写入那将是更好的利用点。4. 漏洞修复与安全加固启示CVE-2018-12613的修复方案很简单官方在4.8.2版本中修改了Core::checkPageValidity函数。核心修复点在于在校验通过后不再使用原始的$page变量进行包含而是使用经过安全处理后的$_page变量。具体来说修复后的代码在第三次校验通过后将$page变量赋值为$_page即解码并截取后的安全部分然后再返回true。这样外部include语句包含的就一定是白名单内的安全文件彻底堵死了利用校验不一致性的可能性。给开发者和运维人员的启示严格的白名单校验对于文件包含、重定向、命令执行等操作使用白名单机制永远比黑名单更可靠。白名单的范围要尽可能精确。校验与执行的一致性安全校验的逻辑必须和最终执行的逻辑完全对应。任何对用户输入进行的“净化”或“解码”操作如果执行阶段使用的是原始输入那么净化就形同虚设。这被称为“规范不一致性”漏洞。深度防御对于phpMyAdmin这样的管理工具除了修复代码漏洞还应实施网络层防御最小权限原则运行phpMyAdmin的进程权限应尽可能低避免使用root。目录限制通过PHP的open_basedir配置或Web服务器的chroot限制PHP可访问的文件系统范围。禁用危险函数在php.ini中禁用allow_url_include并根据需要限制include、require对特定目录的访问。及时更新保持所有中间件、应用和库的最新版本是防范已知漏洞最有效的手段。给安全研究人员的启示关注校验逻辑在代码审计时要特别关注那些对输入进行多次处理如解码、截取、替换的地方仔细追踪处理后的变量是否被正确使用。理解上下文同一个参数在WAF校验、应用逻辑校验和最终执行函数如include、system中其含义和解析方式可能不同。要善于利用这种“差异”来构造绕过。利用链思维一个简单的文件包含通过与服务器配置日志位置、系统特性/proc、其他漏洞文件上传结合可以演变成严重的远程代码执行。在渗透测试中要不断思考如何将发现的小问题串联起来扩大战果。5. 拓展CVE-2018-12613的变种与高级利用在真实的渗透测试或更复杂的CTF题目中漏洞利用可能不会这么直接。这里分享几个可能遇到的变种和高级技巧。5.1 白名单扩展与模糊测试phpMyAdmin的白名单$goto_whitelist并非一成不变。不同版本、不同安装方式是否启用某些功能可能导致白名单列表有差异。在实战中如果常见的db_sql.php等失效需要进行模糊测试。可以写一个简单的脚本遍历libraries/classes/目录下所有的.php文件或者从phpMyAdmin的源码中提取所有可能的入口文件批量尝试作为target的前缀。5.2 路径穿越的细微差别在利用../进行路径穿越时需要注意几个细节绝对路径与相对路径include在包含时如果参数是绝对路径以/开头则直接使用如果是相对路径则基于include_path或当前工作目录。在我们的Payload中db_sql.php?被视为一个相对于当前目录的文件名回溯../是基于这个“虚拟文件”的路径进行的。URL编码除了?编码为%3f/和.有时也需要编码以绕过某些简单的过滤。/可以编码为%2f.可以编码为%2e。但要注意PHP的include在解析路径时通常会对这些字符进行解码。空字节截断CVE-2006-7243在PHP 5.3.4之前可以在路径后添加空字节%00来截断后面的字符串绕过扩展名检查。虽然这个漏洞很老且本题环境不适用但在审计其他老系统时仍是一个思路。5.3 结合其他漏洞形成组合拳LFI 文件上传如果存在一个文件上传点但无法直接上传.php文件有后缀名检查可以尝试上传一个图片马内容为?php system($_GET[‘cmd’]);?然后利用LFI漏洞去包含这个上传的图片文件。即使后缀是.jpg只要文件内容被include当作PHP解析就能执行代码。这需要服务器配置不当如未配置exif模块或open_basedir限制不严。LFI PHP Filter链当无法直接执行代码但可以读取文件时php://filter是一个强大的工具。例如可以使用php://filter/convert.base64-encode/resourceindex.php来以Base64编码形式读取源码避免被直接执行或乱码。更进一步可以利用php://filter的多种过滤器进行编码转换有时能绕过一些简单的WAF过滤。LFI 敏感信息泄露包含/proc/self/maps可以查看进程的内存映射可能泄露库文件地址用于辅助二进制漏洞利用。包含/proc/net/tcp可以查看网络连接辅助内网渗透。5.4 自动化工具与手动测试的平衡对于CVE-2018-12613已有成熟的Metasploit模块和Python脚本。但在CTF或某些严格的渗透测试环境中自动化工具可能因为指纹明显、Payload固定而被WAF拦截。手动构造Payload灵活调整路径深度、白名单文件、编码方式成功率往往更高。理解漏洞原理永远是第一位的工具只是效率的延伸。6. 从CTF到实战漏洞挖掘的思路迁移这道CTF题目是一个完美的教学样本。从实战角度看我们遇到一个疑似phpMyAdmin的系统可以按以下步骤进行识别与版本确认通过页面特征、文件指纹、元数据如README确认是否为phpMyAdmin及其版本。漏洞探测直接使用targetdb_sql.php%3f/../../../../etc/passwd等Payload进行试探。注意观察错误信息不同版本的错误回显可能有助于判断。信息收集利用漏洞读取配置文件(config.inc.php)、日志文件、/proc/self/cmdline、/proc/self/environ等收集数据库密码、网站路径、服务器环境等信息。权限提升尝试如果获取到数据库密码尝试连接数据库查找敏感数据或利用MySQL的INTO OUTFILE功能写Webshell需要secure_file_priv设置允许。通过LFI读取Web应用的其他源码寻找二次注入点、反序列化点等。尝试日志注入RCE获取一个交互式Shell。横向移动如果获取了Shell进一步探查内网寻找其他资产。整个过程思路是连贯的发现漏洞 - 利用漏洞获取信息 - 利用信息扩大访问权限 - 达成目标。CVE-2018-12613这个LFI漏洞就是整个攻击链的起点和支点。最后再分享一个我在内部测试中的小技巧在利用LFI包含日志文件进行RCE时如果直接包含access.log因为文件太大而失败可以尝试包含error.log。有时通过故意触发404错误并在请求URL中携带PHP代码这些代码会被记录到error.log中而这个文件通常比access.log小得多包含成功的概率更大。方法就是请求一个类似/?php phpinfo();?.php的路径然后包含error.log。安全研究往往就藏在这些细微的差别和不断的尝试之中。

相关新闻

最新新闻

为什么 AI 总是“一本正经地胡说八道”?大模型幻觉终于讲明白了

为什么 AI 总是“一本正经地胡说八道”?大模型幻觉终于讲明白了

你问 AI:“这句话出自哪本书?”它给出书名、作者、出版社,甚至精确到页码。你满心欢喜地去查,却发现书里根本没有这句话。更令人困惑的是,它的语气没有一丝犹豫。这不是 AI 在故意撒谎,而是大模型最典型、也…

2026/7/15 0:23:32
ChatGPT技术文档编写:为什么Top 1%团队坚持“3轮人机协同”?——基于GitHub上2,147份PR文档的A/B测试数据揭秘

ChatGPT技术文档编写:为什么Top 1%团队坚持“3轮人机协同”?——基于GitHub上2,147份PR文档的A/B测试数据揭秘

更多请点击: https://kaifayun.com 第一章:ChatGPT技术文档编写:为什么Top 1%团队坚持“3轮人机协同”?——基于GitHub上2,147份PR文档的A/B测试数据揭秘 在对GitHub上2,147份高质量开源项目Pull Request文档进行结构化分析后&a…

2026/7/15 0:23:32
OpenHarmony 关系型数据库 RDB 完整封装(API Version23 本地持久化数据库)

OpenHarmony 关系型数据库 RDB 完整封装(API Version23 本地持久化数据库)

摘要RDB 是鸿蒙内置轻量关系型数据库,基于 SQLite 封装,适合存储结构化大量数据(笔记、用户列表、商品数据),相比 Preferences 仅适合简单键值对,RDB 支持表、字段、主键、条件分页查询、多条件筛选、批量增…

2026/7/15 0:23:32
为什么同一个问题,AI 每次给出的答案都不一样?

为什么同一个问题,AI 每次给出的答案都不一样?

第一次问 AI,它说方案 A 最合适;刷新后再问,它又推荐方案 B。写文案时,这种变化带来灵感;查事实时,却让人心里没底。同一个问题为什么没有唯一答案?是 AI 随机乱说,还是我们其实没有…

2026/7/15 0:23:32
你以为 AI 在思考,其实它只是在猜下一个词?真相没那么简单

你以为 AI 在思考,其实它只是在猜下一个词?真相没那么简单

“大模型不就是自动补全吗?”这句话既抓住了核心,又很容易把人带偏。它确实通过预测下一个词元来训练和生成,但“训练目标简单”不等于“最终能力简单”。就像钢琴只有有限的琴键,却能演奏完全不同的作品;理解 AI&…

2026/7/15 0:23:32
2026年批发订货小程序哪个好?不同行业批发商该怎么挑

2026年批发订货小程序哪个好?不同行业批发商该怎么挑

今天给大家带来2026年批发订货小程序哪个好?不同行业批发商该怎么挑。据艾瑞咨询2025年《中国小程序电商行业发展报告》,2025年中国小程序电商交易规模突破4.8万亿元,B2B批发订货类小程序占比约18%,年增速32%;国内SaaS…

2026/7/15 0:18:32

月新闻