WebGoat JWT
- 本文来自自己打 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 由三部分组成:
头部(Header):头部包含加密算法和类型信息
载荷(Payload):载荷包含用户相关数据
签名(Signature)签名用于验证 Token 的真实性
JWT 解码
- 让我们尝试解码 JWT 令牌,为此您可以使用 WebWolf 中的 JWT 功能。给出以下标记:
1 | eyJhbGciOiJIUzI1NiJ9.ew0KICAiYXV0aG9yaXRpZXMiIDogWyAiUk9MRV9BRE1JTiIsICJST0xFX1VTRVIiIF0sDQogICJjbGllbnRfaWQiIDogIm15LWNsaWVudC13aXRoLXNlY3JldCIsDQogICJleHAiIDogMTYwNzA5OTYwOCwNCiAgImp0aSIgOiAiOWJjOTJhNDQtMGIxYS00YzVlLWJlNzAtZGE1MjA3NWI5YTg0IiwNCiAgInNjb3BlIiA6IFsgInJlYWQiLCAid3JpdGUiIF0sDQogICJ1c2VyX25hbWUiIDogInVzZXIiDQp9.9lYaULTuoIDJ86-zKDSntJQyHPpJ2mZAbnWRfel99iI |
- 复制并粘贴以下令牌并解码令牌,您能找到令牌中的用户吗?
- 使用 WebWolf 中的 JWT 解密网站,解码得到如下内容:
1 | # Header |
- 得到用户名为:user。
JWT 验证流程
- 获取 token 的基本顺序如下:
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 用户:
- 随便切换一个用户:
- 这是我们就可以进行投票了:
- 在点击垃圾桶进行投票重置时,提示需要 admin 用户:
- 由于是需要使用 admin 的身份,在点击垃圾桶时使用 BurpSuite 抓个包试试:
1 | POST /WebGoat/JWT/votings |
- 发现一个 access_token 字段,一看就是 JWT 格式,解密看看:
1 | # Header |
- 找到了 admin 字段,修改成 true,重新编码成 JWT 格式发包试试:
1 | # Payload |
- 但这样会出现一个问题,Signature 部分消失了,因为没有 Secret key 无法进行生成,尝试把 alg 字段变为 none 试试:
1 | # Header |
- 发包返回时,发现还是 false,有个小坑,虽然没有 Signature 字段了,但是 “.” 还是要加的:
- 查看结果,票数重置成功:
JWT 暴力破解
- 通过具有 SHA-2 函数的 HMAC,您可以使用密钥来签名和验证令牌。一旦我们找到了这个密钥,我们就可以创建一个新的令牌并对其进行签名。因此,密钥足够强大非常重要,因此暴力或字典攻击是不可行的。一旦你有了令牌,你就可以开始离线暴力或字典攻击。
- 鉴于我们有以下令牌,请尝试找出密钥并提交一个新密钥,并将用户名更改为 WebGoat:
1 | eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJXZWJHb2F0IFRva2VuIEJ1aWxkZXIiLCJhdWQiOiJ3ZWJnb2F0Lm9yZyIsImlhdCI6MTY5MjUyNDMyMywiZXhwIjoxNjkyNTI0MzgzLCJzdWIiOiJ0b21Ad2ViZ29hdC5vcmciLCJ1c2VybmFtZSI6IlRvbSIsIkVtYWlsIjoidG9tQHdlYmdvYXQub3JnIiwiUm9sZSI6WyJNYW5hZ2VyIiwiUHJvamVjdCBBZG1pbmlzdHJhdG9yIl19.QvGz_b_KM-UFNFxXdf0yWkqCerT3fwBxA-FKedqTZEI |
- 先解密看看:
1 | # Header |
- 但不知道 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 | root at kali in ~ |
- 得到 Signature Key,重新构造 JWT 如下(记得修改 exp 时间戳):
1 | # Header |
JWT Token 刷新
- 实施刷新访问令牌的良好策略非常重要。此作业基于 Bugcrowd 上的私人错误赏金计划中发现的漏洞。
- 从去年的违规行为来看,以下日志文件可在此处找到(http://10.10.8.135:8080/WebGoat/images/logs.txt)。您能找到一种方法来订购这些书籍,但让汤姆支付费用吗?
- 按照题目的意思,需要我们买书,但是让 Tom 付钱(主打一个白嫖),先点击一下 CheckOut,发现显示了:
1 | Not a valid JWT token, please try again |
- 说是没有 JWT Token,抓个包看看:
- 发现一个 Authoriaztion 字段,但不知填什么,查看一下日志文件:
1 | 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" "-" |
- 发现一个 JWT,解密看看:
1 | eyJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE1MjYxMzE0MTEsImV4cCI6MTUyNjIxNzgxMSwiYWRtaW4iOiJmYWxzZSIsInVzZXIiOiJUb20ifQ.DCoaq9zQkyDH25EcVWKcdbyVfUL4c9D4jRvsqOqvi9iAd4QuqmKcchfbU8FNzeBNF9tLeFXHZLU4yRkq-bjm7Q |
发现时间戳,拿去百度看看:
exp (expiration time):过期时间
iat (Issued At):签发时间
发现是 18 年的时间,改一改:
1 | { |
- 重新生成 JWT:
1 | eyJhbGciOiJIUzUxMiJ9.ew0KICAiYWRtaW4iIDogImZhbHNlIiwNCiAgImV4cCIgOiAxNjkyNTI5OTk5LA0KICAiaWF0IiA6IDE1MjYxMzE0MTEsDQogICJ1c2VyIiA6ICJUb20iDQp9. |
- 发包试试看:
- 成功修改!
JWT Final Challenge
- 下面您会看到两个帐户,一个是杰瑞,一个是汤姆。杰瑞想要从推特上删除汤姆的账户,但他的令牌只能删除他的账户。你能尝试帮助他并删除 Toms 帐户吗?
- 按照题目的意思需要删除 Tom 的账号,点击 Tom 的 Delete 显示:
1 | Not a valid JWT token, please try again |
- 抓包看看:
- 发现在 URL 处有一个 Token,解码看看:
1 | eyJ0eXAiOiJKV1QiLCJraWQiOiJ3ZWJnb2F0X2tleSIsImFsZyI6IkhTMjU2In0.eyJpc3MiOiJXZWJHb2F0IFRva2VuIEJ1aWxkZXIiLCJpYXQiOjE1MjQyMTA5MDQsImV4cCI6MTYxODkwNTMwNCwiYXVkIjoid2ViZ29hdC5vcmciLCJzdWIiOiJqZXJyeUB3ZWJnb2F0LmNvbSIsInVzZXJuYW1lIjoiSmVycnkiLCJFbWFpbCI6ImplcnJ5QHdlYmdvYXQuY29tIiwiUm9sZSI6WyJDYXQiXX0.CgZ27DzgVW8gzc0n6izOU638uUCi6UhiOJKYzoEZGE8 |
- 根据之前的想法,alg = none、jerry = tom 试试(时间戳记得更新):
1 | # Header |
- 经尝试,不行,最后发现是 kid 字段存在 SQL 注入:
1 | ResultSet rs = connection.createStatement().executeQuery("SELECT key FROM jwt_keys WHERE id = '" + kid + "'"); |
- 此处查询 jwt_keys 表,且 kid 直接拼接并没有进行过滤等操作。
- 由于这个语句比较熟悉,很有可能是 MySQL,试试用 Union Selet 进行拼接(YWxwYWNh = alpaca):
1 | "SELECT key FROM jwt_keys WHERE id = '" + kid + "'" |
注:
- 不能使用注释符,要用字段进行拼接,不然会爆 500 错误;
- 签名需要先进行 base64,后续后端会进行一次 decodebase64;
- 签名在 base64 后不能出现非英文字符,也会爆 500;
- 最终 JWT 如下:
1 | # Header |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Yongz丶!