Broken Authentication
webgoat 第二部分
验证绕过
2fa password reset 两因子绕过,只需要将body中的问题字段改个名就行了。
JWT token
json web token简称jwt,主要分为三部分header
,payload
,signature
其中header部分指定签名算法,payload指定具体的参数,如token过期时间,用户类型等等
签名需要使用一个私钥,格式如下
常用网站jwt.io
jwt signing
第一题需要伪造jwt,但是如果不知道jwt使用的key那么就无法伪造jwt,通常的思路是爆破weak key,但是此题可以考虑绕过签名,因为java里面使用jsonwebtoken验证签名时如果采用了parse方法,可以被绕过。
本地写一个demo测试一下
@RestController
public class IndexController {
@Autowired
JwtUtil jwtUtil;
@RequestMapping("/getToken")
public String getToken(){
return jwtUtil.generateToken();
}
@RequestMapping("/validToken")
public String validToken(HttpServletRequest request){
String token = request.getParameter("token");
return jwtUtil.validToken(token);
}
}
@Component
public class JwtUtil {
public static final String JWT_PASSWORD = TextCodec.BASE64.encode("victory");
public String generateToken(){
String jwt = Jwts.builder()
.setIssuer("badmonkey") // 签发者
.setSubject("admin") // 用户名
.setAudience("goodmonkey") // 接收者
.setId(UUID.randomUUID().toString())
.signWith(SignatureAlgorithm.HS512,JWT_PASSWORD)
.compact();
return jwt;
}
public String validToken(String token){
try{
Jwt claimsJws = Jwts.parser().setSigningKey(JWT_PASSWORD).parse(token); // 漏洞点
return "Ok";
}catch (JwtException e){
e.printStackTrace();
}
return "No";
}
}
测试效果如下
可以看到,但我们删除签名部分时,可以直接绕过验证,返回Ok,签名存在但是错误时返回No.
修复的方法也比较简单,使用parseClaimsJws方法而不是parse方法即可。
jwt cracking
和上一题一样可以使用去除签名的方法进行绕过,但是这次可以使用hashcat 来爆破密码
refresh a token
token 分为 access token和refresh token,access token字如其名,在访问某项服务的时候使用,而refresh token则是在需要更新access token的时候使用,如果使用A的refresh token可以更新B的access token,那么可以达到越权的效果。本题由于存在refresh token 越权的问题,达到了任意用户checkout的效果。
jerry登陆时,获得access token 和 refresh token
根据日志获得Tom的过期access token
更改header的Authentication 字段为过期的access token,使用jerry的refresh token获得新的tom token
最后使用新token购物
final challenge
挺离谱的,不看源码真做不出来。服务端解析jwt后,将header中的kid 字段拼接成sql语句,然后执行。
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 (var connection = dataSource.getConnection()) {
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;
}
}).parseClaimsJws(token);
利用union select 可以更改sql语句的结果为任意值,不过后续作为key之前进行了一次b64decode,所以需要将key进行一次b64encode
payload如图所示: