/
githubmirror
/
deno
Обзор
Документация
Войти
/
githubmirror
/
deno
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
tests/specs/node/child_process_shell_escape/main.ts
187 строк
6 KB
Felipe Cardozo
fix:(ext/node): escape simple quotes in node:child_process (#32336)
02 мар 2026, 12:34
Не верифицирован
02 мар 2026, 12:34
b4d4a5b
Код
Авторство
О чём код?
// Test that shell metacharacters in arguments are properly escaped // when using shell: true with spawn/spawnSync to prevent command injection. import { spawn, spawnSync } from "node:child_process"; import * as fs from "node:fs"; import * as path from "node:path"; const tempDir = Deno.cwd(); const markerFile = path.join(tempDir, "injection_marker"); // Clean up any existing marker file try { fs.unlinkSync(markerFile); } catch { // ignore } // Test 1: Newline injection should be blocked console.log("Test 1: Newline injection in args"); const newlinePayload = `dummy\ntouch ${markerFile}`; spawnSync("echo", [newlinePayload], { shell: true }); if (fs.existsSync(markerFile)) { console.log("FAIL: Newline injection was not blocked"); Deno.exit(1); } else { console.log("PASS: Newline injection blocked"); } // Test 2: Semicolon injection should be blocked console.log("Test 2: Semicolon injection in args"); const semicolonPayload = `dummy; touch ${markerFile}`; spawnSync("echo", [semicolonPayload], { shell: true }); if (fs.existsSync(markerFile)) { console.log("FAIL: Semicolon injection was not blocked"); Deno.exit(1); } else { console.log("PASS: Semicolon injection blocked"); } // Test 3: Pipe injection should be blocked console.log("Test 3: Pipe injection in args"); const pipePayload = `dummy | touch ${markerFile}`; spawnSync("echo", [pipePayload], { shell: true }); if (fs.existsSync(markerFile)) { console.log("FAIL: Pipe injection was not blocked"); Deno.exit(1); } else { console.log("PASS: Pipe injection blocked"); } // Test 4: Backtick injection should be blocked console.log("Test 4: Backtick injection in args"); const backtickPayload = "`touch " + markerFile + "`"; spawnSync("echo", [backtickPayload], { shell: true }); if (fs.existsSync(markerFile)) { console.log("FAIL: Backtick injection was not blocked"); Deno.exit(1); } else { console.log("PASS: Backtick injection blocked"); } // Test 5: $() injection should be blocked console.log("Test 5: $() injection in args"); const dollarPayload = "$(touch " + markerFile + ")"; spawnSync("echo", [dollarPayload], { shell: true }); if (fs.existsSync(markerFile)) { console.log("FAIL: $() injection was not blocked"); Deno.exit(1); } else { console.log("PASS: $() injection blocked"); } // Test 6: Normal functionality still works - args are passed correctly console.log("Test 6: Normal args work correctly"); const result = spawnSync("echo", ["hello", "world"], { shell: true, encoding: "utf-8", }); if (result.stdout?.trim() === "hello world") { console.log("PASS: Normal args work"); } else { console.log("FAIL: Normal args broken, got:", result.stdout?.trim()); Deno.exit(1); } // Test 7: Args with spaces are preserved console.log("Test 7: Args with spaces preserved"); const result2 = spawnSync("echo", ["hello world"], { shell: true, encoding: "utf-8", }); if (result2.stdout?.trim() === "hello world") { console.log("PASS: Args with spaces work"); } else { console.log("FAIL: Args with spaces broken, got:", result2.stdout?.trim()); Deno.exit(1); } // Test 8: Shell features work when using string command (no args) console.log("Test 8: Shell features work with string command"); const result3 = spawnSync("echo foo | cat", { shell: true, encoding: "utf-8" }); if (result3.stdout?.trim() === "foo") { console.log("PASS: Shell features work"); } else { console.log("FAIL: Shell features broken, got:", result3.stdout?.trim()); Deno.exit(1); } // Test 9: Async spawn also escapes args console.log("Test 9: Async spawn escapes args"); await new Promise<void>((resolve) => { const child = spawn("echo", [`dummy; touch ${markerFile}`], { shell: true }); child.on("close", () => { if (fs.existsSync(markerFile)) { console.log("FAIL: Async spawn injection was not blocked"); Deno.exit(1); } else { console.log("PASS: Async spawn injection blocked"); } resolve(); }); }); // Test 10: Backtick + $VAR injection should be blocked (CVE-2026-27190 bypass) // When an arg contains both $VAR and backticks, the re-quoting logic must not // use double quotes (which allow backtick command substitution in POSIX sh). console.log("Test 10: Backtick + $VAR injection in args"); const backtickVarPayload = "`touch " + markerFile + "`$HOME"; spawnSync(Deno.execPath(), ["eval", "''", backtickVarPayload], { shell: true, env: { PATH: "/usr/bin:/bin", HOME: "/tmp" }, }); if (fs.existsSync(markerFile)) { console.log("FAIL: Backtick + $VAR injection was not blocked"); Deno.exit(1); } else { console.log("PASS: Backtick + $VAR injection blocked"); } // Test 11: $() + $VAR injection should be blocked (CVE-2026-27190 bypass) // Same issue with $() command substitution syntax combined with $VAR. console.log("Test 11: $() + $VAR injection in args"); const subshellVarPayload = "$(touch " + markerFile + ")$HOME"; spawnSync(Deno.execPath(), ["eval", "''", subshellVarPayload], { shell: true, env: { PATH: "/usr/bin:/bin", HOME: "/tmp" }, }); if (fs.existsSync(markerFile)) { console.log("FAIL: $() + $VAR injection was not blocked"); Deno.exit(1); } else { console.log("PASS: $() + $VAR injection blocked"); } // Test 12: Backslash-quote breakout + $VAR injection should be blocked // A \" in the argument can break out of the double-quote re-quoting: // .replace(/"/g, '\\"') turns \" into \\", and the shell interprets // \\ as escaped backslash then " as END QUOTE. The # swallows the // trailing wrapper quote as a comment. console.log("Test 12: Backslash-quote breakout + $VAR injection in args"); const bsQuotePayload = '$HOME\\";touch ' + markerFile + ";#"; spawnSync(Deno.execPath(), ["eval", "''", bsQuotePayload], { shell: true, env: { PATH: "/usr/bin:/bin", HOME: "/tmp" }, }); if (fs.existsSync(markerFile)) { console.log("FAIL: Backslash-quote breakout injection was not blocked"); Deno.exit(1); } else { console.log("PASS: Backslash-quote breakout injection blocked"); } // Test 13: Backslash-quote breakout with pipe injection console.log("Test 13: Backslash-quote breakout + pipe injection in args"); const bsPipePayload = '$HOME\\"|touch ' + markerFile + " #"; spawnSync(Deno.execPath(), ["eval", "''", bsPipePayload], { shell: true, env: { PATH: "/usr/bin:/bin", HOME: "/tmp" }, }); if (fs.existsSync(markerFile)) { console.log("FAIL: Backslash-quote pipe injection was not blocked"); Deno.exit(1); } else { console.log("PASS: Backslash-quote pipe injection blocked"); } console.log("All tests passed!");