Systematic hunt for hardcoded credentials, API keys, certificates, and default passwords in extracted IoT firmware. Covers /etc/shadow and passwd parsing, busybox httpd configs, telnet/dropbear stanzas, MQTT/cloud API key extraction, and cross-referencing against known default-credential databases.
63
75%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Critical
Do not install without reviewing
Fix and improve this skill with Tessl
tessl review fix ./packages/decepticon/decepticon/skills/standard/iot/hardcoded-creds/SKILL.mdExtracted rootfs often contains plaintext or weakly hashed credentials, embedded TLS private keys, cloud API tokens, and hardcoded admin accounts. This playbook covers the full triage path from raw rootfs to validated cred.
binwalk-extract skill (path: /tmp/squashfs_root or similar).john, hashcat, openssl, trufflehog, strings, standard GNU utilities.ROOT=/tmp/squashfs_root # set once; referenced throughout# Check for non-standard shells (telnetd, /bin/sh) and disabled-but-present accounts
grep -v "nologin\|false" "$ROOT/etc/passwd"
# Extract hashed passwords
cat "$ROOT/etc/shadow" 2>/dev/null || echo "shadow not present"
# Identify hash types
# $1$ = MD5, $5$ = SHA-256, $6$ = SHA-512, $y$ = yescrypt, no-$ = DES
awk -F: '{print $1, $2}' "$ROOT/etc/shadow" | grep -v '^\*\|^!\|^:$'
# Crack with hashcat (shadow format)
hashcat -m 1800 "$ROOT/etc/shadow" /usr/share/wordlists/rockyou.txt # $6$ SHA-512
hashcat -m 500 "$ROOT/etc/shadow" /usr/share/wordlists/rockyou.txt # $1$ MD5
hashcat -m 1500 "$ROOT/etc/shadow" /usr/share/wordlists/rockyou.txt # DES
# john fallback (handles multi-type auto)
unshadow "$ROOT/etc/passwd" "$ROOT/etc/shadow" > /tmp/unshadowed.txt
john /tmp/unshadowed.txt --wordlist=/usr/share/wordlists/rockyou.txt
john /tmp/unshadowed.txt --show# BusyBox httpd uses /etc/httpd.conf for auth
find "$ROOT/etc" -name "httpd.conf" -o -name ".htpasswd" | xargs cat 2>/dev/null
# BusyBox udhcpd / telnetd startup stanzas
grep -r "telnetd\|login\|password" "$ROOT/etc/inittab" "$ROOT/etc/init.d/" \
"$ROOT/etc/rc.d/" 2>/dev/null | grep -v "^Binary"
# BusyBox shadow format check (some use MD5 without $1$ prefix)
grep -E '^[^:]+:[^:!*]{3,}' "$ROOT/etc/shadow" 2>/dev/null# Host keys (baked into firmware = same key across all units of same model)
find "$ROOT" -name "dropbear_*_host_key" -o -name "ssh_host_*_key" 2>/dev/null
# Convert Dropbear key to OpenSSH PEM for use
dropbearconvert dropbear openssh "$ROOT/etc/dropbear/dropbear_rsa_host_key" \
/tmp/host_rsa.pem
openssl rsa -in /tmp/host_rsa.pem -text -noout | head -20
# Authorized keys embedded for backdoor accounts
find "$ROOT" -path "*/.ssh/authorized_keys" | xargs cat 2>/dev/null
# Check if root has a passwordless authorized_key → instant SSH root on any unit
grep -r "authorized_keys\|ssh-rsa\|ecdsa-sha2" "$ROOT" 2>/dev/null | head -10# CGI / Lua / PHP / lighttpd configs
find "$ROOT" -name "*.lua" -o -name "*.cgi" -o -name "*.php" | \
xargs grep -l "password\|passwd\|credential\|secret\|admin" 2>/dev/null | head -20
# Plaintext credentials in web config files
grep -rn "admin\|password\|passwd\|secret\|token" \
"$ROOT/etc/lighttpd/" "$ROOT/etc/nginx/" "$ROOT/etc/httpd/" 2>/dev/null
# Hardcoded credentials in Lua/CGI scripts
grep -rn '"admin"\|"root"\|"1234"\|"password"' "$ROOT/usr/lib/lua/" \
"$ROOT/www/" "$ROOT/usr/share/www/" 2>/dev/null | head -30# Broad strings dump: capture all printable sequences ≥ 8 chars
find "$ROOT" -type f -executable | while read f; do
strings "$f" 2>/dev/null
done > /tmp/all_strings.txt
# Filter credential-like patterns
grep -iE 'password|passwd|secret|api.?key|token|credential|auth' /tmp/all_strings.txt \
| grep -vE '^(#|//|<!|<html|<head)' | sort -u | head -100
# AWS / GCP / Azure key patterns
grep -E 'AKIA[0-9A-Z]{16}|AIza[0-9A-Za-z_-]{35}|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}' \
/tmp/all_strings.txt
# JWT tokens
grep -oP 'eyJ[A-Za-z0-9+/=]+\.[A-Za-z0-9+/=]+\.[A-Za-z0-9+/=_-]+' \
/tmp/all_strings.txt | head -10# trufflehog works on directories — point at rootfs
trufflehog filesystem "$ROOT" --only-verified 2>/dev/null
# Without --only-verified for broader sweep:
trufflehog filesystem "$ROOT" --json 2>/dev/null | \
python3 -c "import sys,json; [print(json.dumps(r, indent=2)) for r in map(json.loads, sys.stdin)]"# MQTT broker credentials
grep -rn "mqtt\|MQTT\|mosquitto" "$ROOT" --include="*.conf" --include="*.cfg" \
--include="*.json" --include="*.lua" 2>/dev/null | head -30
# Cloud platform tokens (Tuya, HomeKit, AWS IoT, Azure IoT Hub)
grep -rn "tuya\|homekit\|awsiot\|azure.*iot\|device_key\|productKey\|deviceSecret" \
"$ROOT" --include="*.json" --include="*.conf" 2>/dev/null | head -30
# PCB-serial-derived default PSK (check if device ID used as secret)
grep -rn "serial\|SerialNum\|MAC\|mac_addr" "$ROOT/etc/config/" 2>/dev/null | head -20# Extract all discovered usernames
USERS=$(grep -v 'nologin\|false\|halt\|sync\|shutdown' "$ROOT/etc/passwd" | \
awk -F: '{print $1}' | tr '\n' ' ')
echo "Accounts found: $USERS"
# Cross-reference against routersploit / seclists default creds
# RouterSploit default cred wordlist path (if installed):
DFLT=/usr/share/routersploit/resources/wordlists/default_passwords.txt
[ -f "$DFLT" ] && echo "Default wordlist available: $DFLT"
# SecLists IoT default creds
SECLIST=/usr/share/seclists/Passwords/Default-Credentials/default-passwords.csv
[ -f "$SECLIST" ] && grep -iE 'admin|root|user' "$SECLIST" | head -20| Vendor | Username | Password | Protocol |
|---|---|---|---|
| TP-Link (pre-2022) | admin | admin | HTTP / Telnet |
| Netgear | admin | password | HTTP |
| D-Link | admin | (blank) | HTTP |
| Asus (stock) | admin | admin | HTTP |
| MikroTik | admin | (blank) | SSH / Winbox |
| Hikvision | admin | 12345 | RTSP / HTTP |
| Dahua | admin | admin | HTTP |
| Ubiquiti AirOS | ubnt | ubnt | SSH |
| Western Digital MyCloud | admin | (blank) | HTTP |
| Seagate NAS | admin | admin | HTTP |
# Find PEM-encoded private keys
grep -rl "BEGIN.*PRIVATE KEY\|BEGIN RSA PRIVATE KEY" "$ROOT" 2>/dev/null
# Find DER-format keys (binary — look for SEQUENCE headers)
find "$ROOT" -name "*.der" -o -name "*.key" -o -name "*.pem" | while read f; do
openssl rsa -in "$f" -text -noout 2>/dev/null | head -3 && echo "FILE: $f"
done
# Verify if vendor CA cert is self-signed (same key on all units = MitM vector)
find "$ROOT" -name "*.crt" -o -name "*.pem" | while read f; do
openssl x509 -in "$f" -noout -subject -issuer 2>/dev/null && echo " FILE: $f"
done
# Check if the private key matches the certificate
openssl x509 -noout -modulus -in "$ROOT/etc/ssl/server.crt" | openssl md5
openssl rsa -noout -modulus -in "$ROOT/etc/ssl/server.key" | openssl md5
# Matching MD5 = key pair is valid and present on deviceEVDIR=/workspace/evidence/iot/<target>/credentials
mkdir -p "$EVDIR"
# Save cred findings
john /tmp/unshadowed.txt --show > "$EVDIR/cracked_passwords.txt"
cp /tmp/all_strings.txt "$EVDIR/strings_all.txt"
# Record each finding as a credential node
# kg_add_node(
# kind="credential",
# label="Hardcoded admin credential in /etc/shadow",
# props={
# "key": "iot-cred::<target>::admin",
# "secret_type": "unix_password",
# "username": "admin",
# "hash": "<hash>",
# "plaintext": "<if cracked>",
# "source": "firmware /etc/shadow",
# },
# )# wildcard subscriptions allow full telemetry interception across
the vendor's entire fleet — flag as Critical if RoE scope allows cloud testing.https://github.com/danielmiessler/SecLists/tree/master/Passwords/Default-Credentialshttps://github.com/threat9/routersploithttps://github.com/trufflesecurity/trufflehoghttps://hashcat.net/wiki/doku.php?id=hashcat31e1c8e
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.