brksam/web-security-academy-journey
GitHub: brksam/web-security-academy-journey
PortSwigger Web Security Academy 服务端漏洞实验室的完整实践笔记,记录了多种常见 Web 漏洞的利用过程、根因分析与修复方案。
Stars: 0 | Forks: 0
# 🛡️ Web Security Academy — 我的学习之旅
我是一名有志向的安全工程师,专注于**Offensive Security (Red Team)**和
**Application Security (AppSec/DevSecOps)**。这份文档是我的公开实践记录,记录了刻意的、
动手的实践过程——不仅是完成实验室,更是理解每个漏洞的根本原因以及开发人员应如何修复它。
**✅ 已完成整个“Server-side vulnerabilities”学习路径(全部 52 个实验室)。**
## 🧰 工具与环境
| 工具 | 用途 |
|------|-----|
| **Burp Suite Community** | 拦截代理、Repeater、Intruder |
| **Burp Proxy** | 捕获和检查 HTTP 流量 |
| **Burp Repeater** | 编辑和重放单个请求 |
| **Burp Intruder** | 自动化攻击(字典、数字范围) |
| **浏览器 DevTools (F12)** | 检查 cookies、HTML 源码、网络、JS |
## 📊 进度概览
| 模块 | 状态 |
|--------|--------|
| Path traversal | ✅ 已完成 |
| Access control | ✅ 已完成 |
| Authentication | ✅ 已完成 |
| SSRF | ✅ 已完成 |
| File upload | ✅ 已完成 |
| OS command injection | ✅ 已完成 |
| SQL injection | ✅ 已完成 |
## 📖 Write-ups
### 1. Path Traversal — *文件路径遍历,简单案例*
**难度:** Apprentice
**漏洞:** 产品图片加载器中的目录/路径遍历。
**利用方式:** 产品图片通过 `GET /image?filename=1.jpg` 加载。我更改了该参数以跳出 images 目录:
`../../../etc/passwd`,服务器随后返回了 `/etc/passwd` 的内容。
**有趣细节:** 在浏览器中,该文件显示为损坏的图片图标,因为响应的 `Content-Type` 是 `image/jpeg`。Burp 显示了原始文本(包括 `carlos`、`peter` 在内的系统用户)。
**根本原因:** 用户输入被直接附加到基础路径后读取,且没有任何过滤。
**修复方案:** 拒绝 `../` 及其编码变体;通过白名单进行验证;使用能够将路径限制在预期目录内的安全文件 API。
### 2. Access Control — *未受保护的管理员功能*
**难度:** Apprentice
**利用方式:** `/admin` 链接对普通用户是隐藏的,但该 endpoint 未受保护。`/robots.txt` 泄露了该路径;直接浏览 `/admin` 即可获得完全访问权限。
**根本原因:** 安全性依赖于隐蔽性——隐藏链接并不是访问控制。
**修复方案:** 在每个敏感的 endpoint 上强制执行服务端授权。
### 3. Access Control — *URL 不可预测的未受保护的管理员功能*
**难度:** Apprentice
**利用方式:** 管理 URL 经过了混淆且未作链接,但发送给每个用户的客户端 JavaScript 中,在一个 `if (isAdmin)` 代码块内包含了它。我查看了页面源码,找到了该 URL,并访问了管理面板。
**根本原因:** 这个“秘密” URL 以明文 JavaScript 的形式发送给了每个客户端。
**修复方案:** 在服务端强制执行访问控制;永远不要依赖隐藏的 URL 或客户端的角色检查。
### 4. Access Control — *由 request parameter 控制的用户角色*
**难度:** Apprentice
**利用方式:** 登录请求中包含一个 cookie `Admin=false`。我将其更改为 `Admin=true` 并获得了管理员访问权限。
**根本原因:** 授权基于客户端可控的 cookie。
**修复方案:** 永远不要从客户端可控的数据中推导授权;使用已验证的 session 在服务端强制执行角色控制。
### 5. Access Control — *使用不可预测 (GUID) user ID 的 IDOR*
**难度:** Apprentice
**利用方式:** 账号通过 GUID 引用。我浏览了博客文章,直到找到一篇由 *carlos* 撰写的帖子,该帖子在一个 `href` 中泄露了他的 GUID。我登录了自己的账号,并将我的用户 ID 替换为 carlos 的 GUID,从而访问了他的账号。
**根本原因:** 使用客户端提供的标识符来获取数据且没有进行所有权检查;当不可猜测的 GUID 在其他地方泄露时,它并不能作为访问控制手段。
**修复方案:** 在服务端验证该资源是否属于已认证的用户。
### 6. Access Control — *带有密码泄露的 User ID*
**难度:** Apprentice
**利用方式:** 账号页面在一个掩码(`type="password"`)输入框中预填了密码——但真实的值就在 HTML 源码中。利用 IDOR(`id=administrator`),我加载了管理员的账号页面,从源码中读取了密码,并以 administrator 身份登录。
**根本原因:** 应用程序将真实的密码返回给了客户端;掩码只是在视觉上隐藏了它。
**修复方案:** 永远不要将密码发送回客户端;强制执行所有权检查。
### 7. Authentication — *通过不同响应进行用户名枚举*
**难度:** Apprentice
**利用方式(两个阶段,Burp Intruder — Sniper):**
1. 标记 `username` 字段,加载了用户名字典——有效用户返回了**不同的响应长度**("Incorrect password" 与 "Invalid username")。
2. 固定有效的用户名,对 `password` 字段进行暴力破解——正确的密码返回了 **HTTP 302**(登录成功)。
**根本原因:** 对有效和无效用户名返回了不同的响应;没有速率限制。
**修复方案:** 使用通用的错误消息;实施速率限制、账户锁定、CAPTCHA;强大的密码策略 + MFA。
### 8. Authentication — *2FA 简单绕过*
**难度:** Apprentice
**利用方式:** 输入密码后,在 2FA 步骤之前,该 session 已经处于“已登录”状态。我没有提交验证码,而是直接导航到 `/my-account`,页面成功加载——从而绕过了 2FA。
**根本原因:** 部分认证的 session 被授予了对受保护页面的访问权限。
**修复方案:** 每个受保护的页面都必须验证所有的认证步骤均已完成。
### 9. SSRF — *针对本地服务器的基础 SSRF*
**难度:** Apprentice
**利用方式:** “Check stock”功能发送了一个供服务器获取的 `stockApi` URL。在 Repeater 中,我将其设置为 `http://localhost/admin`(服务器返回了内部管理面板),通过阅读原始 HTML 找到了删除链接,然后请求了 `http://localhost/admin/delete?username=carlos`——服务器执行了删除操作。
**根本原因:** 服务器获取了用户控制的 URL;内部管理面板信任了 `localhost`。
**修复方案:** 不要在服务端获取用户提供的 URL;使用白名单;对内部接口进行身份验证;阻止内部 IP 范围(`localhost`、`127.0.0.1`、`169.254.169.254`、私有网段)。
### 10. SSRF — *针对其他 back-end 系统的基础 SSRF*
**难度:** Apprentice
**利用方式:** 管理面板位于 `192.168.0.0/24:8080` 中一个未知的内部主机上。使用 **Burp Intruder** 和数字 payload(0–255),我通过服务器扫描了该网段;存活的主机返回了 HTTP 200。然后我发送了 `http://192.168.0.X:8080/admin/delete?username=carlos` 来删除该用户。
**注意:** 这是第一个真正让我感到棘手的实验室(Intruder 的数字 `Step` 配置;实验室因不活动而超时,我不得不重做整个利用链)。但我坚持了下来并完成了它。
**根本原因:** 内部系统信任了网络位置而不是进行身份验证。
**修复方案:** 对出站请求使用白名单;阻止内部 IP 范围;对内部服务进行身份验证,独立于其网络位置。
### 11. File Upload — *通过 web shell 上传实现 Remote Code Execution*
**难度:** Apprentice
**利用方式:** 头像上传没有进行任何验证。我上传了一个 PHP web shell:
```
```
然后请求 `/files/avatars/webshell.php` 来触发执行——服务器运行了我的代码并返回了 carlos 的 secret。这是我实现的第一个 **RCE**。
**根本原因:** 任意文件上传,且上传目录中启用了服务端脚本执行。
**修复方案:** 验证文件内容(magic bytes),而不仅仅是文件名;扩展名白名单;重命名上传的文件;将其存储在 webroot 之外;从不可执行的目录提供服务。
### 12. File Upload — *通过绕过 Content-Type 限制上传 web shell*
**难度:** Apprentice
**利用方式:** 应用程序验证了 `Content-Type` 标头。我首先上传了一张真实的图片,以了解可接受的 Content-Type(作为基准——避免猜测),然后上传了 PHP web shell,并在 Repeater 中将文件部分的 `Content-Type` 替换为可接受的 `image/jpeg`。上传成功;请求该 shell 触发了 RCE。
**根本原因:** 验证信任了客户端提供的 `Content-Type` 标头,而没有检查实际的文件内容。
**修复方案:** 验证真实的文件内容(magic bytes),而不是客户端的 Content-Type;使用扩展名白名单;存储在 webroot 之外不可执行的位置。
### 13. OS Command Injection — *简单案例*
**难度:** Apprentice
**利用方式:** 一个库存检查功能将 `storeId`/`productId` 传递给了 shell 命令。我使用管道分隔符注入到该参数中:`1|whoami`。响应返回了 OS 用户(`peter-...`),确认了命令已执行。
**关键学习点:** `&` 分隔符失败了,因为在 `x-www-form-urlencoded` body 中,`&` 是*参数*分隔符(在到达 shell 之前就被消耗掉了)。`|` 能够在表单解析中幸存并充当 shell 分隔符,因此它以“原始”形式生效。一个 shell 元字符必须既能在请求上下文中幸存,又能被 shell 解释执行。
**根本原因:** 用户输入被直接拼接到 shell 命令中且没有进行过滤。
**修复方案:** 避免使用用户输入调用 OS 命令;如果不可避免,请使用参数化的 API;严格的输入白名单;永远不要使用用户数据构建 shell 字符串。
### 14. SQL Injection — *检索隐藏数据 (WHERE 子句)*
**难度:** Apprentice
**利用方式:** 分类过滤器构建了类似 `... WHERE category = 'Gifts' AND released = 1` 的查询。我在 `category` 参数中注入了:`Gifts' OR 1=1--`。`--` 注释掉了 `AND released = 1`,而 `OR 1=1` 使条件永远为真,因此返回了所有产品(包括未发布/隐藏的产品)。
**根本原因:** 用户输入被直接拼接到 SQL 查询中。
**修复方案:** 使用参数化查询 / 预处理语句;永远不要将用户输入拼接到 SQL 中。
### 15. SQL Injection — *登录绕过*
**难度:** Apprentice
**利用方式:** 登录执行了 `... WHERE username = '' AND password = ''`。提交用户名 `administrator'--`(加上任意密码)将查询变成了 `... WHERE username = 'administrator'`——`--` 注释掉了密码检查,从而在无需密码的情况下以 administrator 身份登录。
**故障排除提示:** 在 Repeater 中重放请求失败并出现“Invalid CSRF token”错误,因为 CSRF token 已过期/绑定到了特定 session。在浏览器中执行此操作(每次加载页面都会发布一个新的 token)则成功了——这很好地提醒了 CSRF token 是单次使用且绑定 session 的,也说明我的 SQLi payload 本身是正确的。
**根本原因:** 用户输入被拼接到身份验证查询中。
**修复方案:** 参数化查询;在服务端对密码进行 hash + 验证;永远不要使用原始输入构建身份验证查询。
## 🧠 核心概念与词汇表
| 术语 | 含义 |
|------|---------|
| **Path traversal** | 通过 `../` 读取预期目录之外的文件 |
| **Broken Access Control** | 对用户可执行操作的检查缺失或有缺陷 (OWASP #1) |
| **IDOR** | 通过更改标识符访问其他用户的对象 |
| **Privilege escalation** | *垂直:* 获取管理员权限。*水平:* 访问同级别用户的数据 |
| **Security by obscurity** | 依赖隐藏某些东西而不是保护它——并不是真正的防御手段 |
| **Username enumeration** | 通过应用程序行为的差异来检测有效的用户名 |
| **CSRF token** | 一个单次使用且绑定 session 的值,用于防范伪造的请求 |
| **SSRF** | 使服务器向非预期的(通常是内部的)位置发送请求 |
| **RCE** | Remote Code Execution——在服务器上任意命令 |
| **Web shell** | 一个上传的恶意脚本,用于执行攻击者的命令 |
| **OS command injection** | 将 OS 命令注入到将输入传递给 shell 的应用程序中 |
| **SQL injection** | 注入 SQL 以更改应用程序针对其数据库运行的查询 |
### 反复出现的教训
## 🔭 下一步计划
- PortSwigger **Practitioner** 级别的学习路径(更难、更真实的场景)
- 真实的全机器实践(HackTheBox / VulnHub)
- 继续记录每一个实验室,重点关注根本原因和修复方案
*这段旅程仍在继续。目标不是为了收集“已解决”的徽章——而是为了培养出一种直觉,能够敏锐地发现系统是如何崩溃的,同时同样重要的是,懂得如何让它们变得坚不可摧。*
标签:CISA项目, Web安全, 多线程, 数据展示, 渗透测试笔记, 漏洞分析, 红队, 蓝队分析, 路径探测, 逆向防御