0% encontró este documento útil (0 votos)
26 vistas13 páginas

Seguridad de JWT en Java

El documento habla sobre los JSON Web Tokens (JWT), que son tokens estándar que permiten transmitir información de forma segura entre partes. Los JWT se usan comúnmente para autenticación en aplicaciones web y API, al permitir que un cliente presente un token que representa su identidad al servidor. El documento luego describe algunas consideraciones de seguridad al usar JWT, incluyendo posibles ataques como alteración del algoritmo de firma o robo de tokens, y formas de prevenirlos como agregar un contexto de usuario único al token.

Cargado por

Juan
Derechos de autor
© All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
26 vistas13 páginas

Seguridad de JWT en Java

El documento habla sobre los JSON Web Tokens (JWT), que son tokens estándar que permiten transmitir información de forma segura entre partes. Los JWT se usan comúnmente para autenticación en aplicaciones web y API, al permitir que un cliente presente un token que representa su identidad al servidor. El documento luego describe algunas consideraciones de seguridad al usar JWT, incluyendo posibles ataques como alteración del algoritmo de firma o robo de tokens, y formas de prevenirlos como agregar un contexto de usuario único al token.

Cargado por

Juan
Derechos de autor
© All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd

Introducción

Muchas aplicaciones usan JSON Web Tokens (JWT) para permitir que el
cliente indique su identidad para un mayor intercambio después de la
autenticación.

De [Link] :

JSON Web Token (JWT) es un estándar abierto (RFC 7519) que define una
forma compacta y autónoma para transmitir información de forma segura entre
las partes como un objeto JSON. Esta información se puede verificar y confiar
porque está firmada digitalmente. Los JWT se pueden firmar usando un
secreto (con el algoritmo HMAC) o un par de claves pública / privada usando
RSA.

JSON Web Token se usa para transportar información relacionada con la


identidad y características (reclamos) de un cliente. El servidor firma esta
información para que pueda detectar si fue manipulada después de enviarla al
cliente. Esto evitará que un atacante cambie la identidad o cualquier
característica (por ejemplo, cambiar el rol de usuario simple a administrador o
cambiar el inicio de sesión del cliente).

Este token se crea durante la autenticación (se proporciona en caso de


autenticación exitosa) y el servidor lo verifica antes de cualquier
procesamiento. Es utilizado por una aplicación para permitir que un cliente
presente un token que represente la "tarjeta de identidad" del usuario al
servidor y permita que el servidor verifique la validez e integridad del token de
manera segura, todo esto de forma apátrida y portátil. enfoque (portátil en la
forma en que las tecnologías de cliente y servidor pueden ser diferentes,
incluido también el canal de transporte, incluso si HTTP es el más utilizado).

Estructura de fichas
Ejemplo de estructura de token tomado de [Link] :
[Base64(HEADER)].[Base64(PAYLOAD)].[Base64(SIGNATURE)]
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.
TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
Trozo 1: Encabezado
{
"alg": "HS256",
"typ": "JWT"
}
Trozo 2: Carga útil
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
Trozo 3: Firma
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), KEY )

Objetivo
Esta hoja de trucos proporciona consejos para evitar problemas de seguridad
comunes al usar JSON Web Tokens (JWT) con Java.

Los consejos presentados en este artículo son parte de un proyecto Java que
fue creado para mostrar la forma correcta de manejar la creación y validación
de JSON Web Tokens.

Puede encontrar el proyecto Java aquí , utiliza la biblioteca oficial JWT .

En el resto del artículo, el término token se refiere a los tokens web


JSON (JWT).

Consideración sobre el uso de JWT


Incluso si un token JWT es "fácil" de usar y permite exponer servicios
(principalmente estilo REST) de forma apátrida, no es la solución que se ajuste
a todas las aplicaciones porque viene con algunas advertencias, como por
ejemplo la cuestión del almacenamiento del token (abordado en esta hoja de
trucos) y otros ...

Si su aplicación no necesita ser completamente apátrida, puede considerar el


uso del sistema de sesión tradicional proporcionado por todos los marcos web
y seguir los consejos de la hoja de trucos de administración de
sesión dedicada . Sin embargo, para aplicaciones sin estado, cuando está
bien implementado, es un buen candidato.

Cuestiones
Algoritmo de hash ninguno
Síntoma
Este ataque, descrito aquí, ocurre cuando un atacante altera el token y cambia
el algoritmo de hash para indicar, mediante la palabra clave none , que la
integridad del token ya ha sido verificada. Como se explica en el enlace
anterior, algunas bibliotecas trataron los tokens firmados con el algoritmo none
como un token válido con una firma verificada , por lo que un atacante puede
alterar los reclamos de token y la aplicación confiará en el token.
Como prevenir
Primero, use una biblioteca JWT que no esté expuesta a esta vulnerabilidad.

Por último, durante la validación del token, solicite explícitamente que se use
el algoritmo esperado.

Ejemplo de implementacion
// HMAC key - Block serialization and storage as String in JVM memory
private transient byte[] keyHMAC = ...;

...

//Create a verification context for the token requesting


//explicitly the use of the HMAC-256 hashing algorithm
JWTVerifier verifier = [Link](Algorithm.HMAC256(keyHMAC)).build();

//Verify the token, if the verification fail then a exception is throwed


DecodedJWT decodedToken = [Link](token);

Token Sidejacking
Síntoma
Este ataque ocurre cuando un atacante ha interceptado / robado un token y lo
utilizan para obtener acceso al sistema utilizando la identidad del usuario
objetivo.

Como prevenir
Una forma de evitarlo es agregar un "contexto de usuario" en el token. Un
contexto de usuario estará compuesto por la siguiente información:

 Una cadena aleatoria que se generará durante la fase de autenticación. Se enviará


al cliente como una cookie reforzada ( marcas : HttpOnly +
Secure + SameSite + prefijos de cookies ).
 Se almacenará un hash SHA256 de la cadena aleatoria en el token (en lugar del
valor sin procesar) para evitar cualquier problema de XSS que permita al atacante
leer el valor de la cadena aleatoria y configurar la cookie esperada.

Las direcciones IP no se deben usar porque hay algunas situaciones legítimas


en las que la dirección IP puede cambiar durante la misma sesión. Por
ejemplo, cuando un usuario accede a una aplicación a través de su dispositivo
móvil y el operador móvil cambia durante el intercambio, la dirección IP puede
(a menudo) cambiar. Además, el uso de la dirección IP puede causar
problemas con el cumplimiento del GDPR europeo .
Durante la validación de token, si el token recibido no contiene el contexto
correcto (por ejemplo, si se ha reproducido), entonces debe ser rechazado.
Ejemplo de implementación
Código para crear el token después de una autenticación exitosa.
// HMAC key - Block serialization and storage as String in JVM memory
private transient byte[] keyHMAC = ...;
// Random data generator
private SecureRandom secureRandom = new SecureRandom();

...

//Generate a random string that will constitute the fingerprint for this user
byte[] randomFgp = new byte[50];
[Link](randomFgp);
String userFingerprint = [Link](randomFgp);

//Add the fingerprint in a hardened cookie - Add cookie manually because


//SameSite attribute is not supported by [Link] class
String fingerprintCookie = "__Secure-Fgp=" + userFingerprint
+ "; SameSite=Strict; HttpOnly; Secure";
[Link]("Set-Cookie", fingerprintCookie);

//Compute a SHA256 hash of the fingerprint in order to store the


//fingerprint hash (instead of the raw value) in the token
//to prevent an XSS to be able to read the fingerprint and
//set the expected cookie itself
MessageDigest digest = [Link]("SHA-256");
byte[] userFingerprintDigest = [Link]([Link]("utf-
8"));
String userFingerprintHash =
[Link](userFingerprintDigest);

//Create the token with a validity of 15 minutes and client context


(fingerprint) information
Calendar c = [Link]();
Date now = [Link]();
[Link]([Link], 15);
Date expirationDate = [Link]();
Map<String, Object> headerClaims = new HashMap<>();
[Link]("typ", "JWT");
String token = [Link]().withSubject(login)
.withExpiresAt(expirationDate)
.withIssuer([Link])
.withIssuedAt(now)
.withNotBefore(now)
.withClaim("userFingerprint", userFingerprintHash)
.withHeader(headerClaims)
.sign(Algorithm.HMAC256([Link]));
Código para validar el token.
// HMAC key - Block serialization and storage as String in JVM memory
private transient byte[] keyHMAC = ...;

...

//Retrieve the user fingerprint from the dedicated cookie


String userFingerprint = null;
if ([Link]() != null && [Link]().length > 0) {
List<Cookie> cookies =
[Link]([Link]()).collect([Link]());
Optional<Cookie> cookie = [Link]().filter(c -> "__Secure-Fgp"

.equals([Link]())).findFirst();
if ([Link]()) {
userFingerprint = [Link]().getValue();
}
}

//Compute a SHA256 hash of the received fingerprint in cookie in order to


compare
//it to the fingerprint hash stored in the token
MessageDigest digest = [Link]("SHA-256");
byte[] userFingerprintDigest = [Link]([Link]("utf-
8"));
String userFingerprintHash =
[Link](userFingerprintDigest);

//Create a verification context for the token


JWTVerifier verifier = [Link](Algorithm.HMAC256(keyHMAC))
.withIssuer(issuerID)
.withClaim("userFingerprint",
userFingerprintHash)
.build();

//Verify the token, if the verification fail then an exception is thrown


DecodedJWT decodedToken = [Link](token);

Sin revocación de token incorporado por


parte del usuario
Síntoma
Este problema es inherente a JWT porque un token solo deja de ser válido
cuando caduca. El usuario no tiene una función incorporada para revocar
explícitamente la validez de un token. Esto significa que si es robado, un
usuario no puede revocar el token en sí mismo, bloqueando así al atacante.

Como prevenir
Una forma de protegerse contra esto es implementar una lista negra de tokens
que se utilizará para imitar la función de "cierre de sesión" que existe con el
sistema tradicional de administración de sesiones.

La lista negra mantendrá un resumen (SHA-256 codificado en HEX) del token


con una fecha de revocación. Esta entrada debe durar al menos hasta la
expiración del token.

Cuando el usuario desea "cerrar sesión", llama a un servicio dedicado que


agregará el token de usuario proporcionado a la lista negra, lo que dará como
resultado una invalidación inmediata del token para su uso posterior en la
aplicación.

Ejemplo de implementacion
Almacenamiento de listas negras
Se utilizará una tabla de base de datos con la siguiente estructura como
almacenamiento central de la lista negra.
create table if not exists revoked_token(jwt_token_digest varchar(255)
primary key,
revocation_date timestamp default now());

Gestión de revocación de tokens


Código a cargo de agregar un token a la lista negra y verificar si un token es
revocado.
/**
* Handle the revocation of the token (logout).
* Use a DB in order to allow multiple instances to check for revoked token
* and allow cleanup at centralized DB level.
*/
public class TokenRevoker {

/** DB Connection */
@Resource("jdbc/storeDS")
private DataSource storeDS;

/**
* Verify if a digest encoded in HEX of the ciphered token is present
* in the revocation table
*
* @param jwtInHex Token encoded in HEX
* @return Presence flag
* @throws Exception If any issue occur during communication with DB
*/
public boolean isTokenRevoked(String jwtInHex) throws Exception {
boolean tokenIsPresent = false;
if (jwtInHex != null && ![Link]().isEmpty()) {
//Decode the ciphered token
byte[] cipheredToken = [Link](jwtInHex);

//Compute a SHA256 of the ciphered token


MessageDigest digest = [Link]("SHA-256");
byte[] cipheredTokenDigest = [Link](cipheredToken);
String jwtTokenDigestInHex =
[Link](cipheredTokenDigest);

//Search token digest in HEX in DB


try (Connection con = [Link]()) {
String query = "select jwt_token_digest from revoked_token where
jwt_token_digest = ?";
try (PreparedStatement pStatement =
[Link](query)) {
[Link](1, jwtTokenDigestInHex);
try (ResultSet rSet = [Link]()) {
tokenIsPresent = [Link]();
}
}
}
}

return tokenIsPresent;
}

/**
* Add a digest encoded in HEX of the ciphered token to the revocation token
table
*
* @param jwtInHex Token encoded in HEX
* @throws Exception If any issue occur during communication with DB
*/
public void revokeToken(String jwtInHex) throws Exception {
if (jwtInHex != null && ![Link]().isEmpty()) {
//Decode the ciphered token
byte[] cipheredToken = [Link](jwtInHex);

//Compute a SHA256 of the ciphered token


MessageDigest digest = [Link]("SHA-256");
byte[] cipheredTokenDigest = [Link](cipheredToken);
String jwtTokenDigestInHex =
[Link](cipheredTokenDigest);

//Check if the token digest in HEX is already in the DB and add it


if it is absent
if (![Link](jwtInHex)) {
try (Connection con = [Link]()) {
String query = "insert into revoked_token(jwt_token_digest)
values(?)";
int insertedRecordCount;
try (PreparedStatement pStatement =
[Link](query)) {
[Link](1, jwtTokenDigestInHex);
insertedRecordCount = [Link]();
}
if (insertedRecordCount != 1) {
throw new IllegalStateException("Number of inserted
record is invalid," +
" 1 expected but is " + insertedRecordCount);
}
}
}

}
}

Divulgación de información de token


Síntoma
Este ataque ocurre cuando un atacante tiene acceso a un token (o un conjunto
de tokens) y extrae la información almacenada en él (el contenido de los tokens
JWT está codificado en base64, pero no está encriptado de manera
predeterminada) para obtener información sobre el sistema. La información
puede ser, por ejemplo, los roles de seguridad, el formato de inicio de sesión.

Como prevenir
Una forma de protegerse contra este ataque es cifrar el token utilizando, por
ejemplo, un algoritmo simétrico.

También es importante proteger los datos cifrados contra ataques


como Padding Oracle o cualquier otro ataque mediante criptoanálisis.
Para lograr todos estos objetivos, se utiliza el algoritmo AES- GCM que
proporciona cifrado autenticado con datos asociados .

Más detalles desde aquí :


AEAD primitive (Authenticated Encryption with Associated Data) provides
functionality of symmetric
authenticated encryption.

Implementations of this primitive are secure against adaptive chosen


ciphertext attacks.

When encrypting a plaintext one can optionally provide associated data that
should be authenticated
but not encrypted.

That is, the encryption with associated data ensures authenticity (ie. who
the sender is) and
integrity (ie. data has not been tampered with) of that data, but not its
secrecy.

See RFC5116: [Link]


Nota:

Aquí el cifrado se agrega principalmente para ocultar información interna, pero


es muy importante recordar que la primera protección contra la manipulación
del token JWT es la firma. Por lo tanto, la firma del token y su verificación
deben estar siempre en su lugar.

Ejemplo de implementacion
Cifrado de fichas
Código encargado de gestionar el cifrado. La biblioteca criptográfica dedicada
de Google Tink se utiliza para manejar operaciones de cifrado con el fin de
utilizar las mejores prácticas integradas proporcionadas por esta biblioteca.
/**
* Handle ciphering and deciphering of the token using AES-GCM.
*
* @see "[Link]
*/
public class TokenCipher {

/**
* Constructor - Register AEAD configuration
*
* @throws Exception If any issue occur during AEAD configuration
registration
*/
public TokenCipher() throws Exception {
[Link]();
}

/**
* Cipher a JWT
*
* @param jwt Token to cipher
* @param keysetHandle Pointer to the keyset handle
* @return The ciphered version of the token encoded in HEX
* @throws Exception If any issue occur during token ciphering operation
*/
public String cipherToken(String jwt, KeysetHandle keysetHandle) throws
Exception {
//Verify parameters
if (jwt == null || [Link]() || keysetHandle == null) {
throw new IllegalArgumentException("Both parameters must be
specified!");
}

//Get the primitive


Aead aead = [Link](keysetHandle);

//Cipher the token


byte[] cipheredToken = [Link]([Link](), null);

return [Link](cipheredToken);
}

/**
* Decipher a JWT
*
* @param jwtInHex Token to decipher encoded in HEX
* @param keysetHandle Pointer to the keyset handle
* @return The token in clear text
* @throws Exception If any issue occur during token deciphering
operation
*/
public String decipherToken(String jwtInHex, KeysetHandle keysetHandle)
throws Exception {
//Verify parameters
if (jwtInHex == null || [Link]() || keysetHandle == null)
{
throw new IllegalArgumentException("Both parameters must be
specified !");
}
//Decode the ciphered token
byte[] cipheredToken = [Link](jwtInHex);

//Get the primitive


Aead aead = [Link](keysetHandle);

//Decipher the token


byte[] decipheredToken = [Link](cipheredToken, null);

return new String(decipheredToken);


}
}

Creación / Validación del Token


Utilice el controlador de cifrado de token durante la creación y la validación del
token.

Cargar claves (la clave de cifrado fue generada y almacenada usando Google
Tink ) y configurar el cifrado.
//Load keys from configuration text/json files in order to avoid to storing
keys as a String in JVM memory
private transient byte[] keyHMAC = [Link]([Link]("src",
"main", "conf", "[Link]"));
private transient KeysetHandle keyCiphering =
[Link]([Link](
[Link]("src", "main", "conf", "[Link]").toFile()));

...

//Init token ciphering handler


TokenCipher tokenCipher = new TokenCipher();
Creación de tokens.
//Generate the JWT token using the JWT API...
//Cipher the token (String JSON representation)
String cipheredToken = [Link](token, [Link]);
//Send the ciphered token encoded in HEX to the client in HTTP response...
Validación de tokens.
//Retrieve the ciphered token encoded in HEX from the HTTP request...
//Decipher the token
String token = [Link](cipheredToken, [Link]);
//Verify the token using the JWT API...
//Verify access...

Almacenamiento de tokens en el lado del


cliente
Síntoma
Esto ocurre cuando una aplicación almacena el token de una manera que
exhibe el siguiente comportamiento:

 Enviado automáticamente por el navegador ( almacenamiento de cookies ).


 Se recupera incluso si se reinicia el navegador (Uso del contenedor localStorage del
navegador ).
 Recuperado en caso de problema XSS (Cookie accesible para código JavaScript o
Token almacenado en el almacenamiento local / sesión del navegador).

Como prevenir
1. Almacene el token utilizando el contenedor sessionStorage del navegador .
2. Agréguelo como un encabezado HTTP de portadorAuthentication con JavaScript
cuando llame a servicios.
3. Agregue información de huella digital al token.

Al almacenar el token en el contenedor sessionStorage del navegador ,


expone el token a ser robado a través de un ataque XSS. Sin embargo, las
huellas digitales agregadas al token evitan que el atacante reutilice el token
robado en su máquina. Para cerrar un máximo de superficies de explotación
para un atacante, agregue una Política de seguridad de contenido
del navegador para fortalecer el contexto de ejecución.
Nota:

 El caso restante es cuando un atacante usa el contexto de navegación del usuario


como proxy para usar la aplicación de destino a través del usuario legítimo, per o la
Política de seguridad de contenido puede evitar la comunicación con dominios no
esperados.
 También es posible implementar el servicio de autenticación de manera que el token
se emita dentro de una cookie reforzada, pero en este caso, se debe implementar
la protección contra un ataque de falsificación de solicitud entre sitios .

Ejemplo de implementacion
Código JavaScript para almacenar el token después de la autenticación.
/* Handle request for JWT token and local storage*/
function authenticate() {
const login = $("#login").val();
const postData = "login=" + encodeURIComponent(login) + "&password=test";

$.post("/services/authenticate", postData, function (data) {


if ([Link] == "Authentication successful!") {
...
[Link]("token", [Link]);
}
else {
...
[Link]("token");
}
})
.fail(function (jqXHR, textStatus, error) {
...
[Link]("token");
});
}
Código JavaScript para agregar el token como encabezado de autenticación
HTTP de portador al llamar a un servicio, por ejemplo, un servicio para validar
el token aquí.
/* Handle request for JWT token validation */
function validateToken() {
var token = [Link]("token");

if (token == undefined || token == "") {


$("#infoZone").removeClass();
$("#infoZone").addClass("alert alert-warning");
$("#infoZone").text("Obtain a JWT token first :)");
return;
}

$.ajax({
url: "/services/validate",
type: "POST",
beforeSend: function (xhr) {
[Link]("Authorization", "bearer " + token);
},
success: function (data) {
...
},
error: function (jqXHR, textStatus, error) {
...
},
});
}

Secreto de token débil


Síntoma
Cuando el token está protegido usando un algoritmo basado en HMAC, la
seguridad del token depende completamente de la fuerza del secreto utilizado
con el HMAC. Si un atacante puede obtener un JWT válido, puede llevar a
cabo un ataque fuera de línea e intentar descifrar el secreto utilizando
herramientas como John the Ripper o Hashcat .

Si tienen éxito, podrían modificar el token y volver a firmarlo con la clave que
habían obtenido. Esto podría permitirles escalar sus privilegios, comprometer
las cuentas de otros usuarios o realizar otras acciones dependiendo del
contenido del JWT.

Hay una serie de guías que documentan este proceso con mayor detalle.
Como prevenir
La forma más sencilla de prevenir este ataque es asegurarse de que el secreto
utilizado para firmar los JWT sea fuerte y único, a fin de que sea más difícil de
descifrar para un atacante. Como este secreto nunca necesitaría ser escrito
por un humano, debe tener al menos 64 caracteres y generarse utilizando
una fuente segura de aleatoriedad .

Alternativamente, considere el uso de tokens firmados con RSA en lugar de


usar una clave secreta y HMAC.

Otras lecturas
 {JWT}. {Attack} .Playbook : un proyecto documenta los ataques conocidos y las
posibles vulnerabilidades de seguridad y configuraciones incorrectas de los tokens
web JSON.
 Borrador de Internet de mejores prácticas de JWT

También podría gustarte