Subdomain Enumeration and Security: A Developer's Guide
A practical Fusebox guide to subdomain enumeration and security.
Subdomain Enumeration and Security: A Developer's Guide
Subdomains are the forgotten attack surface of the web. While companies focus on securing their main domain, forgotten subdomains often run outdated software, expose internal tools, or leak sensitive information. In 2020, a major tech company's acquisition was leaked through a carelessly configured staging subdomain. Understanding subdomain enumeration is crucial for both security professionals and developers.
Why Subdomain Security Matters
Organizations typically have dozens or even hundreds of subdomains—from staging.example.com to jenkins.internal.example.com. Each represents a potential entry point. Security researchers routinely find exposed admin panels, development environments with debug mode enabled, and forgotten services running vulnerable software on subdomains.
Subdomain Discovery Engine
Comprehensive subdomain enumeration tool:
// Subdomain Discovery Engine
class SubdomainDiscovery {
constructor(domain) {
this.domain = domain;
this.subdomains = new Set();
this.results = {
found: [],
active: [],
technologies: {},
vulnerabilities: []
};
}
// Passive enumeration using multiple sources
async passiveEnumeration() {
console.log(`Starting passive enumeration for ${this.domain}`);
// DNS methods
await this.checkCommonSubdomains();
await this.checkCertificateTransparency();
await this.checkDNSRecords();
// Web-based methods
await this.checkSearchEngines();
await this.checkWaybackMachine();
// API-based methods
await this.checkSecurityTrails();
return this.results;
}
// Check common subdomain patterns
async checkCommonSubdomains() {
const commonSubdomains = [
'www', 'mail', 'ftp', 'localhost', 'webmail', 'smtp', 'pop', 'ns1', 'ns2',
'blog', 'dev', 'staging', 'api', 'app', 'admin', 'test', 'portal', 'secure',
'vpn', 'cdn', 'cloud', 'git', 'jenkins', 'gitlab', 'jira', 'confluence',
'beta', 'demo', 'qa', 'uat', 'prod', 'stage', 'development', 'testing',
'internal', 'private', 'public', 'static', 'assets', 'images', 'media',
'upload', 'download', 'files', 'docs', 'documentation', 'help', 'support',
'customer', 'client', 'user', 'cpanel', 'webdisk', 'autodiscover',
'autoconfig', 'mx', 'mx1', 'mx2', 'email', 'direct', 'direct-connect',
'mobile', 'm', 'mdm', 'meet', 'conference', 'zoom', 'teams', 'slack'
];
console.log('Checking common subdomain patterns...');
for (const subdomain of commonSubdomains) {
const hostname = `${subdomain}.${this.domain}`;
// Check if subdomain resolves (browser-based check)
try {
// In browser, we can't directly do DNS lookups
// Instead, we'll try to load a resource
const testUrl = `https://${hostname}/favicon.ico`;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 3000);
try {
const response = await fetch(testUrl, {
mode: 'no-cors',
signal: controller.signal
});
clearTimeout(timeoutId);
// If we get here, the subdomain likely exists
this.addSubdomain(hostname, 'common-pattern');
} catch (error) {
// Failed to connect, subdomain might not exist
}
} catch (error) {
// Error in fetch setup
}
}
}
// Check Certificate Transparency logs
async checkCertificateTransparency() {
console.log('Checking Certificate Transparency logs...');
// Using crt.sh API (limited in browser due to CORS)
try {
// This would need a proxy or server-side component
const apiUrl = `https://crt.sh/?q=%.${this.domain}&output=json`;
// Simulated results for demonstration
const ctResults = [
`api.${this.domain}`,
`staging.${this.domain}`,
`dev.${this.domain}`,
`*.${this.domain}`,
`secure.${this.domain}`
];
ctResults.forEach(cert => {
if (cert.startsWith('*.')) {
// Wildcard cert indicates potential subdomains
this.addSubdomain(cert, 'certificate-wildcard');
} else if (cert.includes(this.domain)) {
this.addSubdomain(cert, 'certificate');
}
});
} catch (error) {
console.log('Certificate Transparency check failed:', error);
}
}
// Check DNS records for subdomain hints
async checkDNSRecords() {
console.log('Analyzing DNS records for subdomain hints...');
// In browser context, we can check certain records via APIs
const dnsChecks = {
MX: ['mail', 'smtp', 'mx', 'email'],
TXT: ['spf', 'dkim', 'dmarc', '_domainkey'],
SRV: ['_sip', '_xmpp', '_caldav', '_carddav', '_ldap'],
CAA: ['ca', 'issue', 'issuewild']
};
// These would reveal subdomains in SPF records, DKIM selectors, etc.
Object.entries(dnsChecks).forEach(([recordType, hints]) => {
hints.forEach(hint => {
const potential = `${hint}.${this.domain}`;
// Add as potential subdomain for further verification
this.addSubdomain(potential, `dns-${recordType}-hint`);
});
});
}
// Search engines (Google, Bing, etc.)
async checkSearchEngines() {
console.log('Checking search engines...');
// Google dork patterns
const searchPatterns = [
`site:${this.domain} -www`,
`site:*.${this.domain}`,
`inurl:${this.domain} -www`,
`"${this.domain}" -www -site:www.${this.domain}`
];
// In practice, you'd need to use search APIs or scraping
// Simulated results
const searchResults = [
`blog.${this.domain}`,
`shop.${this.domain}`,
`news.${this.domain}`,
`events.${this.domain}`
];
searchResults.forEach(subdomain => {
this.addSubdomain(subdomain, 'search-engine');
});
}
// Check Wayback Machine for historical subdomains
async checkWaybackMachine() {
console.log('Checking Wayback Machine archives...');
try {
// Wayback Machine CDX API
const waybackUrl = `https://web.archive.org/cdx/search/cdx?url=*.${this.domain}&output=json&fl=original&collapse=urlkey`;
// This would need CORS proxy in practice
// Simulated historical subdomains
const historicalSubdomains = [
`old.${this.domain}`,
`legacy.${this.domain}`,
`archive.${this.domain}`,
`2019.${this.domain}`,
`backup.${this.domain}`
];
historicalSubdomains.forEach(subdomain => {
this.addSubdomain(subdomain, 'wayback-machine');
});
} catch (error) {
console.log('Wayback Machine check failed:', error);
}
}
// Check SecurityTrails (requires API key)
async checkSecurityTrails() {
console.log('Checking SecurityTrails...');
// Would need API key and proxy
// Simulated results
const stResults = [
`internal.${this.domain}`,
`vpn.${this.domain}`,
`remote.${this.domain}`,
`jenkins.${this.domain}`
];
stResults.forEach(subdomain => {
this.addSubdomain(subdomain, 'securitytrails');
});
}
// Add subdomain to results
addSubdomain(subdomain, source) {
if (!this.subdomains.has(subdomain)) {
this.subdomains.add(subdomain);
this.results.found.push({
subdomain,
source,
timestamp: new Date().toISOString()
});
}
}
// Active verification of discovered subdomains
async verifySubdomains() {
console.log('Verifying discovered subdomains...');
for (const item of this.results.found) {
const subdomain = item.subdomain;
try {
// Try HTTPS first
const httpsCheck = await this.checkEndpoint(`https://${subdomain}`);
if (httpsCheck.active) {
this.results.active.push({
...item,
...httpsCheck
});
continue;
}
// Try HTTP
const httpCheck = await this.checkEndpoint(`http://${subdomain}`);
if (httpCheck.active) {
this.results.active.push({
...item,
...httpCheck
});
}
} catch (error) {
// Subdomain not accessible
}
}
return this.results.active;
}
// Check if endpoint is active
async checkEndpoint(url) {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);
const response = await fetch(url, {
method: 'HEAD',
mode: 'no-cors',
signal: controller.signal
});
clearTimeout(timeoutId);
return {
active: true,
url,
protocol: new URL(url).protocol,
// In no-cors mode, we can't access response details
accessible: true
};
} catch (error) {
return {
active: false,
error: error.message
};
}
}
// Technology detection on subdomains
async detectTechnologies() {
console.log('Detecting technologies on active subdomains...');
for (const subdomain of this.results.active) {
try {
const tech = await this.identifyTechnology(subdomain.url);
subdomain.technologies = tech;
// Group by technology
Object.entries(tech).forEach(([category, items]) => {
if (!this.results.technologies[category]) {
this.results.technologies[category] = {};
}
items.forEach(item => {
if (!this.results.technologies[category][item]) {
this.results.technologies[category][item] = [];
}
this.results.technologies[category][item].push(subdomain.subdomain);
});
});
} catch (error) {
subdomain.techError = error.message;
}
}
}
// Basic technology identification
async identifyTechnology(url) {
const technologies = {
servers: [],
frameworks: [],
cms: [],
analytics: [],
security: []
};
try {
// In real implementation, would analyze response headers and content
// Simulated detection based on common patterns
const subdomain = new URL(url).hostname.split('.')[0];
// Common technology patterns
if (subdomain.includes('jenkins')) technologies.frameworks.push('Jenkins');
if (subdomain.includes('gitlab')) technologies.frameworks.push('GitLab');
if (subdomain.includes('jira')) technologies.frameworks.push('JIRA');
if (subdomain.includes('wp') || subdomain.includes('wordpress')) technologies.cms.push('WordPress');
if (subdomain.includes('api')) technologies.frameworks.push('API Server');
if (subdomain.includes('cdn')) technologies.servers.push('CDN');
} catch (error) {
console.error('Technology detection error:', error);
}
return technologies;
}
// Security assessment of subdomains
async assessSecurity() {
console.log('Performing security assessment...');
for (const subdomain of this.results.active) {
const vulnerabilities = [];
// Check for common security issues
// 1. Development/staging environments exposed
if (subdomain.subdomain.match(/(dev|staging|test|qa|uat)/i)) {
vulnerabilities.push({
type: 'exposed-environment',
severity: 'medium',
description: 'Development/staging environment publicly accessible',
subdomain: subdomain.subdomain
});
}
// 2. Admin panels exposed
if (subdomain.subdomain.match(/(admin|manage|panel|cpanel|control)/i)) {
vulnerabilities.push({
type: 'exposed-admin',
severity: 'high',
description: 'Administrative interface potentially exposed',
subdomain: subdomain.subdomain
});
}
// 3. Internal tools exposed
if (subdomain.subdomain.match(/(jenkins|gitlab|jira|confluence|nexus|artifactory)/i)) {
vulnerabilities.push({
type: 'exposed-tools',
severity: 'high',
description: 'Internal development tools exposed',
subdomain: subdomain.subdomain
});
}
// 4. Backup/old versions
if (subdomain.subdomain.match(/(backup|old|archive|legacy|v1|2019|2020)/i)) {
vulnerabilities.push({
type: 'outdated-content',
severity: 'medium',
description: 'Potentially outdated or backup content exposed',
subdomain: subdomain.subdomain
});
}
// 5. VPN/Remote access
if (subdomain.subdomain.match(/(vpn|remote|rdp|ssh|ftp)/i)) {
vulnerabilities.push({
type: 'remote-access',
severity: 'critical',
description: 'Remote access service exposed',
subdomain: subdomain.subdomain
});
}
subdomain.vulnerabilities = vulnerabilities;
this.results.vulnerabilities.push(...vulnerabilities);
}
return this.results.vulnerabilities;
}
// Generate comprehensive report
generateReport() {
const report = {
domain: this.domain,
timestamp: new Date().toISOString(),
summary: {
totalFound: this.results.found.length,
totalActive: this.results.active.length,
vulnerabilities: {
critical: this.results.vulnerabilities.filter(v => v.severity === 'critical').length,
high: this.results.vulnerabilities.filter(v => v.severity === 'high').length,
medium: this.results.vulnerabilities.filter(v => v.severity === 'medium').length,
low: this.results.vulnerabilities.filter(v => v.severity === 'low').length
}
},
subdomains: this.results,
recommendations: this.generateRecommendations()
};
return report;
}
// Generate security recommendations
generateRecommendations() {
const recommendations = [];
if (this.results.vulnerabilities.some(v => v.type === 'exposed-environment')) {
recommendations.push({
priority: 'high',
issue: 'Development environments exposed',
action: 'Restrict access to development/staging environments using IP whitelisting or VPN'
});
}
if (this.results.vulnerabilities.some(v => v.type === 'exposed-admin')) {
recommendations.push({
priority: 'critical',
issue: 'Administrative interfaces exposed',
action: 'Implement strong authentication and IP restrictions for admin panels'
});
}
if (this.results.vulnerabilities.some(v => v.type === 'exposed-tools')) {
recommendations.push({
priority: 'critical',
issue: 'Internal tools publicly accessible',
action: 'Move internal tools behind VPN or implement strong authentication'
});
}
if (this.results.active.length > 20) {
recommendations.push({
priority: 'medium',
issue: 'Large attack surface',
action: 'Audit and decommission unnecessary subdomains'
});
}
recommendations.push({
priority: 'medium',
issue: 'Subdomain management',
action: 'Implement centralized subdomain inventory and monitoring'
});
return recommendations;
}
}
// Usage
const discovery = new SubdomainDiscovery('example.com');
discovery.passiveEnumeration()
.then(() => discovery.verifySubdomains())
.then(() => discovery.detectTechnologies())
.then(() => discovery.assessSecurity())
.then(() => {
console.log('Subdomain Discovery Report:', discovery.generateReport());
});
DNS Zone Transfer Checker
Check for DNS misconfigurations:
// DNS Security Checker
class DNSSecurityChecker {
constructor(domain) {
this.domain = domain;
this.results = {
transfers: [],
misconfigurations: [],
dnssec: false,
records: {}
};
}
// Check for zone transfer vulnerability
async checkZoneTransfer() {
console.log('Checking for DNS zone transfer vulnerability...');
// In browser context, we can't perform actual zone transfers
// This would need to be done server-side
// Instead, we can check for signs of misconfiguration
const nameservers = await this.getNameservers();
nameservers.forEach(ns => {
// Simulated check
const vulnerable = Math.random() < 0.1; // 10% chance for demo
this.results.transfers.push({
nameserver: ns,
vulnerable: vulnerable,
message: vulnerable ?
'Zone transfer may be allowed (requires server-side verification)' :
'Zone transfer likely restricted'
});
});
return this.results.transfers;
}
// Get nameservers (simulated)
async getNameservers() {
// In real implementation, would query DNS
return [
`ns1.${this.domain}`,
`ns2.${this.domain}`,
'ns1.provider.com',
'ns2.provider.com'
];
}
// Check for DNS misconfigurations
async checkMisconfigurations() {
console.log('Checking for DNS misconfigurations...');
// Check for missing records
await this.checkMissingRecords();
// Check for dangling DNS
await this.checkDanglingDNS();
// Check for subdomain takeover possibilities
await this.checkSubdomainTakeover();
return this.results.misconfigurations;
}
// Check for missing security records
async checkMissingRecords() {
const securityRecords = [
{ type: 'SPF', record: 'TXT', pattern: 'v=spf1' },
{ type: 'DMARC', record: '_dmarc', pattern: 'v=DMARC1' },
{ type: 'DKIM', record: 'default._domainkey', pattern: 'v=DKIM1' },
{ type: 'CAA', record: 'CAA', pattern: 'issue' }
];
securityRecords.forEach(check => {
// Simulated check
const exists = Math.random() > 0.3;
if (!exists) {
this.results.misconfigurations.push({
type: 'missing-record',
severity: 'medium',
record: check.type,
description: `Missing ${check.type} record for email security`
});
}
});
}
// Check for dangling DNS records
async checkDanglingDNS() {
// Common dangling DNS patterns
const patterns = [
{ subdomain: 'www', cname: 'example.s3-website.amazonaws.com' },
{ subdomain: 'blog', cname: 'example.github.io' },
{ subdomain: 'help', cname: 'example.zendesk.com' },
{ subdomain: 'shop', cname: 'shops.myshopify.com' }
];
patterns.forEach(pattern => {
// Simulated check for dangling CNAME
const isDangling = Math.random() < 0.2;
if (isDangling) {
this.results.misconfigurations.push({
type: 'dangling-dns',
severity: 'high',
subdomain: `${pattern.subdomain}.${this.domain}`,
cname: pattern.cname,
description: 'Dangling DNS record could lead to subdomain takeover'
});
}
});
}
// Check for subdomain takeover vulnerabilities
async checkSubdomainTakeover() {
const takeoverSignatures = {
'amazonaws.com': { service: 'AWS S3', message: 'NoSuchBucket' },
'github.io': { service: 'GitHub Pages', message: 'There isn\'t a GitHub Pages site here' },
'herokuapp.com': { service: 'Heroku', message: 'No such app' },
'ghost.io': { service: 'Ghost', message: 'The thing you were looking for is no longer here' },
'zendesk.com': { service: 'Zendesk', message: 'Help Center Closed' },
'tumblr.com': { service: 'Tumblr', message: 'Whatever you were looking for doesn\'t currently exist' }
};
// Check discovered subdomains for takeover vulnerabilities
for (const [domain, signature] of Object.entries(takeoverSignatures)) {
// Simulated check
const vulnerable = Math.random() < 0.15;
if (vulnerable) {
this.results.misconfigurations.push({
type: 'subdomain-takeover',
severity: 'critical',
subdomain: `vulnerable.${this.domain}`,
service: signature.service,
description: `Subdomain points to ${signature.service} but service not configured`
});
}
}
}
// Check DNSSEC status
async checkDNSSEC() {
console.log('Checking DNSSEC status...');
// Simulated DNSSEC check
this.results.dnssec = Math.random() > 0.7;
if (!this.results.dnssec) {
this.results.misconfigurations.push({
type: 'missing-dnssec',
severity: 'low',
description: 'DNSSEC not enabled - vulnerable to DNS spoofing'
});
}
return this.results.dnssec;
}
}
Subdomain Monitoring System
Continuous subdomain monitoring:
// Subdomain Monitoring System
class SubdomainMonitor {
constructor(domain) {
this.domain = domain;
this.baseline = new Set();
this.changes = [];
this.alerts = [];
}
// Initialize baseline
async initializeBaseline() {
console.log('Initializing subdomain baseline...');
const discovery = new SubdomainDiscovery(this.domain);
await discovery.passiveEnumeration();
discovery.results.found.forEach(item => {
this.baseline.add(item.subdomain);
});
console.log(`Baseline established with ${this.baseline.size} subdomains`);
return this.baseline;
}
// Monitor for changes
async monitorChanges() {
console.log('Checking for subdomain changes...');
const discovery = new SubdomainDiscovery(this.domain);
await discovery.passiveEnumeration();
const current = new Set(discovery.results.found.map(item => item.subdomain));
// Find new subdomains
const newSubdomains = [];
current.forEach(subdomain => {
if (!this.baseline.has(subdomain)) {
newSubdomains.push(subdomain);
this.baseline.add(subdomain);
}
});
// Find removed subdomains
const removedSubdomains = [];
this.baseline.forEach(subdomain => {
if (!current.has(subdomain)) {
removedSubdomains.push(subdomain);
}
});
// Record changes
if (newSubdomains.length > 0 || removedSubdomains.length > 0) {
const change = {
timestamp: new Date().toISOString(),
new: newSubdomains,
removed: removedSubdomains
};
this.changes.push(change);
// Generate alerts for suspicious changes
this.analyzeChanges(change);
}
return {
new: newSubdomains,
removed: removedSubdomains,
total: this.baseline.size
};
}
// Analyze changes for security implications
analyzeChanges(change) {
// Alert on suspicious new subdomains
change.new.forEach(subdomain => {
// Check for typosquatting
if (this.isTyposquatting(subdomain)) {
this.alerts.push({
type: 'typosquatting',
severity: 'high',
subdomain,
message: 'Possible typosquatting subdomain detected'
});
}
// Check for suspicious patterns
const suspiciousPatterns = [
/phish/i, /fake/i, /scam/i, /hack/i,
/[0-9]{4,}/, // Long numbers
/xn--/, // Punycode
/-{2,}/, // Multiple hyphens
/(.)\1{3,}/ // Repeated characters
];
if (suspiciousPatterns.some(pattern => pattern.test(subdomain))) {
this.alerts.push({
type: 'suspicious-pattern',
severity: 'medium',
subdomain,
message: 'Subdomain contains suspicious pattern'
});
}
});
// Alert on critical subdomain removal
change.removed.forEach(subdomain => {
if (subdomain.match(/(www|api|mail|app)/)) {
this.alerts.push({
type: 'critical-removal',
severity: 'high',
subdomain,
message: 'Critical subdomain appears to be removed'
});
}
});
}
// Check for typosquatting
isTyposquatting(subdomain) {
const legitimate = Array.from(this.baseline);
const subdomainPart = subdomain.split('.')[0];
// Check Levenshtein distance to existing subdomains
for (const legit of legitimate) {
const legitPart = legit.split('.')[0];
const distance = this.levenshteinDistance(subdomainPart, legitPart);
// If very similar but not identical
if (distance > 0 && distance <= 2) {
return true;
}
}
return false;
}
// Calculate Levenshtein distance
levenshteinDistance(str1, str2) {
const matrix = [];
for (let i = 0; i <= str2.length; i++) {
matrix[i] = [i];
}
for (let j = 0; j <= str1.length; j++) {
matrix[0][j] = j;
}
for (let i = 1; i <= str2.length; i++) {
for (let j = 1; j <= str1.length; j++) {
if (str2.charAt(i - 1) === str1.charAt(j - 1)) {
matrix[i][j] = matrix[i - 1][j - 1];
} else {
matrix[i][j] = Math.min(
matrix[i - 1][j - 1] + 1,
matrix[i][j - 1] + 1,
matrix[i - 1][j] + 1
);
}
}
}
return matrix[str2.length][str1.length];
}
// Generate monitoring report
generateReport() {
return {
domain: this.domain,
baseline: {
count: this.baseline.size,
subdomains: Array.from(this.baseline)
},
changes: {
total: this.changes.length,
recent: this.changes.slice(-10)
},
alerts: {
total: this.alerts.length,
critical: this.alerts.filter(a => a.severity === 'critical'),
high: this.alerts.filter(a => a.severity === 'high'),
medium: this.alerts.filter(a => a.severity === 'medium')
},
recommendations: this.generateRecommendations()
};
}
// Generate recommendations
generateRecommendations() {
const recommendations = [];
if (this.baseline.size > 50) {
recommendations.push({
type: 'inventory',
priority: 'high',
action: 'Implement subdomain inventory management system'
});
}
if (this.alerts.some(a => a.type === 'typosquatting')) {
recommendations.push({
type: 'security',
priority: 'critical',
action: 'Investigate potential typosquatting attempts'
});
}
if (this.changes.length > 10) {
recommendations.push({
type: 'monitoring',
priority: 'medium',
action: 'Implement automated subdomain monitoring'
});
}
recommendations.push({
type: 'policy',
priority: 'medium',
action: 'Establish subdomain creation and decommissioning policies'
});
return recommendations;
}
}
// Usage
const monitor = new SubdomainMonitor('example.com');
monitor.initializeBaseline().then(() => {
// Set up periodic monitoring
setInterval(async () => {
const changes = await monitor.monitorChanges();
if (changes.new.length > 0 || changes.removed.length > 0) {
console.log('Subdomain changes detected:', changes);
console.log('Alerts:', monitor.alerts);
}
}, 3600000); // Check every hour
});
Subdomain Takeover Prevention
Prevent subdomain takeover vulnerabilities:
// Subdomain Takeover Prevention
class SubdomainTakeoverPrevention {
constructor() {
this.vulnerableServices = {
'AWS S3': {
cname: ['.s3.amazonaws.com', '.s3-website'],
fingerprint: 'NoSuchBucket',
prevention: 'Create S3 bucket with exact name or remove DNS record'
},
'GitHub Pages': {
cname: ['.github.io'],
fingerprint: 'There isn\'t a GitHub Pages site here',
prevention: 'Create GitHub repository or remove CNAME record'
},
'Heroku': {
cname: ['.herokuapp.com'],
fingerprint: 'No such app',
prevention: 'Create Heroku app or remove DNS record'
},
'Zendesk': {
cname: ['.zendesk.com'],
fingerprint: 'Help Center Closed',
prevention: 'Configure Zendesk or remove DNS record'
},
'Shopify': {
cname: ['.myshopify.com'],
fingerprint: 'Sorry, this shop is currently unavailable',
prevention: 'Configure Shopify store or remove DNS record'
},
'Azure': {
cname: ['.azurewebsites.net', '.cloudapp.azure.com'],
fingerprint: 'Error 404 - Web app not found',
prevention: 'Deploy Azure resource or remove DNS record'
}
};
}
// Scan for takeover vulnerabilities
async scanForTakeovers(subdomains) {
const vulnerabilities = [];
for (const subdomain of subdomains) {
const result = await this.checkSubdomain(subdomain);
if (result.vulnerable) {
vulnerabilities.push(result);
}
}
return vulnerabilities;
}
// Check individual subdomain
async checkSubdomain(subdomain) {
try {
// First, check DNS records (simulated)
const cname = await this.getCNAME(subdomain);
if (!cname) {
return { vulnerable: false, subdomain };
}
// Check against known vulnerable services
for (const [service, config] of Object.entries(this.vulnerableServices)) {
if (config.cname.some(pattern => cname.includes(pattern))) {
// Check if service is properly configured
const isConfigured = await this.checkServiceConfiguration(subdomain, config);
if (!isConfigured) {
return {
vulnerable: true,
subdomain,
service,
cname,
fingerprint: config.fingerprint,
prevention: config.prevention,
severity: 'critical'
};
}
}
}
return { vulnerable: false, subdomain };
} catch (error) {
return {
vulnerable: false,
subdomain,
error: error.message
};
}
}
// Get CNAME record (simulated)
async getCNAME(subdomain) {
// In real implementation, would query DNS
// Simulating CNAME records
const cnameMap = {
'blog': 'example.github.io',
'shop': 'example.myshopify.com',
'help': 'example.zendesk.com',
'app': 'example.herokuapp.com',
'assets': 'example.s3-website.amazonaws.com'
};
const subdomainPart = subdomain.split('.')[0];
return cnameMap[subdomainPart] || null;
}
// Check if service is configured
async checkServiceConfiguration(subdomain, config) {
try {
const response = await fetch(`https://${subdomain}`, {
method: 'GET',
mode: 'no-cors'
});
// In no-cors mode, we can't read response
// In real implementation, would check for fingerprint
// Simulate configuration check
return Math.random() > 0.3; // 70% configured
} catch (error) {
return false;
}
}
// Generate prevention script
generatePreventionScript(vulnerabilities) {
const script = {
automated: [],
manual: []
};
vulnerabilities.forEach(vuln => {
switch (vuln.service) {
case 'AWS S3':
script.automated.push(`
# Create S3 bucket to prevent takeover
aws s3api create-bucket --bucket ${vuln.cname.replace('.s3.amazonaws.com', '')} --region us-east-1
aws s3api put-bucket-policy --bucket ${vuln.cname.replace('.s3.amazonaws.com', '')} --policy '{
"Version": "2012-10-17",
"Statement": [{
"Sid": "PreventTakeover",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": "arn:aws:s3:::${vuln.cname.replace('.s3.amazonaws.com', '')}/*"
}]
}'`);
break;
case 'GitHub Pages':
script.manual.push({
subdomain: vuln.subdomain,
action: 'Create GitHub repository with name matching CNAME target',
steps: [
`1. Create repository: ${vuln.cname.replace('.github.io', '')}`,
'2. Enable GitHub Pages in repository settings',
'3. Add CNAME file with subdomain'
]
});
break;
default:
script.manual.push({
subdomain: vuln.subdomain,
action: vuln.prevention,
service: vuln.service
});
}
});
return script;
}
// Monitor for new vulnerabilities
async continuousMonitoring(domain, interval = 3600000) {
console.log(`Starting continuous subdomain takeover monitoring for ${domain}`);
const checkForVulnerabilities = async () => {
const discovery = new SubdomainDiscovery(domain);
await discovery.passiveEnumeration();
const vulnerabilities = await this.scanForTakeovers(
discovery.results.found.map(item => item.subdomain)
);
if (vulnerabilities.length > 0) {
console.warn('Subdomain takeover vulnerabilities found:', vulnerabilities);
// Generate and log prevention steps
const prevention = this.generatePreventionScript(vulnerabilities);
console.log('Prevention script:', prevention);
// Send alerts (implement your alerting mechanism)
this.sendAlerts(vulnerabilities);
}
return vulnerabilities;
};
// Initial check
await checkForVulnerabilities();
// Set up periodic monitoring
setInterval(checkForVulnerabilities, interval);
}
// Send security alerts
sendAlerts(vulnerabilities) {
vulnerabilities.forEach(vuln => {
console.error(`
CRITICAL SECURITY ALERT: Subdomain Takeover Vulnerability
Subdomain: ${vuln.subdomain}
Service: ${vuln.service}
Action Required: ${vuln.prevention}
`);
});
}
}
// Usage
const takeoverPrevention = new SubdomainTakeoverPrevention();
takeoverPrevention.continuousMonitoring('example.com');
Best Practices for Subdomain Security
- Maintain Inventory: Keep a centralized record of all subdomains
- Regular Audits: Scan for new and removed subdomains weekly
- Decommission Properly: Remove DNS records when services are discontinued
- Monitor Certificates: Track SSL certificates for all subdomains
- Implement Access Controls: Restrict access to internal subdomains
- Use Wildcard Certificates Carefully: They can hide rogue subdomains
- Enable DNSSEC: Prevent DNS hijacking and cache poisoning
- Monitor for Typosquatting: Watch for similar subdomain names
- Automate Checks: Use continuous monitoring for takeover vulnerabilities
- Document Everything: Maintain records of subdomain purposes and owners
Common Subdomain Security Issues
- Forgotten Subdomains: Old development or staging environments
- Subdomain Takeover: Dangling DNS records pointing to unclaimed services
- Information Disclosure: Exposed internal tools and admin panels
- Weak Authentication: Default credentials on subdomain services
- Missing SSL: Subdomains without HTTPS encryption
- Development Environments: Debug mode enabled in production
- Backup Exposures: Accessible backup files on forgotten subdomains
- API Endpoints: Undocumented APIs on subdomains
- Configuration Files: Exposed .env or config files
- Directory Listings: Enabled directory browsing on subdomains
Remember: Every subdomain is a potential entry point into your infrastructure. The subdomain you forget about is the one attackers will find. Implement comprehensive subdomain management and monitoring to maintain security across your entire domain space.