目录
漏洞简介
漏洞实现
文件包含之伪协议
漏洞实例
漏洞防御
什么是文件包含?
在之前接触的python中,我们会通过import来导入一些公共资源
在PHP中,也有类似的函数,为了更好地使用代码的重用性,引入了文件包含函数,可以通过文件包含函数将文件包含进来,直接使用包含文件的代码
文件包含分文本地文件包含(Loacl File Inclusion,LFI)和远程文件包含(Remote File Inclusion,RFI)
远程文件包含需要在php.ini中设置allow_url_include = on和allow_url_fopen= on
文件包含漏洞
什么是文件包含漏洞?
首先得说明,文件包含本身并不是问题,在程序中写死的文件包含不会形成漏洞,几乎所有漏洞的成因都是因为相关参数可控
那么文件包含漏洞是怎么产生的呢?
在包含文件时候,为了灵活包含文件,将被包含文件设置为变量,通过动态变量来引入需要包含的文件时,攻击者利用包含的特性,加上应用本身对文件(包含)控制不严格,最终造成攻击者进行任意文件包含(任意文件包含才是漏洞),这样就导致了文件包含漏洞
通常文件包含漏洞出现在
PHP
语言中
PHP文件包含的函数
- include()函数:使用include引用外部文件时,只有代码执行到include代码段时,调用的外部文件才会被引用并读取,当引用的文件发生错误时,系统只会给出个警告错误,而整个php文件会继续执行
- require()函数:在php文件被执行之前,php解析器会用被引用的文件的全部内容替换require语句,然后与require语句之外的其他语句组成个新的php文件,最好后按新的php文件执行程序代码
- include_once()函数:使用include_once会在导入文件前先检测该文件是否在该页面的其他部分被引用过,如果有,则不会重复引用该文件,程序只能引用一次
- require_once()函数:功能与 require()相同,区别同样在于当重复调用同一文件时,程序只调用一次
本地文件包含漏洞
先说明一下,文件包含,被包含的文件,不一定需要是动态脚本语言,包括txt文件在内都会被直接当成动态语言来执行,下面给出一段代码
修改代码为用户可控,如下
基于apache日志的文件包含
首先开启错误日志,在httpd.conf文件中,相关参数如下
ErrorLog "logs/error.log"
CustomLog "logs/access.log" common
第一步,输入如下url,让apache记录错误
尝试包含此文件,不过需要知道这个文件的绝对路径,发现并没有成功,为什么?
这里面的<>被编码成%3C和%3E,在文件包含中无法对该文件解码,所以只能输出源文件
怎么解决,抓包修改即可
还可以尝试使用可以直接写进
User Agent
里面也可以被写进日志里并且此处需要日志所在文件夹的权限,才可以成功
其他形式
开发者限制了包含文件的后缀,代码如下
经过查询资料,发现可以使用%00截断,但00截断有以下三个条件
- PHP版本 < 5.3 (不包括5.3)
- PHP配置magic_quotes_gpc =
off
- PHP对所接收的参数,如以上代码的
$_GET['file']
未使用addslashes
函数在5.4版本以上该参数被移除,并未找到该参数
此处我先用大于php5.3.29的做实验,配置参数
下面是5.2.17版本,设置如下,然后重启服务
远程文件包含
什么是远程文件包含?
当包含的文件在远程服务器上时,就形成了远程文件包含
构成条件
- 需要
php.ini
中allow_url_include = on
以及allow_url_fopen=on
- 所包含远程服务器的文件后缀不能与目标服务器语言相同。(比如目标服务器是php脚本语言解析的,那么包含的远程服务器文件后缀不能是
php
)首先看下远程服务器的phpinfo信息
本机信息如下
修改配置文件如下,重启服务
直接尝试包含php文件,发现并不是本机的phpinfo信息,如下图
这是因为,我们包含的并不是简单的,而是远程主机执行完这段代码的源代码,如下图
解决方案就是,只要不使用php其他都好,我们可以修改成txt文件,成功执行如下图
文件中可以包含不同的伪协议,我将在下面演示其中的一些
- data:text/plain or data: text/plain; base64
- php://input
- php://filter
- file://
- zip://
data:text/plain
输出直接显示在相应的URL中,显示参数:data:text/plain
然后你需要执行如下所示的php代码
data:text/plain; BASE64
有另一种方法来使用data:text/plain; base64,不过此时你需要使用base64编码来执行PHP代码,base64php代码如下所示
此处我编码的是会报错
php://input
php://input访问请求的原始数据的只读流(read-only stream),会将post请求中的数据作为php代码执行
php://filter
php://filter可以读取php文件的代码base64编码的输出并将其返回给你
file://
file://用于访问本地文件系统,不受allow_url_fopen orallow_url_include的影响,你可以使用file:// absolute / path / to / file来获取
zip://
zip://可以访问zip文件中的文件,但它需要一个绝对路径。你可以使用zip://[archive absolute path] # [compressed file name]在本地创建一个文件并将其压缩到一个zip压缩文件中
此处参考文章
参考文章:https://www.jianshu.com/p/065deed3142e
可尝试审计phpMyAdmin4.8.1 :https://pan.baidu.com/s/1w-G8mCf0jOw6ptekku7HRQ 提取码:9k6a
代码审计工具:https://pan.baidu.com/s/1DuRkZMy4Ya9RkkFmWNpQqQ 提取码:0zj9
打开工具,新建项目,加载源码,全局搜索include,如下图
发现前几个include都是包含的固定文件,只有这个是接受传参的,在index.php文件中,直接跟进源码
这里有四个条件
- 通过is_string()函数,判断参数是不是字符串(可满足)
- 通过preg_match()函数做正则匹配,判断是否以index开头(可满足)
- 通过in_array()函数判断传参字符串是否在$target_blacklist数组中
- 检查是否符合自建类的checkPageValidity()函数
继续根基第三个条件的数组只要我们输入不是一下这两个文件名即可
继续跟进checkPageValidity()函数,找到了此函数的定义,发现有三种情况会返回true,而且都是要求$page在$whitelist数组中,如下图
接下来看第一个条件,判断$whitelist是否为空,如果为空,重新赋值
继续跟进$goto_whitelis数组,发现这个数组相当于一个白名单,如下图
接下来来到第一个返回true的位置,判断很简单就看传参的字符串是否在数组里面,在这里我们完全可以输入一个白名单的文件名,但是没有什么意义,包含一个已被写好的文件,无法达成任何目标
接着分析,第二个返回true的位置,首先看看上面的代码做了什么处理
mb_substr()函数:截取字符串。语法:mb_substr($a,0,3),意思是截取字符串a的前三位
mb_strpos()函数:判断位置的函数,首先把传参$page连接一个问号,在查找问号的位置并返回,只会返回第一个问号的位置,如果我们传参里面有问号的话。。。。。
在这里如果我们能传入一个问号,再通过相对路径那岂不是可以包含任意文件了?可惜,get传参传不了问号会报错,因为在url中问号代表接下来是传参的意思,而自身无法作为传参值,有人可能会想可不可以编码,首先有编码需要有解码,没解码的话你的传参只是字符串将不再具有任何意义,而我们知道get型传参是会进行一侧解码的,所以只通过%3f是无法绕过的
继续看下个判断
接着看最后一个判断,如果还是不可以,那可就GG了,注意看第三个位置除了标红的地方,其他代码和上个位置一样
urldecode()函数:进行一次url解码
突然好像感觉抓住了什么,url中的%3f会被直接识别成问号,那么在进行一次编码呢?反正这里会在进行一次解码操作。
很明显二次编码后为%25%3f,通过url解码一次为%3f,代码执行到第三个位置这里,会再进行一次解码,那么问号不久出来了吗?
如下图,白名单中有个sql.php,假设所在文件的我们访问文件的上一级存在一个1.txt
我们尝试构造payload:target=sql.payload%253f/../1.txt
payload传到后台之后是:sql.php%3f../1.txt
传参是字符串,满足了第一个被包含的第一个条件
传参不是index开头,满足了第二个条件
传参不是impot.php和export.php,满足了第三个条件
我们去看一下返回true的第三个条件
首先url解码-->>sql.php?/../1.txt
拼接问号-->>sql.php?/../1.txt?-->由mb_strpos()函数返回位置数字 7
由mb_substr()函数截取前7为字符-->>sql.php
由in_array()函数判断sql.php是否在白名单中
我们是从白名单拿出来的肯定在啊,所以这里就构成了任意文件包含,实验结果如下
如何获取webshell?
但是有个问题这里的1.txt是我上直接写在服务器里面的,这里的phpmyadmin并没有上传功能,
解决方法
1.进行一次查询,查询之后会有日志文件,进入后台后,在变量栏目里开启日志记录
查询语句如下
SELECT ''
日志文件如下,尝试包含,此处只能使用相对位置
第二个思路,我们可以创建一个新表,这里还是带着php文件头吧,如下图
接下来我们去找一下这个表,尝试包含
其他方式
还看到一个sessionid的,不过我没找到文件
参考文章:https://www.jianshu.com/p/0d75017c154f
1、无需情况下设置allow_url_include和allow_url_fopen为关闭
2、对可以包含的文件进行限制,可以使用白名单的方式,或者设置可以包含的目录,如open_basedir
3、建议假定所有输入都是可疑的,尝试对所有输入提交可能可能包含的文件地址,包括服务器本地文件及远程文件,进行严格的检查,参数中不允许出现../之类的目录跳转符。
4、严格检查include类的文件包含函数中的参数是否外界可控