{"repro_id":"REPRO-2026-00285","version":6,"title":"Samba’s printing subsystem allows OS command injection via unescaped job description (%J), enabling remote code execution through crafted print jobs.","repro_type":"security","status":"published","severity":"high","description":"A flaw in Samba’s printing subsystem passes the client-controlled job description string to the configured `print command` using the `%J` substitution without escaping shell metacharacters. An unauthenticated/remote attacker who can submit a print job can inject shell commands, leading to remote code execution on the Samba server.","root_cause":"# Root Cause Analysis: CVE-2026-4480 — Samba Printing Subsystem OS Command Injection via %J\n\n## Summary\n\nCVE-2026-4480 is an unauthenticated remote code execution vulnerability in Samba's printing subsystem. When a Samba server is configured with a non-CUPS/non-iPrint printing backend (e.g., `printing = sysv`) and a `print command` that includes the `%J` substitution character (job description), a remote attacker can submit a print job whose document name contains shell metacharacters. Samba substitutes the client-controlled job name into the `print command` string and executes it via `system()` with only partial sanitization — the characters `$ \\` \" ' ; %` are stripped, but `& ( ) # | < >` and others survive, enabling OS command injection and arbitrary code execution as the Samba service account.\n\n## Impact\n\n- **Package/component affected:** Samba `smbd` printing subsystem — `source3/printing/print_generic.c`, function `generic_job_submit()` → `print_run_command()` → `smbrun_no_sanitize()` → `system()`\n- **Affected versions:** All Samba versions prior to 4.22.10, 4.23.8, and 4.24.3 (released 26 May 2026). Verified vulnerable on 4.22.9.\n- **Risk level:** Critical (CVSS 3.1 base 10.0 — AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H). Unauthenticated, remote, default guest print access.\n- **Consequences:** Arbitrary OS command execution with the privileges of the Samba service (typically `nobody` or the configured `guest account`). Full server compromise possible.\n\n## Impact Parity\n\n- **Disclosed/claimed maximum impact:** Remote code execution (unauthenticated, network-reachable)\n- **Reproduced impact from this run:** Remote code execution confirmed — the `id` command executed on the Samba server and its output (`uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)`) was captured in the server-side log, plus an arbitrary file (`marker`) was created in the spool directory by the injected `touch` command. All via an unauthenticated guest SMB connection over TCP 445.\n- **Parity:** `full`\n- **Not demonstrated:** A full reverse shell or privilege escalation to root was not demonstrated (the Samba guest account runs as `nobody`), but the core claim of unauthenticated remote code execution is fully reproduced.\n\n## Root Cause\n\n### Vulnerable code path\n\nWhen a client opens a file on a printable SMB share, `smbd` processes it as a print job:\n\n1. `smbd/open.c` → `print_spool_open(fsp, filename, vuid)` — the client-supplied filename becomes the document name (`docname = \"Remote Downlevel Document <filename>\"`)\n2. `print_spool_open()` → `print_job_start(docname)` → `fstrcpy(pjob.jobname, docname)` — the docname is stored as the job name with **no sanitization**\n3. On file close → `print_job_end()` → `generic_job_submit()`\n4. `generic_job_submit()` in `source3/printing/print_generic.c`:\n   ```c\n   jobname = talloc_strdup(ctx, pjob->jobname);\n   jobname = talloc_string_sub(ctx, jobname, \"'\", \"_\");  // only replaces ' → _\n   ...\n   ret = print_run_command(snum, ..., lp_print_command(snum), NULL,\n       \"%s\", p, \"%J\", jobname, ...);\n   ```\n5. `print_run_command()` substitutes `%J` with the jobname using `talloc_string_sub()`:\n   ```c\n   syscmd = talloc_string_sub(ctx, syscmd, arg, value);  // arg=\"%J\", value=jobname\n   ```\n6. `talloc_string_sub()` calls `talloc_string_sub2()` with `remove_unsafe_characters=true`, which replaces only `$ \\` \" ' ; % \\r \\n` with `_` in the **insert** (jobname) string\n7. The resulting command string is passed to `smbrun_no_sanitize()` → `execl(\"/bin/sh\", \"sh\", \"-c\", cmd, NULL)` — **no shell escaping**\n\n### The gap\n\nThe `talloc_string_sub2()` unsafe-character list is:\n```c\ncase '$': case '`': case '\"': case '\\'': case ';': case '%': case '\\r': case '\\n':\n```\n\nThis misses critical shell metacharacters: `&`, `|`, `<`, `>`, `(`, `)`, `#`, space, `!`, etc. The `&` character (and `&&`) is a valid command separator in POSIX shells and is **not** in the sanitized set. An attacker can inject `&&` in the job description to chain arbitrary commands.\n\n### The fix (commit `b80131fcf582`)\n\nThe fix in Samba 4.22.10 adds `replace_print_cmd_J()` which:\n- Defines `STRING_SUB_UNSAFE_CHARACTERS \"$\\`\\\"';%|&<>\"` (includes `&`, `|`, `<`, `>`)\n- Masks ALL unsafe characters (plus `/` and `\\`) to `_`\n- Wraps the `%J` substitution in single quotes (or falls back to a fixed `__CVE-2026-4480_FallbackJobname__` string for mixed-quoting configurations)\n- Pre-substitutes `%J` in the print command before passing to `print_run_command`, removing the raw `%J` → jobname path\n\nFix commit: `b80131fcf582ecc8e8c1b97e6051bb324bb8bef8` (Samba master), backported to 4.22.10, 4.23.8, 4.24.3.\n\n## Reproduction Steps\n\n1. **Reference:** `bundle/repro/reproduction_steps.sh`\n2. **What the script does:**\n   - Builds vulnerable Samba 4.22.9 and fixed Samba 4.22.10 from source (or reuses cached builds from the project cache)\n   - Configures `smbd` with `printing = sysv`, a `printcap` entry for printer `testprn`, and a `print command` referencing unquoted `%J`: `(echo %J) > /tmp/samba_printlog 2>&1`\n   - Starts the real `smbd` daemon listening on TCP port 445 with guest-accessible printing\n   - Uses an impacket-based Python SMB client to connect over TCP 445 (guest/anonymous login), open a file named `PWN&&id&&touch marker&&END` on the `[testprn]` printable share, write print data, and close it\n   - The server substitutes the document name (becomes `%J`) into the print command and executes it via `system()` — the `&&` survives sanitization, causing `id` and `touch marker` to execute\n   - Repeats the same exploit against the fixed Samba 4.22.10 as a negative control\n3. **Expected evidence of reproduction:**\n   - **Vulnerable (4.22.9):** `/tmp/samba_printlog` contains `uid=65534(nobody)...` (output of injected `id` command) and a `marker` file is created in the spool directory\n   - **Fixed (4.22.10):** `/tmp/samba_printlog` contains the literal masked string `PWN__id__touch marker__END` (no `uid=`, no command execution) and no `marker` file is created\n\n## Evidence\n\n### Vulnerable Samba 4.22.9 — RCE confirmed\n\n**Print log** (`bundle/artifacts/smb-vuln/printlog.txt`):\n```\nRemote Downlevel Document PWN\nuid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)\nsh: 1: END: not found\n```\nThe `uid=65534(nobody)` line is the output of the injected `id` command — **arbitrary code execution proven**.\n\n**Marker file** (`bundle/artifacts/smb-vuln/marker_check.txt`):\n```\n-rw-rw-rw- 1 nobody nogroup 0 Jul 12 21:47 /tmp/sambatest/spool/marker\n```\nCreated by the injected `touch marker` command running as `nobody` (the Samba guest account).\n\n**smbd log** (`bundle/artifacts/smb-vuln/smbd_command_log.txt`):\n```\nRunning the command `(echo Remote Downlevel Document PWN&&id&&touch marker&&END) > /tmp/samba_printlog 2>&1' gave 127\n```\nThe `&&` is intact in the executed command — no shell escaping applied.\n\n### Fixed Samba 4.22.10 — injection blocked (negative control)\n\n**Print log** (`bundle/artifacts/smb-fixed/printlog.txt`):\n```\nRemote Downlevel Document PWN__id__touch marker__END\n```\nThe `&&` was masked to `__` and the jobname was wrapped in single quotes — **no command execution**.\n\n**Marker file** (`bundle/artifacts/smb-fixed/marker_check.txt`):\n```\nno marker (correct)\n```\n\n**smbd log** (`bundle/artifacts/smb-fixed/smbd_command_log.txt`):\n```\nRunning the command `(echo 'Remote Downlevel Document PWN__id__touch marker__END') > /tmp/samba_printlog_fixed 2>&1' gave 0\n```\nThe jobname is single-quoted and masked — safe execution.\n\n### Environment\n- Samba built from source: `samba-4.22.9` (vulnerable, commit `ff3dd69`) and `samba-4.22.10` (fixed, commit `0abface`)\n- Build: `--bundled-libraries=ALL --without-ad-dc --disable-python` (standalone file/print server)\n- OS: Ubuntu 26.04 LTS, 32 cores\n- Client: impacket 0.13.1 SMBConnection over TCP 445, guest/anonymous authentication\n\n## Recommendations / Next Steps\n\n1. **Upgrade immediately** to Samba 4.22.10, 4.23.8, or 4.24.3 (or later)\n2. **Remove `%J`** from `print command` in `smb.conf`, or wrap it in single quotes (`'%J'`) as a temporary mitigation\n3. **Switch to CUPS** (`printing = cups`) — CUPS/iPrint backends bypass the vulnerable `generic_job_submit()` path\n4. **Disable guest printer access** — require authentication for print shares\n5. **Audit** existing Samba deployments for `print command` entries containing `%J`\n\n## Additional Notes\n\n- **Idempotency:** The reproduction script cleans all state directories between runs and re-creates them fresh. Each vulnerable/fixed test uses a separate base directory.\n- **SMB filename restrictions:** The classic SMB print path restricts the filename (and thus the jobname) to characters valid in SMB/CIFS names (no `/ \\ : * ? \" < > |`). The `&` character is allowed in SMB filenames and is not sanitized by the vulnerable `talloc_string_sub()`, making it the key injection vector. The spoolss RPC path (`StartDocPrinter` with `pDocName`) is not subject to SMB filename restrictions and allows even more characters, but the same `&` injection works through both paths.\n- **Why `&&` and not `;` or `$()`:** The vulnerable `talloc_string_sub2()` sanitizes `;`, `$`, and backtick but NOT `&`. The `&&` operator is a valid POSIX shell command separator that passes through the sanitization, enabling command chaining.\n- **The `echo %J >> file` vs `(echo %J) > file 2>&1` difference:** The subshell in the print command captures all output (including the injected commands' stdout/stderr) in the log file, providing clear RCE evidence.\n","cve_id":"CVE-2026-4480","cwe_id":"CWE-78","source_url":"https://nvd.nist.gov/vuln/detail/CVE-2026-4480","package":{"name":"samba-team/samba","ecosystem":"generic","affected_versions":"All Samba versions before the fix (advisory says 'All versions')"},"reproduced_at":"2026-07-13T05:29:04.742716+00:00","duration_secs":1735.0,"tool_calls":343,"handoffs":2,"total_cost_usd":8.768708870000001,"agent_costs":{"judge":0.00999335,"repro":5.1937915499999985,"support":0.10793016,"vuln_variant":3.4569938099999997},"cost_breakdown":{"judge":{"gpt-5.4-mini":0.00999335},"repro":{"accounts/fireworks/routers/glm-5p2-fast":5.1937915499999985},"support":{"accounts/fireworks/routers/glm-5p2-fast":0.10793016},"vuln_variant":{"accounts/fireworks/routers/glm-5p2-fast":3.4569938099999997}},"quality":{"confidence":"high","idempotent_verified":false,"community_verifications":0},"environment":{"sandbox_image":"ghcr.io/n3mes1s/pruva-sandbox@sha256:8096b2518d6022e13d68f885c3b8ded6b4fe607098b1a1ccbfb99abc004d1dc1"},"published_at":"2026-07-13T05:29:28.475700+00:00","retracted":false,"artifacts":[{"path":"bundle/repro/reproduction_steps.sh","filename":"reproduction_steps.sh","size":11809,"category":"reproduction_script"},{"path":"bundle/repro/rca_report.md","filename":"rca_report.md","size":9569,"category":"analysis"},{"path":"bundle/vuln_variant/reproduction_steps.sh","filename":"reproduction_steps.sh","size":11729,"category":"reproduction_script"},{"path":"bundle/vuln_variant/rca_report.md","filename":"rca_report.md","size":14345,"category":"analysis"},{"path":"bundle/artifact_promotion_manifest.json","filename":"artifact_promotion_manifest.json","size":13898,"category":"other"},{"path":"bundle/artifact_promotion_report.json","filename":"artifact_promotion_report.json","size":13916,"category":"other"},{"path":"bundle/vuln_variant/source_identity.json","filename":"source_identity.json","size":729,"category":"other"},{"path":"bundle/vuln_variant/root_cause_equivalence.json","filename":"root_cause_equivalence.json","size":2792,"category":"other"},{"path":"bundle/logs/CVE-2026-4480_fix.patch","filename":"CVE-2026-4480_fix.patch","size":5869,"category":"other"},{"path":"bundle/repro/validation_verdict.json","filename":"validation_verdict.json","size":962,"category":"other"},{"path":"bundle/repro/runtime_manifest.json","filename":"runtime_manifest.json","size":1065,"category":"other"},{"path":"bundle/logs/reproduction_steps.log","filename":"reproduction_steps.log","size":3244,"category":"log"},{"path":"bundle/logs/vuln_variant/variant_repro.log","filename":"variant_repro.log","size":3482,"category":"log"},{"path":"bundle/vuln_variant/spoolss_exploit.py","filename":"spoolss_exploit.py","size":7097,"category":"script"},{"path":"bundle/vuln_variant/variant_manifest.json","filename":"variant_manifest.json","size":3892,"category":"other"},{"path":"bundle/vuln_variant/validation_verdict.json","filename":"validation_verdict.json","size":3164,"category":"other"},{"path":"bundle/vuln_variant/patch_analysis.md","filename":"patch_analysis.md","size":6596,"category":"documentation"},{"path":"bundle/vuln_variant/runtime_manifest.json","filename":"runtime_manifest.json","size":1576,"category":"other"}]}