$npx -y skills add utkusen/sast-skills --skill sast-rceDetect Remote Code Execution (RCE) vulnerabilities in a codebase using a three-phase approach: recon (find dangerous execution sinks), batched verify (trace user input to sinks in parallel subagents, 3 sinks each), and merge (consolidate batch results). Covers OS command injectio
| 1 | # Remote Code Execution (RCE) Detection |
| 2 | |
| 3 | You are performing a focused security assessment to find Remote Code Execution vulnerabilities in a codebase. This skill uses a three-phase approach with subagents: **recon** (find dangerous execution sinks), **batched verify** (trace whether user-supplied input reaches each sink in parallel batches of 3), and **merge** (consolidate batch results into the final report). |
| 4 | |
| 5 | **Prerequisites**: `sast/architecture.md` must exist. Run the analysis skill first if it doesn't. |
| 6 | |
| 7 | --- |
| 8 | |
| 9 | ## What is Remote Code Execution |
| 10 | |
| 11 | Remote Code Execution (RCE) occurs when an attacker can cause the application to execute arbitrary OS commands or application-level code that they control. This is typically the highest-severity vulnerability class, often resulting in complete server compromise. |
| 12 | |
| 13 | RCE arises from three primary root causes: |
| 14 | |
| 15 | 1. **OS Command Injection**: User input is embedded unsafely into an OS command string, allowing shell metacharacters to inject additional commands. |
| 16 | 2. **Code Injection (eval-like)**: User input is passed to functions that interpret it as executable code (`eval`, `exec`, `Function()`, etc.). |
| 17 | 3. **Unsafe Deserialization**: User-supplied serialized data is deserialized using a gadget-prone deserializer, triggering arbitrary code execution via crafted payloads. |
| 18 | |
| 19 | ### What RCE IS |
| 20 | |
| 21 | - Passing user input directly or indirectly into OS command execution functions with shell interpretation enabled |
| 22 | - Using `eval()`, `exec()`, `Function()`, or equivalent constructs with user-controlled strings |
| 23 | - Deserializing user-supplied bytes/strings with inherently unsafe deserializers (pickle, PHP unserialize, Java native serialization, Ruby Marshal, etc.) |
| 24 | - Using `yaml.load()` without a safe loader on user-supplied content |
| 25 | - Dynamic `require()`/`import()` with user-controlled module paths |
| 26 | - PHP file inclusion (`include`/`require`) with user-controlled paths |
| 27 | |
| 28 | ### What RCE is NOT |
| 29 | |
| 30 | Do not flag these as RCE: |
| 31 | |
| 32 | - **SSRF**: Making HTTP requests to attacker-controlled URLs — different vulnerability class (no code execution) |
| 33 | - **Path Traversal**: Reading/writing arbitrary files — separate class (unless the read file is then executed/deserialized) |
| 34 | - **SSTI**: Template injection via template engines — a separate though related class; flag as SSTI, not RCE |
| 35 | - **XSS**: JavaScript execution in a victim's browser — client-side only, not server-side RCE |
| 36 | - **SQL Injection**: Injecting into database queries — different class (even if `xp_cmdshell` can lead to OS commands, flag it as SQLi) |
| 37 | - **Safe subprocess list-form calls**: `subprocess.run(["ls", user_arg])` with a list and no `shell=True` — arguments are passed directly to the OS without shell expansion; not vulnerable to command injection |
| 38 | - **Safe deserialization**: `json.loads()`, `yaml.safe_load()`, `xml.etree.ElementTree.parse()` — these formats have no code execution semantics |
| 39 | |
| 40 | ### Patterns That Prevent RCE |
| 41 | |
| 42 | When you see these patterns, the code is likely **not vulnerable**: |
| 43 | |
| 44 | **1. Subprocess list form without shell interpretation** |
| 45 | ``` |
| 46 | # Python — list args, no shell=True |
| 47 | subprocess.run(["convert", "-resize", size, input_file, output_file]) |
| 48 | subprocess.Popen(["git", "clone", repo_url]) |
| 49 | |
| 50 | # Node.js — spawn with separate args (no shell) |
| 51 | child_process.spawn("ffmpeg", ["-i", inputFile, outputFile]) |
| 52 | |
| 53 | # Java — ProcessBuilder with list |
| 54 | new ProcessBuilder("ls", "-la", dir).start() |
| 55 | |
| 56 | # Ruby — system() with multiple args (not a single interpolated string) |
| 57 | system("ffmpeg", "-i", "input.mp4", "-f", format, "output") |
| 58 | ``` |
| 59 | |
| 60 | **2. Safe deserialization formats** |
| 61 | ``` |
| 62 | # Python — JSON instead of pickle |
| 63 | import json |
| 64 | data = json.loads(user_input) # no code execution semantics |
| 65 | |
| 66 | # Python — safe YAML loader |
| 67 | import yaml |
| 68 | data = yaml.safe_load(user_input) # restricts to basic types only |
| 69 | |
| 70 | # Java — Jackson without enableDefaultTyping, with concrete target type |
| 71 | ObjectMapper mapper = new ObjectMapper(); |
| 72 | MyClass obj = mapper.readValue(json, MyClass.class); # safe |
| 73 | ``` |
| 74 | |
| 75 | **3. Strict allowlist before command construction** |
| 76 | ``` |
| 77 | # Python — allowlist for dynamic arguments |
| 78 | ALLOWED_FORMATS = {"png", "jpg", "webp"} |
| 79 | if fmt not in ALLOWED_FORMATS: |
| 80 | return abort(400) |
| 81 | subprocess.run(["convert", infile, f"output.{fmt}"]) |
| 82 | |
| 83 | # Node.js — allowlist for dynamic args |
| 84 | const ALLOWED_COMMANDS = ['ls', 'pwd']; |
| 85 | if (!ALLOWED_COMMANDS.includes(cmd)) return res.status(400).end(); |
| 86 | spawn(cmd, []); |
| 87 | ``` |
| 88 | |
| 89 | --- |
| 90 | |
| 91 | ## Vulnerable vs. Secure Examples |
| 92 | |
| 93 | ### OS Command Inje |