Broken Authentication

badmonkey 2021年04月09日 90次浏览

Broken Authentication

webgoat 第二部分

验证绕过

2fa password reset 两因子绕过,只需要将body中的问题字段改个名就行了。

image-20210409104513191

JWT token

json web token简称jwt,主要分为三部分header,payload,signature

image-20210409104750598

其中header部分指定签名算法,payload指定具体的参数,如token过期时间,用户类型等等

签名需要使用一个私钥,格式如下

image-20210409105006358

常用网站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";
    }
}

测试效果如下

image-20210409141114096

可以看到,但我们删除签名部分时,可以直接绕过验证,返回Ok,签名存在但是错误时返回No.

修复的方法也比较简单,使用parseClaimsJws方法而不是parse方法即可。

image-20210409142929749

jwt cracking

和上一题一样可以使用去除签名的方法进行绕过,但是这次可以使用hashcat 来爆破密码

image-20210409143311536

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

image-20210409144735256

根据日志获得Tom的过期access token

image-20210409144825436

更改header的Authentication 字段为过期的access token,使用jerry的refresh token获得新的tom token

image-20210409145145348

最后使用新token购物

image-20210409145348141

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如图所示:

image-20210409150914982

参考链接

https://pvxs.medium.com/webgoat-jwt-tokens-8-6ea5f5132499