1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
|
Prendiamo ad esempio un'applicazione che si interfaccia ad un backend usando JSON Web Tokens\cite{JWT:1} per la validazione dell'autorizzazione.
Se prendiamo il token qui sotto
\begin{lstlisting}
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJ1c2VyX2lkIjoxfQ.
jYyRJbb0WImFoUUdcslQQfwnXTHJzne-6tsPd8Hrw0I
\end{lstlisting}
sappiamo che la prima parte si riferisce all'header:
\begin{lstlisting}
$ echo "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" | base64 -d
{"alg":"HS256","typ":"JWT"}
\end{lstlisting}
che la seconda al payload
\begin{lstlisting}
$ echo "eyJ1c2VyX2lkIjoxfQ" | base64 -d
{"user_id":1}
\end{lstlisting}
e la terza all'HS256 \cite{HMACSHA:1} della stringa dei due più un secret. Sappiamo che è HS256 dall'header.
Questo token quindi è passato come header HTTP come chiave d'autorizzazione del tipo \emph{Bearer} \cite{BEARER:1}.
\begin{lstlisting}
POST /v1/users/2/ HTTP/2
Host: example.com
Referer: https://example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJ1c2VyX2lkIjoxfQ.
jYyRJbb0WImFoUUdcslQQfwnXTHJzne-6tsPd8Hrw0I
[...]
\end{lstlisting}
Un esempio reale fatto da una richiesta di un'istanza Mastodon è la seguente.
\begin{lstlisting}
GET /api/v1/timelines/home?max_id=109391647440107910 HTTP/1.1
Host: hachyderm.io
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:101.0) Gecko/20100101 Firefox/101.0
Accept: application/json, text/plain, */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
X-CSRF-Token: IvSSEJLEUwEqR16Bz1CsflGvdGPSi6vVOJJ4NJevQkPzLXGPx0p3lULJDIVPegqxTnLV8pS5OIAqm2Q-_Ywd5dw
DNT: 1
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
Sec-GPC: 1
Authorization: Bearer a026Y6lPcrhXYBPmx7jvSwsUYtC_MrR-1iKlnPT8c0z
Referer: https://hachyderm.io/
Connection: keep-alive
Cookie: _session_id=eyJfcmFpbHMiOnsibWVzc2FnZSI1IklxVTFPK0kwWqmSaE5XTTFOV011TmpBNE9ESmpOR1ZqT...
\end{lstlisting}
Ipotizziamo che l'endpoint \underline{https://example.com/v1/users/2/} di questa API che vogliamo attaccare guardi l'autenticazione da un cookie che gli si passa e poi guardi l'autorizzazione per accedere all'utente con quell'ID solo se corrisponde all'utente dell'autorizzazione.
L'autenticazione procederebbe con successo ma arrivando all'autorizzazione e decodificato il JWT avremmo un responso del tipo:
\begin{lstlisting}
HTTP/2 401 Unauthorized
\end{lstlisting}
Quindi potremmo ritoccare il token perché presupponiamo che il flow di autorizzazione sia proprio quello descritto nel paragrafo sopra.
Se scoprissimo qual è il secret usato per fare l'hashing potremmo creare un nuovo payload:
\begin{lstlisting}
$ echo '{"user_id":2}' | base64
eyJ1c2VyX2lkIjoyfQo=
\end{lstlisting}
Il padding ("=") viene omesso in realtà come da standard.
Diamo per buono che il secret sia la stringa \textbf{secret}. Tramite un software di cui il sito JWT.io \cite{JWT:2} stesso predispone si può creare e verificare la firma del token. Alla fine avremo proprio
\begin{lstlisting}
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJ1c2VyX2lkIjoyfQ.
9YCOE7tXJFvXEkLKezdd42NArXH6JXLtHbQu-KrwQSA
\end{lstlisting}
che, passandolo alla richiesta, avremo finalmente il responso con i dati dell'utente con ID 2:
\begin{lstlisting}
HTTP/2 200 OK
[...]
\end{lstlisting}
Questo potrebbe essere un bell'attacco, peccato però che tutti (spero) i backend che adoperano l'uso di JWT (spesso anche per autenticazione) usano un payload del tipo:
\begin{lstlisting}
$ echo "eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNjY0NjE1NDc
xLCJqdGkiOiIyMGM3Nzk2YTljM2Y0Yjk4YmM3ODdkNDRmNzRiNGE0YyIsInV
zZXJfaWQiOjF9" | base64 -d
{"token_type":"access","exp":1664615471,"jti":"20c7796a9c3f4b98bc787d44f74b4a4c","user_id":1}
\end{lstlisting}
e da questo, anche se riuscissimo a scoprirne il \textbf{secret} per la verifica, dovremmo far conto sia con la timestamp di scadenza che con l'ID (JTI). Il payload di questo listato è stato generato da un'app realizzata usando dj-rest-auth \cite{DJ-REST-AUTH:1} il quale alla base usa la libreria PyJWT \cite{PYJWT:1}.
Come ampiamente discusso da Portswigger nel loro articolo \cite{JWT-ATTACK:1} sono numerosi i possibili attacchi a JWT: molti dei quali, in realtà, vengono eseguiti modificando l'header.
Un esempio è l'attacco a JWK \cite{JWK:1}, niente che una buona libreria aggiornata non possa prevenire.
|