domainlooker
Version:
A fast CLI and MCP server for inspecting domains: WHOIS/RDAP, DNS, SSL, ports, subdomains, with CSV/JSON export.
146 lines • 6.84 kB
JavaScript
import { promises as dns } from 'dns';
import axios from 'axios';
export class SubdomainService {
constructor() {
this.commonSubdomains = [
'www', 'mail', 'ftp', 'localhost', 'webmail', 'smtp', 'pop', 'ns1', 'webdisk', 'ns2',
'cpanel', 'whm', 'autodiscover', 'autoconfig', 'mobile', 'm', 'dev', 'staging', 'test',
'api', 'admin', 'blog', 'shop', 'cdn', 'assets', 'static', 'img', 'images', 'media',
'secure', 'vpn', 'remote', 'support', 'help', 'docs', 'status', 'monitor', 'demo',
'beta', 'alpha', 'preview', 'sandbox', 'app', 'portal', 'dashboard', 'panel', 'control',
'git', 'repo', 'code', 'build', 'ci', 'jenkins', 'gitlab', 'github', 'bitbucket',
'email', 'imap', 'pop3', 'exchange', 'outlook', 'office', 'calendar', 'contacts',
'intranet', 'extranet', 'internal', 'external', 'public', 'private', 'secure2',
'news', 'press', 'media2', 'content', 'cms', 'editor', 'author', 'writer',
'video', 'stream', 'live', 'broadcast', 'radio', 'podcast', 'audio', 'music',
'files', 'download', 'upload', 'share', 'cloud', 'storage', 'backup', 'archive',
'search', 'find', 'directory', 'index', 'catalog', 'library', 'database', 'db',
'forum', 'community', 'social', 'chat', 'discuss', 'feedback', 'comments', 'reviews'
];
}
async discoverSubdomains(domain) {
const sources = {
certificateTransparency: [],
commonNames: []
};
// Wildcard DNS makes the common-name resolution check meaningless (every name
// resolves), so skip it when a wildcard is present and rely on cert transparency.
const hasWildcard = await this.checkWildcardDNS(domain);
const [ctResults, commonResults] = await Promise.allSettled([
this.certificateTransparencyLookup(domain),
hasWildcard ? Promise.resolve([]) : this.commonSubdomainCheck(domain)
]);
if (ctResults.status === 'fulfilled') {
sources.certificateTransparency = ctResults.value;
}
if (commonResults.status === 'fulfilled') {
sources.commonNames = commonResults.value;
}
// Combine and deduplicate all found subdomains
const allSubdomains = new Set([
...sources.certificateTransparency,
...sources.commonNames
]);
const subdomains = Array.from(allSubdomains).sort();
return {
subdomains,
sources,
totalFound: subdomains.length
};
}
async certificateTransparencyLookup(domain) {
const subdomains = [];
try {
// Use crt.sh API for certificate transparency logs
const response = await axios.get(`https://crt.sh/?q=${encodeURIComponent(domain)}&output=json`, {
timeout: 10000,
headers: {
'User-Agent': 'DOMAINLOOKER/1.0 Security Research Tool'
}
});
if (response.data && Array.isArray(response.data)) {
const certificates = response.data;
const subdomainSet = new Set();
certificates.forEach((cert) => {
if (cert.name_value) {
const names = cert.name_value.split('\n');
names.forEach((name) => {
name = name.trim().toLowerCase();
// Filter valid subdomains
if (name.endsWith(`.${domain}`) && name !== domain) {
// Remove wildcards and get the subdomain
const cleanName = name.replace(/^\*\./, '');
if (cleanName !== domain && this.isValidSubdomain(cleanName, domain)) {
subdomainSet.add(cleanName);
}
}
});
}
});
subdomains.push(...Array.from(subdomainSet));
}
}
catch (error) {
// Certificate transparency lookup failed
}
return subdomains;
}
async commonSubdomainCheck(domain) {
const foundSubdomains = [];
const batchSize = 10; // Process in batches to avoid overwhelming DNS servers
for (let i = 0; i < this.commonSubdomains.length; i += batchSize) {
const batch = this.commonSubdomains.slice(i, i + batchSize);
const batchPromises = batch.map(subdomain => this.checkSubdomain(subdomain, domain));
const results = await Promise.allSettled(batchPromises);
results.forEach((result, index) => {
if (result.status === 'fulfilled' && result.value) {
foundSubdomains.push(`${batch[index]}.${domain}`);
}
});
// Small delay between batches to be respectful
if (i + batchSize < this.commonSubdomains.length) {
await new Promise(resolve => setTimeout(resolve, 100));
}
}
return foundSubdomains;
}
async checkSubdomain(subdomain, domain) {
const fullDomain = `${subdomain}.${domain}`;
try {
// Try both A and AAAA records
const [aRecords, aaaaRecords] = await Promise.allSettled([
dns.resolve4(fullDomain),
dns.resolve6(fullDomain)
]);
return aRecords.status === 'fulfilled' || aaaaRecords.status === 'fulfilled';
}
catch (error) {
return false;
}
}
async checkWildcardDNS(domain) {
try {
// Check if a random subdomain resolves (indicating wildcard DNS)
const randomSubdomain = `random-${Date.now()}-test.${domain}`;
await dns.resolve4(randomSubdomain);
return true; // Wildcard DNS detected
}
catch (error) {
return false; // No wildcard DNS
}
}
isValidSubdomain(subdomain, baseDomain) {
// Basic validation for subdomain format
if (!subdomain || subdomain === baseDomain)
return false;
// Check if it's actually a subdomain of the base domain
if (!subdomain.endsWith(`.${baseDomain}`))
return false;
// Remove the base domain part
const subPart = subdomain.replace(`.${baseDomain}`, '');
// Check for valid subdomain characters
const validSubdomainRegex = /^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$/;
return validSubdomainRegex.test(subPart);
}
}
//# sourceMappingURL=subdomain.js.map