# Hardcoded Creds

> 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.

- Skill: `purpleailab/hardcoded-creds` (Agent Skill)
- Install (CLI): `npx skillmds@latest add purpleailab/hardcoded-creds`
- Raw SKILL.md: https://api.skillmd.com/api/skills/purpleailab/hardcoded-creds/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: purpleailab (https://skillmd.com/u/purpleailab)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/purpleailab/hardcoded-creds

---


# Hardcoded Credentials

> Extracted 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.

## Prerequisites

- Extracted rootfs from `binwalk-extract` skill (path: `/tmp/squashfs_root` or similar).
- Tools: `john`, `hashcat`, `openssl`, `trufflehog`, `strings`, standard GNU utilities.

```bash
ROOT=/tmp/squashfs_root   # set once; referenced throughout
```

---

## Phase 1 — Account Databases

### /etc/passwd + /etc/shadow

```bash
# 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-specific user configs

```bash
# 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
```

---

## Phase 2 — SSH / Dropbear Keys

```bash
# 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
```

---

## Phase 3 — Web Interface Credentials

```bash
# 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
```

---

## Phase 4 — Strings-Based Secret Sweep

```bash
# 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 filesystem scan

```bash
# 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)]"
```

---

## Phase 5 — MQTT / Cloud / IoT-Platform Tokens

```bash
# 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
```

---

## Phase 6 — Default Credential Matrix Cross-Reference

```bash
# 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
```

### Known vendor default credential matrix

| 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 |

---

## Phase 7 — TLS Certificate Private Keys

```bash
# 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 device
```

---

## Evidence

```bash
EVDIR=/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",
#     },
# )
```

## OPSEC Notes

- Cracked shadow credentials are valid on every unit of the same firmware version
  (until the vendor patches). Document model + firmware version precisely.
- Private key extraction enables MitM against the vendor's cloud API for all devices
  using that firmware. Escalate finding severity accordingly.
- MQTT tokens with `#` wildcard subscriptions allow full telemetry interception across
  the vendor's entire fleet — flag as Critical if RoE scope allows cloud testing.

## References

- SecLists default credentials: `https://github.com/danielmiessler/SecLists/tree/master/Passwords/Default-Credentials`
- RouterSploit modules: `https://github.com/threat9/routersploit`
- trufflehog: `https://github.com/trufflesecurity/trufflehog`
- Hashcat hash-modes: `https://hashcat.net/wiki/doku.php?id=hashcat`

