Web安全基础篇——文件包含漏洞

Web安全基础篇——文件包含漏洞
Audience文件包含漏洞
开发人员一般会把重复使用的函数写到单个文件中,需要使用某个函数时直接调用此文件,而无需再次编写,这种文件调用的过程一般被称为文件包含。 在实际WEB应用中,当页眉需要更新时,只更新一个包含文件就可以了,或者当您向网站添加一张新页面时,仅仅需要修改一下菜单文件 (而不是更新所有网页中的链接)。
漏洞危害
当被包含的文件为变量时,在文件包含函数加载的参数没有经过过滤或者严格的定义,可以被用户控制,包含其它恶意文件,导致执行了非预期的代码。
相关危险函数
在JSP、PHP、 ASP等开发语言中都提供了内置的文件包含函数,这些函数可以使开发人员在一个代码文件中直接包含(引入)另外一个代码文件。一般来说在PHP中出现的概率会比较大,而像JSP、ASP等语言概率较小甚至不会产出。
比如在PHP中,提供了四个包含文件的函数:
**include()**:当使用该函数包含文件时,只有代码执行到include()函数时才将文件包含进来, 发生错误时只给出一个警告,继续向下执行。include()函数并不在意被包含的文件是什么类型,只要有php代码,都会被解析出来。
**include once()**:功能和include()相同,区别在于当重复调用同一文件时,程序只调用一次。
**require()**:require()与include()的区别在于require()执行如果发生错误,函数会输出错误信息,并终止脚本的运行。使用require()函数包含文件时,只要程序一执行, 立即调用文件,而include()只有程序执行到该函数时才调用。
**require_once()**:它的功能与require()相同,区别在于当重复调用同一文件时,程序只调用一次。
区别:include(),include_ once()在包含文件时,即使遇到错误,只生成警告(E_WARNING),下面的代码依然会继续执行;而require()和require_ _once()则会报错,生成致命错误(E_COMPILE ERROR) 并停止脚本,直接退出程序。
利用条件
大多数情况下,文件包含函数中包含的代码文件是固定的,因此也不会出现安全问题。但是当文件包含的代码文件被写成了一个变量,且这个变量前端用户可控,这种情况下,如果没有做足够的安全考虑,则可能会引发文件包含漏洞。攻击者会指定一个特殊文件让包含函数去包含,从而造成恶意操作。
利用条件:
- include等函数通过动态执行变量的方式引入需要包含的文件
- 用户能够控制这个变量
分类
本地文件包含
本地文件包含漏洞仅能够对服务器本地的文件进行包含,由于服务器上的文件并不是攻击者所能够控制的,因此该情况下,攻击者更多的会包含一些固定的系统配置文件,从而读取系统敏感信息。很多时候本地文件包含漏洞会结合一些特殊的文件上传漏洞,从而形成更大的威力,可以包含网站下任意文件,配合目录穿越漏洞。
- 使用绝对路径读取本地文件,如果后端加上目录名称等字符串的就行不通。
- 使用相对路径进行读取,通过
./
表示当前位置路径,../
表示上一级路径位置,在linux中同样适用。例如当前页面所在路径为C:\Apache24\htdocs\
,我们需要使用../
退到C盘再进行访问,构造路径如下:../../windows/system.ini
。
一些常见的敏感目录信息路径:
Windows系统:
1 | C:\boot.ini #查看系统版本 |
Linux/Unix系统:
1 | /etc/password #账户信息 |
远程文件包含
如果PHP的配置选项allow_url_include
、allow_url_fopen
状态为ON的话,则include/require函数是可以加载远程文件的,这种漏洞被称为远程文件包含(RFI
)。这两个选项在php.ini配置文件中。
漏洞利用
配合文件上传漏洞
有时候我们找不到文件上传漏洞,无法上传webshell,可以先上传一个图片格式的webshell到服务器,再利用本地文件包含漏洞进行解析。
编辑一个图片马,内容如下:
1 |
|
我们也可以直接在webshell.jpg
中写一句话木马,然后再通过文件包含漏洞去连接webshell.jpg
,但这种方法有时候webshell
功能会出现异常。所以我们选择上面的方式,生成一个.php
格式的一句话木马,再去连接。
包含Apache日志文件
有时候网站存在文件包含漏洞,但是却没有文件上传点。这个时候我们还可以通过利用Apache的日志文件来生成一句话木马。
利用条件
- 对日志文件可读
- 知道日志文件存储目录
一般情况下日志存储目录会被修改,需要读取服务器配置文件(httpd.conf
,nginx.conf
等)或者根据phpinfo()
中的信息来得知日志记录的信息都可以被调整,比如记录报错的等级,或者内容格式。
在用户发起请求时,服务器会将请求写入access.log
,当发生错误时将错误写入error.log
。
当我们正常访问一个网页时,如http://127.0.0.1/phpinfo.php
,access日志会进行记录。
1 | 127.0.0.1 - - [17/Aug/2022:11:22:48 +0800] "GET /phpinfo.php HTTP/1.1" 200 86110 |
如果我们访问一个不存在的资源,也一样会进行记录,例如访问127.0.0.1/<?php phpinfo();?>
。发现也会被记录下来,但是一些符号已经被url编码了。
1 | 127.0.0.1 - - [29/Mar/2024:21:26:02 +0800] "GET /%3C?php%20phpinfo();?> HTTP/1.1" 403 2208 |
可以使用burp进行抓包,将报文修改回去再进行发送即可记录下正确的php代码。通过本地文件包含漏洞访问,即可执行。我们可以在此处写入一句话木马,再使用webshell
管理工具进行连接。
包含SESSION文件
可以先根据尝试包含到SESSION文件,在根据文件内容寻找可控变量,在构造payload插入到文件中,最后包含即可。
条件:
- 找到Session内的可控变量
- Session文件可读写,并且知道存储路径
php的session文件的保存路径可以在phpinfo
的session.save_path
看到。
session常见存储路径:
1 | /var/lib/php/sess_PHPSESSID |
session文件格式:sess_[phpsessid]
,而phpsessid
在发送的请求的cookie字段中可以看到。
PHP伪协议
PHP
内置了很多URL风格的封装协议,可用于类似fopen()
、copy()
、file_exists()
和filesize()
的文件系统函数。也就是说,如果要通过php发送http请求,我们可以直接调用php的相关函数。因为开发人员在开发php这个语言的时候,已经将http协议用PHP语言编写好了,我们直接调用即可。
file://协议
用于访问本地文件系统,在CTF中通常用来读取本地文件,且不受allow_url_fopen
与allow_url_include
的影响。
载荷为
1 | file://[文件的绝对路径和文件名] |
如:
1 | http://192.168.233.133/dvwa/vulnerabilities/fi/?page=file://D:\flag.txt |
php://协议
PHP提供了一些杂项输入/输出(IO)流,允许访问 PHP的输入输出流、标准输入输出和错误描述符,输入/输出流也就是「数据流」,数据流可以是某个文件(xx.php
)或某个url(http://www.baidu.com
)。
经常使用的是php://filter
和php://input
。前者用于读取源码,后者用于执行php代码。
php://filter
可以在访问数据流之前进行「过滤」,并指定过滤方式。读取源代码并进行base64编码输出,不然会直接当做php代码执行就看不到源代码内容了。filter也不会受到allow_url_fopen
和allow_url_include
的影响。
名称 | 描述 |
---|---|
resource=<要过滤的数据流> | 这个参数是必须的。它指定了你要筛选过滤的数据流。 |
read=<读链的筛选列表> | 该参数可选。可以设定一个或多个过滤器名称 |
write=<写链的筛选列表> | 该参数可选。可以设定一个或多个过滤器名称 |
<; 两个链的筛选列表> | 任何没有以 read= 或 write= 作前缀 的筛选器列表会视情况应用于读或写链。 |
read/write参数不是必须的,可以直接使用过滤器,比如 php://filter/convert.base64-encode/resource=hello.php
。将 hello.php
文件的内容使用base64
加密后读取出来。复制后解密就能拿到被包含文件的内容。
常用的过滤器有
string.rot13:
对数据流的内容进行
rot13
编码,等于用str_rot13()
函数编码。rot13
编码读取文件内容后,复制页面输出的编码后的内容,手动用str_rot13()
函数解码。string.toupper:
PHP5.0.0
及以后版本,将数据流转换成大写,类似于strupper()
函数。需要注意的是,
string.tuupper
不是直接将文件内容变成大写,而是先执行文件内容,再将执行后的结果转换成大写。string.tolower:
将数据流转换成小写,类似
strtolower()
函数。和
string.toupper
一样,也是先执行文件的内容,再将执行后的结果转换成小写。convert.base64-encode:
对数据流的内容进行
base64
编码,相当于base64_encode()
函数。前面已经演示过。**convert.iconv.***:
将数据流的内容按照指定字符编码来转换,使用格式有两种:
1
2convert.iconv.<input-encoding>.<output-encoding>
convert.iconv.<input-encoding>/<output-encoding>比如,
convert.iconv.utf-8*.utf-16*
的意思就是把文件的字符编码从utf-8*
转换为utf-16*
。支持的字符编码有:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77UCS-4*
UCS-4BE
UCS-4LE*
UCS-2
UCS-2BE
UCS-2LE
UTF-32*
UTF-32BE*
UTF-32LE*
UTF-16*
UTF-16BE*
UTF-16LE*
UTF-7
UTF7-IMAP
UTF-8*
ASCII*
EUC-JP*
SJIS*
eucJP-win*
SJIS-win*
ISO-2022-JP
ISO-2022-JP-MS
CP932
CP51932
SJIS-mac
SJIS-Mobile#DOCOMO
SJIS-Mobile#KDDI
SJIS-Mobile#SOFTBANK
UTF-8-Mobile#DOCOMO
UTF-8-Mobile#KDDI-A
UTF-8-Mobile#KDDI-B
UTF-8-Mobile#SOFTBANK
ISO-2022-JP-MOBILE#KDDI
JIS
JIS-ms
CP50220
CP50220raw
CP50221
CP50222
ISO-8859-1*
ISO-8859-2*
ISO-8859-3*
ISO-8859-4*
ISO-8859-5*
ISO-8859-6*
ISO-8859-7*
ISO-8859-8*
ISO-8859-9*
ISO-8859-10*
ISO-8859-13*
ISO-8859-14*
ISO-8859-15*
ISO-8859-16*
byte2be
byte2le
byte4be
byte4le
string.rot13
HTML-ENTITIES
7bit
8bit
EUC-CN*
CP936
GB18030
HZ
EUC-TW*
CP950
BIG-5*
EUC-KR*
UHC
ISO-2022-KR
Windows-1251
Windows-1252
CP866
KOI8-R*
KOI8-U*
ArmSCII-8
如果需要多个条件过滤,在过滤器中间加上|
符即可,如:
1 | php://filter/read=convert.base64-encode|string.toupper/resource=hello.php |
php://input
可以访问请求的原始数据的只读流,将post请求中的数据作为PHP代码执行。当传入的参数作为文件名打开时,可以将参数设为php://input,同时post想设置的文件内容,php执行时会将post内容当作文件内容。从而导致任意代码执行。
利用条件:
- allow_url_fopen:off/on
- allow_url_include:on
利用该方法,可以直接写入php文件,输入file=php://input
,然后使用burp抓包,请求体中写入php代码。
1 |
|
这样服务器就会执行请求体的php代码,在该目录下创建一句话木马文件了。
注:当enctype="multipart/form-data"
的时候 php://input
是无效的。
ZIP://协议
可以访问压缩包里面的文件。当它与包含函数结合时,zip://流会被当作php文件执行,从而实现任意代码执行。
- zip://中只能传入绝对路径。
- 要用
#
分割压缩包和压缩包里的内容,并且#
要用url
编码成%23
(即下述POC
中#
要用%23
替换)。 - 只需要是zip的压缩包即可,后缀名可以任意更改。
- 相同的类型还有
zlib://
和bzip2://
。
载荷为:
1 | zip://[压缩包绝对路径]#[压缩包内文件] |
data://协议
类似与php://input
,可以让用户来控制输入流,当它与包含函数结合时,用户输入的data://
流会被当作php文件执行,从而导致任意代码执行。
利用条件:
- allow_url_fopen:on
- allow_url_include:on
载荷:
1 | data://text/plain,<?php phpinfo();?> |
伪协议总结
协议 | PHP版本 | low_url_fopen | allow_urL_include | 用法 |
---|---|---|---|---|
file:// | >=5.2 | off/on | off/on | ?file=file://[绝对路径或相对路径] |
php://filter | >=5.2 | off/on | off/on | ?file=php://filter/[过滤器]/resource=[文件路径] |
php://input | >=5.2 | off/on | on | ?file=php://input,POST: |
zip:// | >=5.2 | off/on | off/on | `?ile=zip://[压缩文件绝对路径]%23[压缩包里的文件] |
compress.bzip2:// | >=5.2 | off/on | off/on | `?file=compress.bzip://./file.bz2 |
compress.zlib:// | >=5.2 | off/on | off/on | `?file=compress .zlib://./file.gz |
data:// | >=5.2 | on | on | ?file=dat://text/plain, 或?file=data://text/plain;base64.PD9waHAgcGhwaW5mbygpPz4= |