[Bug]: Arbitrary code execution through same-basename YAML sidecar of a checkpoint (model.target)

#17499 · open · 0 comments

View on GitHub ↗

3em0

### Checklist - [x] The issue exists after disabling all extensions - [x] The issue exists on a clean installation of webui - [ ] The issue is caused by an extension, but I believe it is caused by a bug in the webui - [x] The issue exists in the current version of the webui - [x] The issue has not been reported before recently - [ ] The issue has been reported before but has not been fixed yet ### What happened? stable-diffusion-webui automatically adopts a same-basename `.yaml` file sitting next to a `.safetensors` checkpoint as the model configuration (modules/sd_models_config.py, find_checkpoint_config_near_filename, lines 128-136). The YAML is parsed with OmegaConf and its `model.target` string is resolved to a Python object with importlib.import_module() + getattr() and invoked with `model.params` as kwargs (modules/sd_models.py:767-783, called from load_model at lines 809-820). A model package containing only three ordinary files therefore executes arbitrary attacker-supplied Python as soon as the victim selects the checkpoint in the UI: mbe2e_pack/ model.safetensors (inert, written with the official safetensors writer API) model.yaml model.target: torch.hub.load hubconf.py arbitrary top-level Python, imported by torch.hub with model.yaml: model: target: torch.hub.load params: repo_or_dir: models/Stable-diffusion/mbe2e_pack model: build source: local Selecting model.safetensors makes instantiate_from_config call torch.hub.load(repo_or_dir=..., model="build", source="local", use_ema=False), and torch.hub imports the package's hubconf.py, running its top-level code inside the webui process. The injected `use_ema=False` (repair_config, sd_models.py:599-601) is absorbed by torch.hub.load's **kwargs forwarding, so this gadget survives the real product path. I verified this end-to-end on a clean checkout of commit 82a973c04367123ae98bd9abdf80d9eda9b910e2 (the v1.10.1 release commit): a negative-control package whose model.yaml only differs in `model.target: torch.nn.Identity` (same hubconf.py on disk) does NOT execute hubconf.py, while the package with `target: torch.hub.load` writes a canary file from hubconf.py. The product's own API even reports the attacker's YAML as the model config: GET /sdapi/v1/sd-models shows "config": ".../models/Stable-diffusion/mbe2e_pack/model.yaml". ### Steps to reproduce the problem 1. Unpack the attacker's package into models/Stable-diffusion/: mbe2e_pack/model.safetensors (any valid tiny safetensors file), mbe2e_pack/model.yaml (target: torch.hub.load, repo_or_dir pointing at the package dir, model: build, source: local), mbe2e_pack/hubconf.py (top-level code that writes a canary file, plus a build(**kwargs) entry point). 2. Start the webui normally (my run: --api --skip-load-model-at-start --use-cpu all; the code path is identical when selecting a checkpoint from the UI dropdown). 3. Select the checkpoint "mbe2e_pack/model.safetensors" (UI dropdown, or POST /sdapi/v1/options {"sd_model_checkpoint": "mbe2e_pack/model.safetensors"}). 4. Observe the canary file created by hubconf.py inside the webui process, and the console line "Creating model from config: .../mbe2e_pack/model.yaml". 5. Negative control: an otherwise identical package with `model.target: torch.nn.Identity` and `params: {}` loads the same sidecar (same console line) but does NOT run hubconf.py — proving the execution is driven by the YAML's `target` field. ### What should have happened? Files packaged next to a downloaded checkpoint must not be executed as code. The sidecar config should either be restricted to a validated, allow-listed schema (no arbitrary `model.target` object resolution), or same-basename YAMLs from untrusted model archives should be ignored/warned about. The dynamic import path (get_obj_from_str -> importlib.import_module + getattr, sd_models.py:778) should not be reachable from user-supplied config files. ### What browsers do you use to access the UI ? Google Chrome ### Sysinfo [sysinfo-2026-09-29-00-33.json](https://github.com/user-attachments/files/32834174/sysinfo-2026-09-29-00-33.json) ### Console logs ```Shell Calculating sha256 for /home/<user>/mbe2e/a1111-yaml-target/stable-diffusion-webui/models/Stable-diffusion/mbe2e_neg/model.safetensors: e2aa1d1ef36ae74397455e34469d45f9b025a92c2784931ebd8738d5a49f46ad Loading weights [e2aa1d1ef3] from /home/<user>/mbe2e/a1111-yaml-target/stable-diffusion-webui/models/Stable-diffusion/mbe2e_neg/model.safetensors Creating model from config: /home/<user>/mbe2e/a1111-yaml-target/stable-diffusion-webui/models/Stable-diffusion/mbe2e_neg/model.yaml Calculating sha256 for /home/<user>/mbe2e/a1111-yaml-target/stable-diffusion-webui/models/Stable-diffusion/mbe2e_pack/model.safetensors: d5f1f2669119094351dcd419f93e40bec964ff6a08223a0f6ad1598cf34e5133 Loading weights [d5f1f26691] from /home/<user>/mbe2e/a1111-yaml-target/stable-diffusion-webui/models/Stable-diffusion/mbe2e_pack/model.safetensors Creating model from config: /home/<user>/mbe2e/a1111-yaml-target/stable-diffusion-webui/models/Stable-diffusion/mbe2e_pack/model.yaml # after the second selection, the canary file written by hubconf.py exists: $ ls -l /out total 4 -rw-r--r-- 1 <user> <user> 34 Sep 29 08:29 pwned_by_a1111_yaml_target $ cat /out/pwned_by_a1111_yaml_target MBE2E-CANARY-a1111-yaml-target-v1 ``` ### Additional information - Affected: v1.10.1 (release commit 82a973c04367123ae98bd9abdf80d9eda9b910e2) and current master HEAD (same commit). Verified on Linux (Python 3.11, torch 2.1.2+cpu); the vulnerable code paths are platform-independent Python. - Vulnerable code: modules/sd_models_config.py:128-136 (same-basename sidecar discovery); modules/sd_models.py:809-820 (OmegaConf.load -> instantiate_from_config); modules/sd_models.py:767-783 (target string -> importlib + getattr -> call); modules/sd_models.py:599-601 (repair_config injects use_ema=False) - Suggested severity: High — CVSS 3.1 AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H (8.8); CWE-470 / CWE-94. Model packages are routinely downloaded from third-party sites and unpacked into models/Stable-diffusion/, so "unpack + select" is the standard victim flow. - The safetensors file itself is inert here (official writer API); the flaw is the automatic adoption of the sidecar YAML plus unrestricted target instantiation. - Suggested fix: validate sidecar configs against a fixed schema with an allow-list for model.target, or only honor explicitly registered configs; do not dynamically import attacker-named modules from checkpoint-adjacent files. - Attached: a1111-yaml-target-poc.zip containing the positive package (mbe2e_pack/), the negative-control package (mbe2e_neg/), the generator script (make_poc.py), SHA256SUMS.txt, a step-by-step REPRODUCE.md and the 12 evidence screenshots (screenshots/). The canary path /out/pwned_by_a1111_yaml_target in hubconf.py is an inert marker file write used to demonstrate execution. [a1111-yaml-target-poc.zip](https://github.com/user-attachments/files/32834193/a1111-yaml-target-poc.zip) link: https://github.com/3em0/cve_repo/blob/main/2026/AUTOMATIC1111_stable-diffusion-webui_v1.10.1_Arbitrary_Code_Execution_via_Same-Basename_YAML_Sidecar(%60model.target%60).md

Comments