【愚公系列】2024年08月 《CTF实战:从入门到提升》 009-Web安全入门(PHP文件包含漏洞)
🏆 作者简介,愚公搬代码
🏆《头衔》:华为云特约编辑,华为云云享专家,华为开发者专家,华为产品云测专家,CSDN博客专家,CSDN商业化专家,阿里云专家博主,阿里云签约作者,腾讯云优秀博主,腾讯云内容共创官,掘金优秀博主,51CTO博客专家等。
🏆《近期荣誉》:2022年度博客之星TOP2,2023年度博客之星TOP2,2022年华为云十佳博主,2023年华为云十佳博主等。
🏆《博客内容》:.NET、Java、Python、Go、Node、前端、IOS、Android、鸿蒙、Linux、物联网、网络安全、大数据、人工智能、U3D游戏、小程序等相关领域知识。
🏆🎉欢迎 👍点赞✍评论⭐收藏
🚀前言
PHP文件包含漏洞是指在PHP代码中存在安全漏洞,允许攻击者包含并执行恶意文件或代码。
PHP文件包含漏洞通常发生在以下两种情况下:
-
动态文件包含:当PHP代码使用动态输入来包含文件时,攻击者可能通过构造恶意输入来包含并执行任意文件。这可能会导致攻击者执行远程代码、读取服务器上的敏感文件,或者执行其他恶意操作。
-
本地文件包含:当PHP代码使用本地文件路径来包含文件时,攻击者可能通过修改文件路径参数来包含并执行任意文件。这可能会导致攻击者读取服务器上的敏感文件,或者执行其他恶意操作。
攻击者利用PHP文件包含漏洞可以执行各种恶意操作,包括但不限于获取敏感信息、修改数据、执行远程代码等。为了防止PHP文件包含漏洞,开发人员应该遵循安全编码实践,对用户输入进行正确的过滤和验证,并且不直接使用用户输入来包含文件。另外,及时更新和维护PHP的版本,以防止已知的漏洞被攻击利用。
🚀一、PHP文件包含漏洞
在PHP开发过程中,为了缩减单个PHP文件的代码行数,也为了提升代码的复用性,通常会将这些代码拆分编写当最后要运行时再用相应的语法互相包含。而一旦这些语法使用不恰当,就会存在安全漏洞。
🔎1.PHP中常见的文件包含函数
在PHP中,从一个PHP文件中包含其他PHP文件的语法或者说函数有如下几个:
include、include once、 require 、require once。
使用的例子如下,以下例子均是包含b.php:
-
PHP文件包含函数中的"once":在PHP中,可以使用include_once()或require_once()来包含文件。这些函数会在包含文件之前检查是否已经包含过,如果已经包含过,则不会再次包含。这意味着无论在代码中多次调用相同的包含语句,只会包含一次该文件。
-
require和include的区别:在PHP中,require和include都是用于包含文件的语句,但它们在遇到错误时的行为有所不同。当include包含文件时,如果遇到了一些错误(如文件不存在),程序会抛出一个警告,但会继续执行后续的代码。这意味着即使包含文件失败,程序仍然可以继续往下执行。而当require包含文件时,如果遇到错误,程序会抛出一个致命错误,并且不会继续执行后续的代码。这意味着如果包含文件失败,程序将停止执行并输出错误信息。
总结起来,使用include_once或require_once可以确保文件只会被包含一次,而require语句在包含文件失败时会停止程序执行,而include语句则会抛出警告但继续执行。开发人员可以根据具体的需求和错误处理策略选择合适的包含语句。
🔎2.PHP中文件包含漏洞的概念
再回到最基本的概念上,文件包含顾名思义就是把其他文件包含进来,在开发中经常用到,例如,项目结构如下:
在上面的项目中,index.php和search.php都要进行SOL查询,难道每次都要写重复代码连接数据库进行查询吗?
文件包含不一定必须是PHP扩展名:PHP的文件包含功能会先读取文件,并根据文件内容进行处理。如果文件内容是PHP代码,那么它会将代码解析为PHP代码并包含进来,此时与文件的扩展名无关。如果文件内容不是PHP代码,它会将内容原样输出。
文件包含漏洞的危害与处理方式有关:
-
第一种情况:攻击者上传恶意文件并包含:攻击者可以将恶意的文件(扩展名可以是任何类型)上传到服务器上,并在其中包含恶意的PHP代码。当被包含的文件被执行时,恶意代码也会被执行,导致攻击者可以执行任意的PHP代码,可能导致数据泄露、系统崩溃等严重后果。
-
第二种情况:直接包含非PHP代码的文件:攻击者利用文件包含漏洞直接包含非PHP代码文件,如系统敏感文件(如 /etc/passwd)。当被包含的文件是非PHP代码时,它会直接输出文件内容,这就变成了一个任意文件读取漏洞,可能导致敏感信息泄露。
因此,在应用程序中要特别注意文件包含漏洞,确保只包含可信任的文件,并对用户上传的文件进行严格的安全检查和过滤,以防止恶意代码的执行和敏感信息的泄露。
🔎3.PHP本地文件包含漏洞
知道了什么是文件包含,接下来就学习下文件包含漏洞及常见的利用。本地文件包含,顾名思义,包含的文件必须存储在本地。来看下面这样一段代码:
观察上面的代码,我们可以看到两个不同的区别点:
-
第一行代码直接将file参数的值传入include,没有任何限制。这意味着攻击者可以通过传递文件路径或URL,直接利用文件包含漏洞。他们可以使用绝对路径或协议的方式来访问任意文件,并执行其中的代码。
-
第二行代码会在file参数的值前面拼接上一个绝对路径(/var/www/html/)。这样做的目的是为了限制文件包含的路径,制定了一个固定的前缀。这种方式会限制攻击者通过传递绝对路径或协议的方式来利用文件包含漏洞。但仍然可能存在目录遍历漏洞(如使用…/…/跨目录),攻击者可以利用此漏洞访问到系统敏感文件。
在解决文件包含漏洞时,需要考虑后端源代码的编写方式。根据代码的限制和处理方式,我们可以进行猜测和预测。例如,当存在一个接口/?file=admin.php时,我们可以猜测后端源码可能是这样的:
- 如果没有限制,不做任何处理直接包含文件(include $_GET[‘file’];)
- 如果进行了限制,在传入的文件路径前面拼接了一个固定的绝对路径(include ‘/var/www/html/’ . $_GET[‘file’];)
- 或者限定了仅包含特定扩展名的文件(如只允许包含.php文件:include $_GET[‘file’] . ‘.php’;)
对于不同的代码处理方式,探测方法也会有所不同:
-
无限制: 攻击者可以直接输入敏感文件路径(如/etc/passwd),如果返回了passwd文件的内容(一般以"root: x:"开头),说明后端没有对文件包含进行任何处理,直接将参数传入到包含函数中。
-
前面限制(路径拼接): 如果没有得到任何反应,那么说明参数的开头或者PHP配置可能受到了限制。此时,攻击者可以尝试使用目录遍历(如使用…/跨目录)来读取文件,例如尝试使用"…/etc/passwd"来读取密码文件。
此外,还有一种限制是对于包含文件的扩展名进行限制,例如只允许包含.php文件。这种情况下,攻击者可以尝试利用文件包含伪协议来绕过扩展名限制,将文件包含漏洞转化为任意文件读取漏洞。下面是一些常用的伪协议:
- php://input: 从标准输入中读取数据。
- php://filter/convert.base64-encode/resource=/etc/passwd: 将指定文件进行base64编码后输出。
- data:text/plain;base64,SGVsbG8gV29ybGQ=: 将base64编码的数据作为文件内容输出。
了解如何判断文件包含漏洞之后,我们可以尝试将其转化为任意文件读取漏洞以进行信息搜集。在CTF竞赛中,有价值的文件路径通常包括以下内容:
- /etc/passwd: 包含了系统用户的信息。
- /etc/hosts: 包含了主机名和IP地址的映射关系。
- /etc/shadow: 包含了系统用户的密码信息(通常权限限制)。
- /proc/self/environ: 包含当前进程的环境变量信息。
- /var/log/*: 包含了各种系统日志,可能包含敏感信息。
以上就是“任意文件读取漏洞”的利用方式,那么如何包含文件,进而执行PHP代码的Getshell
1)Web一般都会有文件上传等功能,只要在图片中插入我们的PHP代码,然后包含该图片即可,如图所示:
2)可以利用中间件日志文件来助攻。一般中间件默认都会开启日志记录,当请求http://127.0.0.1:8080/1.php?file=<?php phpinfo();?>这个地址时,这个请求就会被中间件保存在自己的日志中,如图所示:
可以通过文件包含来包含日志文件,从而解析日志中存在的“恶意代码”,如图所示
3)通过SSH日志文件包含:尝试通过命令ssh'<?php phpinfo();>'@HOST
去连接到目标机器,我们的代码就会被当成用户名存放在/var/logauth.log
中,如图所示
然后文件包含即可,如图所示
🔎4.PHP远程文件包含漏洞
上一节介绍了PHP本地文件包含既然有本地,那么也就有远程文件包含了。
远程文件包含先根据输入的URL访问到远程资源,然后再把内容返回。进行包含时,远程文件包含需要PHP配置项中allow_url_include=On,否则无法利用,大家可以修改/etc/php/7.0/apache2/php.ini(以Kali Linux为例)中的配置项,如图所示:
远程文件包含可以包含远程服务器的文件。之前我们的利用都需要在本地存在文件,然后包含,如果开启allow url include后,可以直接包含远程服务器的文件执行代码。
远程文件包含一般有HTTPFTP、SMB、Webdav等各种协议,限于篇幅,这里简单介绍一下HTTP和FTP的文件包含。
1)HTTP远程文件包含:在远程HTTP服务器上,写一个文件,内容是PHP代码;在靶机上输入http://IP/1,靶机就会通过HTTP协议去包含指定的文件,如图所示:
2)FTP远程文件包含:与HTTP远程文件包含同理,但不同的是这里使用的是FTP协议进行文件包含。这里我们用Python启动一个服务器:
然后在tmp目录下编写PHP代码的文件,用FTP远程文件包含此文件,如图所示:
请记住,上面提到的这些内容仅仅是文件包含漏洞的开始,在下一小节中会进一步介绍。
🔎5.PHP中常见的伪协议
文件包含中可以使用非常多的协议,例如,常见的HTTP、FTP,或者PHP特有的php://filter等。接下来让我们探索这些协议在文件包含漏洞中的利用方法。
之前介绍了文件包含无限制及开头受到限制两种情况,还提到了扩展名限制这种情况,在这种情况下有什么利用方式,先来看看下面这段代码
在做题时常常会遇到这种情况,看到file=index这种类似的URL时就应该想到这里可能存在文件包含漏洞,且猜测后端源码为index拼接.php。此时我们就可以用一个特殊的协议来读取源码,既php://filter/。
php://filter/是元封装器,用于在数据流打开时筛选过滤应用,通俗来讲就是把读取到文件的内容进行一些处理。
之前介绍过:文件包含会先读取文件内容然后包含进来解析。那么如果遇到PHP文件呢?难道只能包含PHP文件执行吗?有什么办法可以获取到它的源码,接下来做个小实验。
这里我们写两个文件:一个文件为index.php,用于文件包含;一个文件为api.php,里面是php代码。当输入以下payload后,可成功将api.php的内容Base64编码后输出,如图所示。
- php://filter表示用PHP过滤器
- /read后面跟上你要使用的过滤器,如convert.Base64-encode就是调用了Base64过滤器对文件内容进行编码。
- /resource后面是输入的文件。
包含时,它会先读取文件内容,然后经过过滤器对其进行Base64编码,最后包含进来。由于Base64编码后不是PHP代码,而是一些普通字符,所以会直接输出。之后我们可以调用PHP的Base64或者在线工具解密一下获取到的源码
除了Base64过滤器,还有其他的过滤器,例如:
如果Base64被过滤了,只需要用其他协议打乱文件中的PHP标签。因为PHP依据<?php?>标签决定是否解析,假如我们使用convert.iconv.utf-8.utf-7把PHP标签打乱,使其无法正常解析,其中的代码就会被当作字符串直接输出。
上面就是通过伪协议读取PHP文件的方法了。再来思考这样一种情况,假设扩展名限制了只能包含php结尾的文件,除了读取源码还能RCE吗?当然是可以的,只是有一些条件,如开头不能受限制,并且你能上传一些特殊文件。以下的这些协议都是对压缩包进行解析,能获取到压缩包内的文件,并且对压缩包的文件名无要求,只要文件结构是压缩包即可。各个协议利用如下。
1)phar:///tmp/zip.jpg/1.php(获取压缩包中的1.php文件然后包含)。
-
phar://表示协议格式。
-
/tmp/zip.jpg表示要解析的压缩包,与扩展名无关。
-
/1.php表示压缩包中的文件。
2)zip:///tmp/zip.jpg#1.php,与phar相同,但是获取压缩包下文件的分隔符为#。
以上的利用方式常用于限制扩展名,并且能上传ZIP文件的情况下。例如,有一个文件上传只能是JPG扩展名,并且存在一个文件包含点,只能包含abc扩展名的文件,由于文件上传护展名限制,且无法绕过,那么我们就可以上传一个名为1.jpg的压缩包,压缩包中放一个1.abc内容的PHP代码,通过phar或者ZIP协议去包含压缩包内的1.abc即FRCE(Remote Command/CodeExecude)
如果不能上传文件,又该如何RCE?需要在php.ini中设置url_allow_include=On,开启远程文件包含这个配置项,如图所示。
当我们开启上面的配置项后,就可以使用php://input这个地址,在PHP中包含我们POST请求中的内容。php://input是PHP的输入流,可以获取到POST请求中的所有数据。可以将其理解为另一种获取POST值的方式,效果如图所示。
通过POST输入的所有值都会传入include函数,因此如果我们传入恶意的PHP代码,也会传入include函数中,从而造成远程代码执行(RCE),如图所示。
除了使用php://input这种输入流外,我们还可以使用data://来构造需要的数据进行include。data协议是一种传入数据的协议,类似于input://。例如,通过data:/text/plain,hello world,我们可以使用data协议后面传入明文或者Base64编码的数据来传入字符串。这样可以实现图所示的效果
同样,把hello world替换成<?php phpinfo();?>,data协议解析成字符串后,传入include,同样能RCE。如果有关键字过滤,如过滤php、system等字符串,它们明文会被拦截,可以使用Base64编码来绕过:data://textplain:Base64,PD9waHAgcGhwaW5mbygpOyA/Pg==。
🔎6.案例解析[BJDCTF2020]ZJCTF
让我们来看看“[BJDCTF2020ZJCTF,不过如此”这道题。打开这个题,直接给出了源码,如图所示
通过审计,我们传入的text需要等于I have a dream,并且file不能包含flag字符,这一步是为了防止包含/flag直接拿到flag。并且下面有个提示next.php,那么需要用伪协议去拿到next.php的源码。
text有两种方法,一种是data伪协议一种是php://input,这里直接使用php://input传入I have a dream。而对于要读取的目标文件file参数,则可以传入php://filter/read=convert.Base64-encode得到的截图如图resource=next.php,如图所示:
将Base64编码后的源码解码,得到next.php源码。继续往下解题就要涉及其他的知识点了,暂且不提,主要是第一步文件包含需要掌握。
🚀感谢:给读者的一封信
亲爱的读者,
我在这篇文章中投入了大量的心血和时间,希望为您提供有价值的内容。这篇文章包含了深入的研究和个人经验,我相信这些信息对您非常有帮助。
如果您觉得这篇文章对您有所帮助,我诚恳地请求您考虑赞赏1元钱的支持。这个金额不会对您的财务状况造成负担,但它会对我继续创作高质量的内容产生积极的影响。
我之所以写这篇文章,是因为我热爱分享有用的知识和见解。您的支持将帮助我继续这个使命,也鼓励我花更多的时间和精力创作更多有价值的内容。
如果您愿意支持我的创作,请扫描下面二维码,您的支持将不胜感激。同时,如果您有任何反馈或建议,也欢迎与我分享。
再次感谢您的阅读和支持!
最诚挚的问候, “愚公搬代码”
- 点赞
- 收藏
- 关注作者
评论(0)