Aller au contenu

Fingerprint d'un cookie de session : reconnaître Flask d'un JWT

Identifier la technologie qui tourne derrière une application oriente tout le reste d’un test. Un cookie de session en dit long : sa structure, ses préfixes et son nom révèlent souvent le framework, et donc la famille d’attaques à tenter. Un cookie qui commence par .eJw désigne presque toujours Flask, là où eyJ désigne un JWT.

Une requête curl -v sur une connexion expose l’en-tête Set-Cookie :

Set-Cookie: session=.eJwljEEKAjEMRe-StQu1TZrGy0zTpDCsdVzJ3F0GXPzFf7z_hry2gh2O93qHA-aXwwGDm7fRhpKWSHVoLdakyk6qUZ2iN26aOytFHMlYbfKcVjhikGRznTrJmZBGtNbaVCel7Cq5DKuxRhPr8w2jF_ZawuP4AGrcYYE.ag7Wlw.k2mDqX8vTnZpR4wLsY3bJcF7hUo; HttpOnly; Path=/

Trois indices se combinent pour conclure qu’il s’agit de Flask, le framework web Python.

Un cookie Flask se présente toujours sous la forme de trois chaînes séparées par des points :

[Payload Base64] . [Timestamp] . [Signature]

Sur le cookie ci-dessus, la séparation est nette :

  • .eJwljEEKAjEMRe...P4AGrcYYE : le payload, un flux compressé et encodé en Base64.
  • ag7Wlw : le timestamp, un bloc court.
  • k2mDqX8vTnZpR4wLsY3bJcF7hUo : la signature binaire encodée.

Beaucoup de formats ont des « nombres magiques », des octets de signature en tête de flux. Flask compresse le contenu de session avec zlib avant de l’encoder en Base64 URL-safe. Or un flux zlib commence presque toujours par les octets 78 9C ou 78 01.

Encodés en Base64, ces octets donnent systématiquement les deux premiers caractères eJ. Un point initial peut précéder ce préfixe lorsque le payload est effectivement compressé.

Dès qu’un cookie ou un jeton commence par eJw ou .eJw, il s’agit à quasi-certitude d’un objet compressé en zlib puis encodé, le comportement par défaut de Flask via sa bibliothèque itsdangerous.

Par défaut, si le développeur n’a pas modifié la configuration, Flask nomme toujours son cookie session :

Set-Cookie: session=.eJwljEE...

Le nom session=, combiné à la structure à trois blocs et au démarrage en eJ, forme la signature complète de Flask.

C’est le piège classique : un JWT possède lui aussi trois parties séparées par des points, header.payload.signature. La distinction se fait au premier coup d’œil.

IndiceCookie FlaskJWT
PréfixeeJw ou .eJw (payload compressé zlib)eyJ (en-tête JSON {"alg"...} en clair)
Bloc centralTimestamp court et dédiéAucun bloc dédié, le timestamp est dans les claims
Nom d’en-tête typiquesession=Authorization: Bearer ou cookie applicatif

Le JWT commence par eyJ parce que son en-tête non compressé est un JSON qui débute par {"alg". Le cookie Flask commence par eJw parce qu’il est compressé. Le JWT n’a pas de bloc central court dédié au timestamp : sa date d’émission est incluse directement dans le JSON de la charge utile (iat, exp).

Le fingerprint n’est pas une fin en soi, il ouvre la suite. Savoir qu’un cookie est un session Flask signé par itsdangerous oriente vers un axe précis : l’intégrité repose sur une clé secrète serveur, la SECRET_KEY. Si cette clé est faible ou par défaut, la signature se rejoue hors ligne et le contenu de session devient forgeable, exactement comme un secret HMAC faible sur un JWT. Identifier Flask, c’est décider quelle attaque tenter ensuite plutôt que de tâtonner.

La forme d’un cookie de session est un révélateur de technologie. Trois blocs séparés par des points, un préfixe .eJw, un nom session= : c’est Flask et sa compression zlib. Un préfixe eyJ sans bloc timestamp dédié : c’est un JWT. Cette lecture au premier coup d’œil évite les fausses pistes et pointe directement la famille d’attaques adaptée, à commencer par le test de la clé de signature.