Privilege Escalation on Digitalocean
How Privilege Escalation Manifests in Digitalocean
Privilege escalation in Digitalocean environments often stems from improper role-based access control (RBAC) configurations and overly permissive API tokens. Digitalocean's cloud-native architecture creates unique escalation vectors that differ from traditional on-premises setups.
One common pattern involves Digitalocean Spaces (object storage) where developers inadvertently grant write permissions to entire teams instead of specific service accounts. For example:
// Vulnerable: Overly permissive Spaces policy
const spacesClient = new SpacesClient({
accessKeyId: process.env.SPACES_ACCESS_KEY,
secretAccessKey: process.env.SPACES_SECRET_KEY,
region: 'nyc3'
});
// Anyone with this token can modify any object
const uploadFile = async (bucket, filePath, data) => {
await spacesClient.putObject({
Bucket: bucket,
Key: filePath,
Body: data,
ACL: 'public-read' // Dangerous default
});
};
Another Digitalocean-specific escalation vector occurs through Kubernetes clusters. When developers create clusters with overly permissive service accounts, attackers who compromise one pod can escalate to cluster-admin:
# Vulnerable: Cluster-admin role granted to default service account
apiVersion: v1
kind: ServiceAccount
metadata:
name: default
namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: permissive-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: default
namespace: default
Digitalocean's API tokens present another escalation risk. When developers use personal access tokens with broad scopes instead of project-specific tokens:
# Vulnerable: Personal access token with all scopes
curl -X GET "https://api.digitalocean.com/v2/droplets" \
-H "Authorization: Bearer $DO_TOKEN_WITH_ALL_SCOPES"Database connections in Digitalocean environments often contain hardcoded credentials or use database users with excessive privileges:
# Vulnerable: Database user with admin privileges
import psycopg2
def get_db_connection():
return psycopg2.connect(
host='db-nyc3-01.digitaloceanspaces.com',
database='app_db',
user='admin_user', # Should be limited user
password='password123' # Hardcoded credential
)
Digitalocean-Specific Detection
Detecting privilege escalation in Digitalocean requires examining both configuration files and runtime behavior. Digitalocean's API-first architecture means many escalation paths can be discovered through automated scanning.
middleBrick's black-box scanning approach is particularly effective for Digitalocean environments because it tests the actual attack surface without requiring credentials. The scanner examines endpoints for authentication bypass attempts and privilege escalation patterns specific to Digitalocean's API structure.
For Digitalocean Spaces, look for these indicators of privilege escalation risk:
| Indicator | Risk Level | Detection Method |
|---|---|---|
| Public ACL on sensitive objects | High | Scan bucket permissions via API |
| Write permissions on entire buckets | Critical | Test write access to various paths |
| Service accounts with admin roles | High | Check IAM role bindings |
middleBrick's scanning process for Digitalocean environments includes:
# Scan a Digitalocean API endpoint
middlebrick scan https://api.digitalocean.com/v2/droplets \
--output json \
--category "Privilege Escalation"The scanner tests for common escalation patterns including:
- Authentication bypass attempts using predictable token patterns
- Role-based access control misconfigurations
- Service account privilege escalations
- Database connection string vulnerabilities
For Kubernetes clusters on Digitalocean, privilege escalation detection focuses on:
# Check for risky default configurations
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: dangerous-default
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["*"] # Full cluster access
Digitalocean's metadata service can also be a privilege escalation vector if not properly secured. Attackers who gain access to a droplet can query metadata.digitalocean.com for sensitive information:
# Test for metadata service exposure
curl -s http://169.254.169.254/metadata/v1/idDigitalocean-Specific Remediation
Remediating privilege escalation in Digitalocean requires a defense-in-depth approach using Digitalocean's native security features. The principle of least privilege should guide all configurations.
For Digitalocean Spaces, implement granular access controls:
// Secure: Principle of least privilege
const spacesClient = new SpacesClient({
accessKeyId: process.env.SPACES_READ_ONLY_KEY,
secretAccessKey: process.env.SPACES_READ_ONLY_SECRET,
region: 'nyc3'
});
// Specific bucket and path permissions
const uploadFile = async (bucket, filePath, data) => {
await spacesClient.putObject({
Bucket: bucket,
Key: filePath,
Body: data,
ACL: 'private', // Default to private
ContentType: 'application/octet-stream'
});
};
// Use presigned URLs for temporary access
const generatePresignedUrl = (bucket, filePath, expiresInSeconds = 300) => {
return spacesClient.getSignedUrl('getObject', {
Bucket: bucket,
Key: filePath,
Expires: expiresInSeconds
});
};
For Kubernetes on Digitalocean, implement proper RBAC with namespace isolation:
# Secure: Least privilege RBAC
apiVersion: v1
kind: ServiceAccount
metadata:
name: app-service
namespace: app-namespace
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: app-role
namespace: app-namespace
rules:
- apiGroups: [""]
resources: ["configmaps", "secrets"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: app-binding
namespace: app-namespace
subjects:
- kind: ServiceAccount
name: app-service
namespace: app-namespace
roleRef:
kind: Role
name: app-role
apiGroup: rbac.authorization.k8s.io
Digitalocean's API tokens should use the narrowest possible scope:
# Create scoped tokens
# Read-only for droplets
digitalocean_token create \
--name "droplet-readonly" \
--scopes "read:droplets"
# Read-write for specific project
digitalocean_token create \
--name "project-rw" \
--scopes "write:projects" \
--project-id "your-project-id"
For database connections, use least-privileged database users and environment variables:
# Secure: Least privilege database access
import os
import psycopg2
from psycopg2.extras import RealDictCursor
def get_db_connection():
return psycopg2.connect(
host=os.getenv('DB_HOST'),
database=os.getenv('DB_NAME'),
user=os.getenv('DB_USER'), # Limited user
password=os.getenv('DB_PASSWORD'),
cursor_factory=RealDictCursor
)
def execute_query(query, params=()):
conn = get_db_connection()
try:
with conn.cursor() as cur:
cur.execute(query, params)
return cur.fetchall()
finally:
conn.close()
Digitalocean's metadata service should be disabled or firewalled when not needed:
# Disable metadata service access
ufw deny from 169.254.169.254 to any
# Or use Digitalocean's cloud firewall
doctl compute firewall create \
--name "no-metadata-access" \
--droplet-ids $(doctl compute droplet list -q) \
--inbound-rules "protocol:tcp,ports:80,address:169.254.169.254"