{"repro_id":"REPRO-2026-00222","version":7,"title":"SimpleHelp OIDC authentication accepts unsigned/forged ID tokens, enabling remote authentication bypass and possible MFA bypass in versions 5.5.15 and earlier and 6.0 prereleases prior to the fixed release.","repro_type":"security","status":"published","severity":"critical","cvss_score":9.5,"description":"SimpleHelp’s OpenID Connect (OIDC) authentication flow fails to verify the cryptographic signature on submitted identity tokens. An unauthenticated remote attacker can forge an ID token with arbitrary claims to obtain a fully authenticated technician session; in some configurations this also bypasses multi‑factor authentication. The issue affects SimpleHelp 5.5.15 and earlier and 6.0 prerelease builds before the fixed release.","root_cause":"## Summary\nSimpleHelp 5.5.15 accepts a forged OpenID Connect (OIDC) ID token during the real technician-login OIDC callback flow. In the reproduced configuration, an unauthenticated client requests a legitimate SimpleHelp OIDC login URL from `/auth/v1/account/oidc_get`, receives a server-generated pending `state`, completes the callback at `/oidc`, and supplies an attacker-controlled JWT with `alg: none` and a bogus signature. The vulnerable 5.5.15 server creates a fully authenticated technician session for the forged identity `attacker` / `Forged Attacker`; the patched 5.5.16 server rejects the same forged token and remains unauthenticated.\n\n## Impact\n- **Package/component affected:** SimpleHelp server technician authentication, specifically the OIDC authentication provider and `/oidc` callback path.\n- **Affected versions:** SimpleHelp 5.5.15 and earlier, and 6.0 prerelease builds before the fixed release.\n- **Patched versions tested:** SimpleHelp 5.5.16 (`SimpleHelp-linux-amd64.tar.gz`, SHA256 `9360af980277e1ef4330eb1ed08d981c9dfdcc4e25d87ed0552a47f5ebd5161a`).\n- **Risk level and consequences:** Critical. A remote unauthenticated attacker can authenticate as a group-authenticated technician using forged identity claims. A technician account can access the SimpleHelp technician console and, depending on group permissions and deployment, remote support / remote access capabilities.\n\n## Impact Parity\n- **Disclosed/claimed maximum impact:** Remote authentication/authorization bypass via forged OIDC token, yielding an authenticated technician session and potential MFA bypass.\n- **Reproduced impact from this run:** Full remote API/OIDC authentication bypass on the real SimpleHelp 5.5.15 server. The script obtains a legitimate pending OIDC state from the product API, submits a forged ID token through the genuine `/oidc` callback, and observes `FULLY_AUTHENTICATED` status for the attacker-controlled technician identity.\n- **Parity:** `full`\n- **Not demonstrated:** Post-auth remote-control actions against managed endpoints were not performed; the reproduced impact stops at a concrete authenticated technician session and technician-console page access.\n\n## Root Cause\nThe vulnerable SimpleHelp OIDC implementation parses JWT claims from the ID token but does not cryptographically verify the token signature before using those claims for technician authentication. Runtime/class evidence shows that SimpleHelp 5.5.15 includes `utils/oauth/oidc/IDToken.class` and OIDC callback classes, but no `IDTokenVerifier` class. The patched 5.5.16 build adds `utils/oauth/oidc/IDTokenVerifier.class` and related JWKS caching classes, and its callback rejects the same forged token.\n\nThe key behavioral difference is:\n- **5.5.15 vulnerable:** The forged token reaches `OIDCAuthenticator`, is parsed into an `IDToken`, passes group-authentication logic, registers a new anonymous/group-authenticated technician, and registers a session token.\n- **5.5.16 patched:** The same forged token causes the callback to fail closed; the status endpoint remains `UNAUTHENTICATED`.\n\nNo public fix commit hash was provided in the ticket because SimpleHelp is a commercial binary distribution; the script anchors the negative control to the vendor fixed 5.5.16 release identified in the advisory.\n\n## Reproduction Steps\n1. Run `bundle/repro/reproduction_steps.sh`.\n2. The script:\n   - Verifies/downloads the official SimpleHelp 5.5.15 and 5.5.16 Linux server tarballs.\n   - Extracts clean vulnerable and patched server instances.\n   - Initializes each real SimpleHelp server through the first-launch path.\n   - Configures a real OIDC authentication provider and a non-admin `Technicians` technician group that allows group-authenticated/OIDC-created logins.\n   - Starts a local fake OIDC IdP that returns a JWT with `alg: none` and a bogus signature segment.\n   - Requests a real OIDC authorization URL from `https://127.0.0.1/auth/v1/account/oidc_get`, preserving the server-issued `state`.\n   - Delivers the forged token through the genuine `https://127.0.0.1/oidc?code=...&state=...` callback.\n   - Checks `https://127.0.0.1/auth/v1/account/status` as the post-login authorization proof.\n   - Repeats the same flow against patched 5.5.16 as the negative control.\n3. Expected evidence:\n   - Vulnerable flow: `status_after_body` contains `\"state\":\"FULLY_AUTHENTICATED\"` and the forged identity (`Forged Attacker`, `attacker`, `attacker@example.com`).\n   - Patched flow: callback contains `Login Failed` and status remains `\"state\":\"UNAUTHENTICATED\"`.\n\n## Evidence\nPrimary evidence files:\n- `bundle/logs/reproduction_steps.log` — full script stdout/stderr for the successful run.\n- `bundle/logs/vuln_flow.json` — vulnerable HTTP/API/OIDC flow result.\n- `bundle/logs/patched_flow.json` — patched negative-control flow result.\n- `bundle/logs/vuln_idp.log` — fake IdP requests and the forged ID token returned to the product.\n- `bundle/logs/patched_idp.log` — same forged-token IdP interaction for patched version.\n- `bundle/logs/vuln_runtime_tail.log` — vulnerable server runtime log excerpts.\n- `bundle/logs/patched_runtime_tail.log` — patched server runtime log excerpts.\n- `bundle/logs/class_comparison.log` — class-level fix comparison showing `IDTokenVerifier` absent in 5.5.15 and present in 5.5.16.\n- `bundle/repro/runtime_manifest.json` — runtime evidence manifest written by the reproduction script.\n\nKey excerpts from the successful run:\n\n```json\n// bundle/logs/vuln_flow.json\n\"status_after_body\": \"{\\\"state\\\":\\\"FULLY_AUTHENTICATED\\\",\\\"user\\\":{\\\"uniqueID\\\":480346,\\\"displayName\\\":\\\"Forged Attacker\\\",\\\"username\\\":\\\"attacker\\\",\\\"emailAddress\\\":\\\"attacker@example.com\\\",\\\"isOnline\\\":true},\\\"code\\\":1}\"\n```\n\n```json\n// bundle/logs/patched_flow.json\n\"callback_contains_login_failed\": true,\n\"status_after_body\": \"{\\\"state\\\":\\\"UNAUTHENTICATED\\\",\\\"code\\\":0}\"\n```\n\n```text\n// bundle/logs/vuln_runtime_tail.log\n[OIDCAuthenticator] Received OIDC response (...)\n[ProxyServerAuthentication] Group authenticated technician via group 'Technicians'\n[ServerConfig] Registering technician login for attacker / (Technicians)\n[Server Config] Configuration save requested (Forged Attacker - attacker [(Technicians)] [New Anon])\n[ProxyServerAuthentication] Registering session token for Forged Attacker - attacker [(Technicians)] (...)\n```\n\n```text\n// bundle/logs/class_comparison.log\n[*] Vulnerable OIDC classes\n     7852 ... utils/oauth/oidc/IDToken.class\n     6340 ... com/aem/shelp/proxy/wds/OIDCCallbackManager.class\n[*] Patched OIDC classes\n     8796 ... utils/oauth/oidc/IDToken.class\n      252 ... utils/oauth/oidc/IDTokenVerifier$1.class\n     1660 ... utils/oauth/oidc/IDTokenVerifier$CachedJwks.class\n    17996 ... utils/oauth/oidc/IDTokenVerifier.class\n```\n\nEnvironment details captured in logs include SimpleHelp server version/build (`5.5.15` build `20260326-092709`, patched `5.5.16` build `20260526-203544`), bundled JRE versions, HTTPS listener startup, and the exact API/callback URLs exercised.\n\n## Recommendations / Next Steps\n- Upgrade SimpleHelp servers to 5.5.16 or later, or to the fixed 6.0 release/RC identified by the vendor.\n- Ensure OIDC ID tokens are validated using issuer metadata/JWKS before any claims are trusted:\n  - Verify the JWT signature.\n  - Reject `alg: none` or unsupported algorithms.\n  - Validate `iss`, `aud`, `exp`, `iat`, and nonce/state binding.\n- Add regression tests that submit unsigned and incorrectly signed ID tokens through the real `/oidc` callback and assert rejection.\n- Audit existing SimpleHelp deployments for unexpected group-authenticated/anonymous technician accounts, especially identities created via OIDC.\n\n## Additional Notes\n- The script was run successfully twice consecutively after the escaping/configuration fix.\n- The reproduction uses the real SimpleHelp server binaries and real HTTPS API/callback paths; the only test double is the OIDC identity provider, which is attacker-controlled by design for this class of vulnerability.\n- The script rewrites the IdP redirect host to `127.0.0.1` after preserving the SimpleHelp-issued callback path, `code`, and `state`, because the product derives a public hostname from its local environment. This keeps the callback on the genuine SimpleHelp `/oidc` endpoint while avoiding external DNS/network dependence.\n","cve_id":"CVE-2026-48558","cwe_id":"CWE-347 (Improper Verification of Cryptographic Signature)","source_url":"https://nvd.nist.gov/vuln/detail/CVE-2026-48558","package":{"name":"SimpleHelp","ecosystem":"other (commercial, Java-based server application)","affected_versions":"SimpleHelp 5.5.15 and earlier; 6.0 prerelease versions before 6.0 RC2","fixed_version":"5.5.16; 6.0 RC2 / 6.0 prerelease (20260327-150806)"},"reproduced_at":"2026-07-04T07:12:55.570720+00:00","duration_secs":5665.0,"tool_calls":550,"handoffs":4,"total_cost_usd":37.965324480000035,"agent_costs":{"coding":1.9671581700000005,"hypothesis_generator":0.012159,"judge":0.05948125,"repro":32.530761160000004,"support":0.061098900000000005,"vuln_variant":3.334666000000001},"cost_breakdown":{"coding":{"accounts/fireworks/routers/glm-5p2-fast":1.9671581700000005},"hypothesis_generator":{"accounts/fireworks/models/glm-5p2":0.012159},"judge":{"gpt-5.4-mini":0.05948125},"repro":{"accounts/fireworks/routers/glm-5p2-fast":6.493109160000002,"gpt-5.5":26.037652000000005},"support":{"accounts/fireworks/routers/glm-5p2-fast":0.061098900000000005},"vuln_variant":{"gpt-5.5":3.334666000000001}},"quality":{"confidence":"high","idempotent_verified":false,"community_verifications":0},"environment":{"sandbox_image":"ghcr.io/n3mes1s/pruva-sandbox@sha256:8096b2518d6022e13d68f885c3b8ded6b4fe607098b1a1ccbfb99abc004d1dc1"},"published_at":"2026-07-04T07:12:56.692888+00:00","retracted":false,"artifacts":[{"path":"bundle/repro/rca_report.md","filename":"rca_report.md","size":8336,"category":"analysis"},{"path":"bundle/vuln_variant/rca_report.md","filename":"rca_report.md","size":13229,"category":"analysis"},{"path":"bundle/repro/reproduction_steps.sh","filename":"reproduction_steps.sh","size":17868,"category":"reproduction_script"},{"path":"bundle/vuln_variant/reproduction_steps.sh","filename":"reproduction_steps.sh","size":19340,"category":"reproduction_script"},{"path":"bundle/coding/proposed_fix.diff","filename":"proposed_fix.diff","size":24553,"category":"patch"},{"path":"bundle/ticket.md","filename":"ticket.md","size":3301,"category":"ticket"},{"path":"bundle/ticket.json","filename":"ticket.json","size":4273,"category":"other"},{"path":"bundle/logs/vuln_idp.log","filename":"vuln_idp.log","size":973,"category":"log"},{"path":"bundle/logs/patched_idp.log","filename":"patched_idp.log","size":1011,"category":"log"},{"path":"bundle/logs/class_comparison.log","filename":"class_comparison.log","size":1099,"category":"log"},{"path":"bundle/logs/vuln_runtime_tail.log","filename":"vuln_runtime_tail.log","size":18609,"category":"log"},{"path":"bundle/logs/patched_runtime_tail.log","filename":"patched_runtime_tail.log","size":18242,"category":"log"},{"path":"bundle/vuln_variant/patch_analysis.md","filename":"patch_analysis.md","size":7478,"category":"documentation"},{"path":"bundle/coding/summary_report.md","filename":"summary_report.md","size":10229,"category":"documentation"},{"path":"bundle/coding/verify_logs/fixed_idp.log","filename":"fixed_idp.log","size":372,"category":"log"},{"path":"bundle/coding/src/FixAgent.java","filename":"FixAgent.java","size":3972,"category":"other"},{"path":"bundle/coding/fixagent.jar","filename":"fixagent.jar","size":5923,"category":"other"},{"path":"bundle/repro/runtime_manifest.json","filename":"runtime_manifest.json","size":1035,"category":"other"},{"path":"bundle/repro/validation_verdict.json","filename":"validation_verdict.json","size":740,"category":"other"},{"path":"bundle/logs/reproduction_steps.log","filename":"reproduction_steps.log","size":6019,"category":"log"},{"path":"bundle/logs/flow_summary.json","filename":"flow_summary.json","size":765,"category":"other"},{"path":"bundle/logs/vuln_flow.json","filename":"vuln_flow.json","size":2498,"category":"other"},{"path":"bundle/logs/patched_flow.json","filename":"patched_flow.json","size":2341,"category":"other"},{"path":"bundle/logs/forged_jwt.txt","filename":"forged_jwt.txt","size":350,"category":"other"},{"path":"bundle/vuln_variant/runtime_manifest.json","filename":"runtime_manifest.json","size":965,"category":"other"},{"path":"bundle/vuln_variant/validation_verdict.json","filename":"validation_verdict.json","size":2311,"category":"other"},{"path":"bundle/vuln_variant/variant_manifest.json","filename":"variant_manifest.json","size":3914,"category":"other"},{"path":"bundle/vuln_variant/root_cause_equivalence.json","filename":"root_cause_equivalence.json","size":1484,"category":"other"},{"path":"bundle/coding/verify_fix.sh","filename":"verify_fix.sh","size":17672,"category":"other"},{"path":"bundle/coding/verify_logs/verify_result.json","filename":"verify_result.json","size":717,"category":"other"},{"path":"bundle/coding/verify_logs/fix_evidence.log","filename":"fix_evidence.log","size":318,"category":"log"},{"path":"bundle/coding/verify_logs/fixed_flow.json","filename":"fixed_flow.json","size":1090,"category":"other"}]}