Dangling Dns on Digitalocean
How Dangling Dns Manifests in Digitalocean
Dangling DNS in DigitalOcean occurs when DNS records point to resources that no longer exist or are no longer controlled by the owner. This creates a significant security risk where attackers can claim abandoned resources and intercept traffic intended for legitimate services.
In DigitalOcean's infrastructure, dangling DNS commonly appears in these specific scenarios:
- Load Balancer Recycling: When a Load Balancer is destroyed but DNS records aren't updated, attackers can create a new Load Balancer with the same IP and intercept traffic
- App Platform De-provisioning: DigitalOcean App Platform assigns random subdomains (app-name.app-name-
.ondigitalocean.app) that may be reassigned when apps are deleted - Spaces Object Storage: When Spaces buckets are deleted, their endpoints become available for others to claim
- Kubernetes Service Exposure: LoadBalancer services in DOKS can expose public IPs that get recycled
Here's a common dangling DNS scenario in DigitalOcean:
package main
import (
"fmt"
"net/http"
"os"
)
func main() {
// Attacker creates a new app after original is deleted
app, err := appplatform.CreateApp(&appplatform.CreateAppRequest{
Name: "vulnerable-app",
Region: "nyc",
Spec: appplatform.AppSpec{
ServiceSpec: &appplatform.ServiceSpec{
GitRepo: &appplatform.GitRepo{
RepoCloneURL: "https://github.com/attacker/vulnerable-app.git",
Branch: "main",
},
},
},
})
if err != nil {
fmt.Fprintf(os.Stderr, "Error creating app: %v\n", err)
return
}
fmt.Printf("App created at: %s\n", app.AppURI)
// Original app's DNS might still point here
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// Capture credentials, session tokens, etc.
w.Write([]byte("Service unavailable"))
})
http.ListenAndServe(":8080", nil)
}
The DigitalOcean API doesn't automatically clean up DNS records when resources are destroyed, making this a persistent vulnerability. Attackers can monitor for recently deleted resources and rapidly provision new ones to claim the IP addresses or subdomains.
Digitalocean-Specific Detection
Detecting dangling DNS in DigitalOcean requires both passive scanning and active verification. Here are DigitalOcean-specific detection methods:
DigitalOcean API Resource Monitoring
Use the DigitalOcean API to track resource lifecycle and identify potential dangling references:
package main
import (
"context"
"fmt"
"log"
"github.com/digitalocean/godo"
)
func checkDanglingResources(ctx context.Context, client *godo.Client) {
// Check for recently deleted resources that might have dangling DNS
opt := &godo.ListOptions{PerPage: 100}
// Check Load Balancers
lbs, _, err := client.LoadBalancers.List(ctx, opt)
if err != nil {
log.Fatal(err)
}
for _, lb := range lbs {
// Check if Load Balancer has DNS records pointing to it
if lb.Status == "active" {
// Verify DNS records exist for this IP
records, err := client.Domains.ListRecords(ctx, lb.Region, &godo.ListOptions{})
if err != nil {
continue
}
for _, record := range records {
if record.Type == "A" || record.Type == "CNAME" {
// Check if record points to this Load Balancer
if record.Data == lb.IP || record.Data == lb.ID {
log.Printf("Potential dangling DNS: %s -> %s\n", record.Name, record.Data)
}
}
}
}
}
}
Using middleBrick for Automated Detection
middleBrick's black-box scanning approach is particularly effective for detecting dangling DNS in DigitalOcean environments. The scanner tests the unauthenticated attack surface without requiring credentials:
# Install middleBrick CLI
npm install -g middlebrick
# Scan a DigitalOcean App Platform URL
middlebrick scan myapp-nyc.ondigitalocean.app
# Scan with specific focus on DigitalOcean services
middlebrick scan --service digitalocean myapp-nyc.ondigitalocean.app
middleBrick performs these DigitalOcean-specific checks:
| Check Type | DigitalOcean Context | Detection Method |
|---|---|---|
| DNS Resolution | App Platform subdomains | Resolves myapp-nyc.ondigitalocean.app to current IP |
| Service Identification | DigitalOcean-specific headers | Looks for DO-specific server headers and response patterns |
| Resource Availability | Load Balancer recycling | Attempts to claim recently freed IPs |
Automated Monitoring Script
Set up continuous monitoring for dangling DNS in your DigitalOcean account:
#!/bin/bash
# Monitor for dangling DNS in DigitalOcean
# Run this as a cron job to detect issues
DIGITALOCEAN_TOKEN="your-token-here"
check_dangling() {
# Get list of all domains
curl -s -X GET "https://api.digitalocean.com/v2/domains" \
-H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \
-H "Content-Type: application/json" > domains.json
# Check each domain for potentially dangling records
for domain in $(jq -r '.[].name' domains.json); do
echo "Checking $domain for dangling records..."
# Get all records for this domain
curl -s -X GET "https://api.digitalocean.com/v2/domains/$domain/records" \
-H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \
-H "Content-Type: application/json" > records.json
# Check for records pointing to potentially recycled resources
for record in $(jq -r '.domain_records[] | select(.type == "A" or .type == "CNAME") | @base64' records.json); do
_jq() {
echo ${record} | base64 --decode | jq -r ${1}
}
record_name=$(_jq '.name')
record_data=$(_jq '.data')
record_type=$(_jq '.type')
echo "Checking: $record_type $record_name -> $record_data"
done
done
}
check_dangling
Digitalocean-Specific Remediation
Remediating dangling DNS in DigitalOcean requires both preventive measures and active cleanup procedures. Here are DigitalOcean-specific remediation strategies:
DigitalOcean API-Based Cleanup
Use the DigitalOcean API to programmatically clean up DNS records when resources are destroyed:
package main
import (
"context"
"fmt"
"log"
"github.com/digitalocean/godo"
)
// Clean up DNS records when a resource is destroyed
func cleanupDNSEntries(ctx context.Context, client *godo.Client, resourceID string) error {
// Get resource information to identify associated DNS records
resource, _, err := client.LoadBalancers.Get(ctx, resourceID)
if err != nil {
return err
}
// Find all domains that might have records pointing to this resource
domains, _, err := client.Domains.List(ctx, &godo.ListOptions{PerPage: 100})
if err != nil {
return err
}
for _, domain := range domains {
records, _, err := client.Domains.ListRecords(ctx, domain.Name, &godo.ListOptions{})
if err != nil {
continue
}
for _, record := range records {
// Check if record points to this resource
if record.Type == "A" && record.Data == resource.IP ||
record.Type == "CNAME" && record.Data == resource.ID {
// Delete the dangling record
_, err := client.Domains.DeleteRecord(ctx, domain.Name, record.ID)
if err != nil {
log.Printf("Failed to delete record %s: %v", record.Name, err)
} else {
log.Printf("Deleted dangling record: %s.%s", record.Name, domain.Name)
}
}
}
}
return nil
}
// Call this when destroying a resource
func destroyLoadBalancer(ctx context.Context, client *godo.Client, lbID string) error {
// First clean up associated DNS records
err := cleanupDNSEntries(ctx, client, lbID)
if err != nil {
log.Printf("DNS cleanup error: %v", err)
}
// Then destroy the resource
_, err = client.LoadBalancers.Delete(ctx, lbID)
return err
}
DigitalOcean App Platform Safeguards
Implement safeguards when using DigitalOcean App Platform to prevent dangling DNS:
# Terraform configuration for App Platform with DNS management
resource "digitalocean_app" "example" {
name = "my-secure-app"
spec {
service {
git {
repo_clone_url = "https://github.com/myorg/myapp.git"
branch = "main"
}
dockerrun {
github {
repo = "myorg/myapp"
}
}
}
}
// Enable automatic cleanup of DNS records
lifecycle {
prevent_destroy = false
ignore_changes = [created_at, updated_at]
}
}
resource "digitalocean_domain" "example" {
name = "myapp.example.com"
}
resource "digitalocean_record" "app" {
domain = digitalocean_domain.example.name
name = ""
type = "CNAME"
value = digitalocean_app.example.app_uri
// Add lifecycle policy to clean up when app is destroyed
lifecycle {
create_before_destroy = true
}
}
# Custom script to monitor and clean dangling DNS
#!/bin/bash
# Monitor DigitalOcean App Platform for dangling DNS
monitor_app_platform() {
# Get list of all apps
curl -s -X GET "https://api.digitalocean.com/v2/apps" \
-H "Authorization: Bearer $DIGITALOCEAN_TOKEN" > apps.json
# Check each app's DNS status
for app in $(jq -r '.[].id' apps.json); do
echo "Checking app: $app"
# Get app details
curl -s -X GET "https://api.digitalocean.com/v2/apps/$app" \
-H "Authorization: Bearer $DIGITALOCEAN_TOKEN" > app_details.json
done
}
# Automated cleanup script
cleanup_dangling() {
# Check for apps that were recently deleted
# This would be part of a larger monitoring system
find /var/log/digitalocean -name "app_deleted_*.log" -mtime -1 | while read log; do
echo "Processing deleted app from: $log"
# Extract app information and clean up DNS
done
}
middleBrick Integration for Continuous Monitoring
Integrate middleBrick into your DigitalOcean workflow for ongoing dangling DNS detection:
# GitHub Action for DigitalOcean API security
name: DigitalOcean Security Scan
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
schedule:
- cron: '0 2 * * *' # Daily at 2 AM
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run middleBrick scan
run: |
npm install -g middlebrick
middlebrick scan myapp-nyc.ondigitalocean.app \
--service digitalocean \
--output json > security-report.json
- name: Check for dangling DNS
run: |
# Parse middleBrick report for DNS issues
ISSUES=$(jq '.findings[] | select(.category == "DNS" and .severity == "high")' security-report.json)
if [ -n "$ISSUES" ]; then
echo "Dangling DNS detected!"
exit 1
fi
- name: Upload report
uses: actions/upload-artifact@v3
if: always()
with:
name: security-report
path: security-report.json
This integration ensures that any dangling DNS issues in your DigitalOcean environment are caught early in the development lifecycle, preventing production security incidents.