recon.tsā¢23.2 kB
import { exec } from 'child_process';
import { promisify } from 'util';
import axios from 'axios';
import * as cheerio from 'cheerio';
import { URL } from 'url';
const execAsync = promisify(exec);
export interface ScanResult {
target: string;
timestamp: string;
tool: string;
results: any;
status: 'success' | 'error';
error?: string;
}
export interface PortScanResult {
port: number;
protocol: string;
state: string;
service: string;
version?: string;
}
export interface TechDetectionResult {
technology: string;
version?: string;
confidence: number;
category: string;
}
export interface ServiceRecommendation {
service: string;
port: number;
protocol: string;
version?: string;
recommendedTools: string[];
testingApproach: string[];
riskLevel: 'low' | 'medium' | 'high' | 'critical';
}
export class ReconTools {
async serviceAnalysis(ports: PortScanResult[]): Promise<{serviceRecommendations: ServiceRecommendation[]}> {
const recommendations: ServiceRecommendation[] = [];
for (const port of ports) {
const service = port.service.toLowerCase();
const recommendation: ServiceRecommendation = {
service: port.service,
port: port.port,
protocol: port.protocol,
version: port.version,
recommendedTools: [],
testingApproach: [],
riskLevel: 'low'
};
// SMB/NetBIOS services
if (service.includes('smb') || service.includes('netbios') || port.port === 445 || port.port === 139) {
recommendation.recommendedTools = ['crackmapexec', 'smbclient', 'enum4linux', 'smbmap', 'rpcclient'];
recommendation.testingApproach = [
'SMB null session enumeration',
'Share enumeration and access testing',
'User enumeration via RPC',
'Password spraying attacks',
'SMB vulnerability scanning (EternalBlue, etc.)'
];
recommendation.riskLevel = 'high';
}
// SSH services
else if (service.includes('ssh') || port.port === 22) {
recommendation.recommendedTools = ['hydra', 'medusa', 'ncrack', 'ssh-audit', 'ssh-keyscan'];
recommendation.testingApproach = [
'SSH banner analysis and version detection',
'Authentication method enumeration',
'Weak credential testing',
'SSH key enumeration',
'Protocol vulnerability testing'
];
recommendation.riskLevel = 'medium';
}
// FTP services
else if (service.includes('ftp') || port.port === 21) {
recommendation.recommendedTools = ['ftp', 'hydra', 'medusa', 'nmap-scripts'];
recommendation.testingApproach = [
'Anonymous FTP access testing',
'FTP bounce attack testing',
'Credential brute-forcing',
'Directory traversal testing',
'FTP vulnerability scanning'
];
recommendation.riskLevel = 'medium';
}
// HTTP/HTTPS services
else if (service.includes('http') || port.port === 80 || port.port === 443 || port.port === 8080) {
recommendation.recommendedTools = ['dirb', 'gobuster', 'feroxbuster', 'nikto', 'burpsuite', 'owasp-zap'];
recommendation.testingApproach = [
'Directory and file enumeration',
'Parameter discovery and fuzzing',
'OWASP Top 10 vulnerability testing',
'Technology stack analysis',
'SSL/TLS configuration testing'
];
recommendation.riskLevel = 'medium';
}
// Database services
else if (service.includes('mysql') || service.includes('postgres') || service.includes('mssql') ||
port.port === 3306 || port.port === 5432 || port.port === 1433) {
recommendation.recommendedTools = ['sqlmap', 'hydra', 'medusa', 'nmap-scripts'];
recommendation.testingApproach = [
'Default credential testing',
'SQL injection testing',
'Database enumeration',
'Privilege escalation testing',
'Configuration review'
];
recommendation.riskLevel = 'high';
}
// RDP services
else if (service.includes('rdp') || port.port === 3389) {
recommendation.recommendedTools = ['rdesktop', 'xfreerdp', 'hydra', 'crowbar', 'bluekeep-scanner'];
recommendation.testingApproach = [
'RDP connection testing',
'Credential brute-forcing',
'BlueKeep vulnerability testing',
'NLA bypass testing',
'Session hijacking testing'
];
recommendation.riskLevel = 'high';
}
// SNMP services
else if (service.includes('snmp') || port.port === 161) {
recommendation.recommendedTools = ['snmpwalk', 'snmpcheck', 'onesixtyone', 'snmp-check'];
recommendation.testingApproach = [
'SNMP community string enumeration',
'SNMP walk and enumeration',
'SNMP version detection',
'Information disclosure testing',
'Write access testing'
];
recommendation.riskLevel = 'medium';
}
// LDAP services
else if (service.includes('ldap') || port.port === 389 || port.port === 636) {
recommendation.recommendedTools = ['ldapsearch', 'ldapenum', 'enum4linux'];
recommendation.testingApproach = [
'Anonymous LDAP bind testing',
'Directory enumeration',
'User and group enumeration',
'Password policy enumeration',
'LDAP injection testing'
];
recommendation.riskLevel = 'medium';
}
recommendations.push(recommendation);
}
return { serviceRecommendations: recommendations };
}
async nmapScan(target: string, scanType: string = 'quick'): Promise<ScanResult> {
try {
let nmapArgs = '';
switch (scanType) {
case 'quick':
nmapArgs = '-F -sV';
break;
case 'full':
nmapArgs = '-p- -sV -sC';
break;
case 'stealth':
nmapArgs = '-sS -T2 -f';
break;
case 'aggressive':
nmapArgs = '-A -T4';
break;
default:
nmapArgs = '-F -sV';
}
const command = `nmap ${nmapArgs} -oX - ${target}`;
console.error(`Executing: ${command}`);
const { stdout, stderr } = await execAsync(command, { timeout: 300000 }); // 5 min timeout
// Parse XML output
const ports = this.parseNmapXML(stdout);
return {
target,
timestamp: new Date().toISOString(),
tool: 'nmap',
results: {
scan_type: scanType,
open_ports: ports,
raw_output: stdout
},
status: 'success'
};
} catch (error) {
return {
target,
timestamp: new Date().toISOString(),
tool: 'nmap',
results: {},
status: 'error',
error: error instanceof Error ? error.message : String(error)
};
}
}
async subdomainEnum(domain: string, wordlist?: string, useSubfinder?: boolean, fuzzTool?: 'ffuf' | 'wfuzz'): Promise<ScanResult> {
try {
const subdomains = new Set<string>();
// Method 1: Certificate Transparency logs
try {
const ctLogs = await this.getCertTransparencySubdomains(domain);
ctLogs.forEach(sub => subdomains.add(sub));
} catch (e) {
console.error('CT logs failed:', e);
}
// Method 2: DNS brute force with common subdomains or supplied wordlist
const commonSubs = [
'www', 'mail', 'ftp', 'localhost', 'webmail', 'smtp', 'pop', 'ns1', 'webdisk',
'ns2', 'cpanel', 'whm', 'autodiscover', 'autoconfig', 'dev', 'staging', 'test',
'api', 'admin', 'blog', 'shop', 'forum', 'support', 'mobile', 'app', 'cdn'
];
const customWordlist = wordlist ? await this.loadWordlist(wordlist) : commonSubs;
for (const subdomain of customWordlist) {
try {
const fullDomain = `${subdomain}.${domain}`;
const dns = require('dns').promises;
await dns.lookup(fullDomain);
subdomains.add(fullDomain);
} catch (e) {
// Subdomain doesn't exist
}
}
// Method 3: subfinder integration
if (useSubfinder) {
try {
const cmd = `subfinder -silent -d ${domain}`;
const { stdout } = await execAsync(cmd, { timeout: 60000 });
stdout.split('\n').map(s => s.trim()).filter(s => s).forEach(s => subdomains.add(s));
} catch (e) {
console.error('subfinder failed:', e);
}
}
// Method 4: Fuzzing with ffuf/wfuzz
if (fuzzTool) {
try {
const fuzzSubs = await this.fuzzSubdomains(domain, fuzzTool, wordlist);
fuzzSubs.forEach(sub => subdomains.add(sub));
} catch (e) {
console.error(`${fuzzTool} subdomain fuzzing failed:`, e);
}
}
return {
target: domain,
timestamp: new Date().toISOString(),
tool: 'subdomain_enum',
results: {
subdomains: Array.from(subdomains),
count: subdomains.size,
methods_used: ['certificate_transparency', 'dns_bruteforce']
.concat(useSubfinder ? ['subfinder'] : [])
.concat(fuzzTool ? [`${fuzzTool}_fuzzing`] : [])
},
status: 'success'
};
} catch (error) {
return {
target: domain,
timestamp: new Date().toISOString(),
tool: 'subdomain_enum',
results: {},
status: 'error',
error: error instanceof Error ? error.message : String(error)
};
}
}
async techDetection(url: string): Promise<ScanResult> {
try {
const technologies: TechDetectionResult[] = [];
// Make HTTP request to analyze headers and content
const response = await axios.get(url, {
timeout: 10000,
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}
});
const headers = response.headers;
const html = response.data;
const $ = cheerio.load(html);
// Detect technologies from headers
this.detectFromHeaders(headers, technologies);
// Detect technologies from HTML content
this.detectFromHTML($, technologies);
// Detect technologies from meta tags
this.detectFromMetaTags($, technologies);
// Detect technologies from script sources
this.detectFromScripts($, technologies);
return {
target: url,
timestamp: new Date().toISOString(),
tool: 'tech_detection',
results: {
technologies,
headers: headers,
status_code: response.status,
server_info: {
server: headers.server || 'Unknown',
powered_by: headers['x-powered-by'] || 'Unknown',
generator: $('meta[name="generator"]').attr('content') || 'Unknown'
}
},
status: 'success'
};
} catch (error) {
return {
target: url,
timestamp: new Date().toISOString(),
tool: 'tech_detection',
results: {},
status: 'error',
error: error instanceof Error ? error.message : String(error)
};
}
}
async directoryBruteforce(url: string, wordlist?: string, extensions?: string[], useSecLists?: boolean): Promise<ScanResult> {
try {
const foundPaths: string[] = [];
const baseUrl = new URL(url);
// Default directory wordlist
const defaultDirs = [
'admin', 'administrator', 'login', 'dashboard', 'panel', 'config', 'backup',
'test', 'dev', 'staging', 'api', 'uploads', 'files', 'images', 'css', 'js',
'assets', 'static', 'public', 'private', 'secure', 'hidden', 'secret',
'wp-admin', 'wp-content', 'wp-includes', 'phpmyadmin', 'mysql', 'database'
];
let dirs: string[] = defaultDirs;
if (wordlist) {
dirs = await this.loadWordlist(wordlist);
} else if (useSecLists) {
const candidates = [
'/usr/share/seclists/Discovery/Web-Content/common.txt',
'/usr/share/seclists/Discovery/Web-Content/raft-small-words.txt',
'/usr/share/seclists/Discovery/Web-Content/raft-medium-words.txt'
];
for (const path of candidates) {
try {
const loaded = await this.loadWordlist(path);
if (loaded.length > 0) { dirs = loaded; break; }
} catch (_) {}
}
}
const exts = extensions || ['php', 'html', 'asp', 'aspx', 'jsp', 'txt', 'xml', 'json'];
// Check directories
for (const dir of dirs) {
try {
const testUrl = `${baseUrl.origin}/${dir}/`;
const response = await axios.head(testUrl, { timeout: 5000 });
if (response.status === 200 || response.status === 403) {
foundPaths.push(testUrl);
}
} catch (e) {
// Path not found or error
}
}
// Check files with extensions
for (const dir of dirs) {
for (const ext of exts) {
try {
const testUrl = `${baseUrl.origin}/${dir}.${ext}`;
const response = await axios.head(testUrl, { timeout: 5000 });
if (response.status === 200) {
foundPaths.push(testUrl);
}
} catch (e) {
// File not found or error
}
}
}
return {
target: url,
timestamp: new Date().toISOString(),
tool: 'directory_bruteforce',
results: {
found_paths: foundPaths,
total_found: foundPaths.length,
wordlist_size: dirs.length,
extensions_tested: exts
},
status: 'success'
};
} catch (error) {
return {
target: url,
timestamp: new Date().toISOString(),
tool: 'directory_bruteforce',
results: {},
status: 'error',
error: error instanceof Error ? error.message : String(error)
};
}
}
// Helper methods
private parseNmapXML(xmlOutput: string): PortScanResult[] {
const ports: PortScanResult[] = [];
// Simple XML parsing for ports - in production, use proper XML parser
const portRegex = /<port protocol="(tcp|udp)" portid="(\d+)">[\s\S]*?<state state="(open|closed|filtered)"[\s\S]*?<service name="([^"]*)"(?:\s+version="([^"]*)")?/g;
let match;
while ((match = portRegex.exec(xmlOutput)) !== null) {
ports.push({
port: parseInt(match[2]),
protocol: match[1],
state: match[3],
service: match[4],
version: match[5] || undefined
});
}
return ports;
}
private async getCertTransparencySubdomains(domain: string): Promise<string[]> {
try {
const response = await axios.get(`https://crt.sh/?q=%.${domain}&output=json`, {
timeout: 10000
});
const subdomains = new Set<string>();
for (const cert of response.data) {
if (cert.name_value) {
const names = cert.name_value.split('\n');
for (const name of names) {
if (name.includes(domain) && !name.includes('*')) {
subdomains.add(name.trim());
}
}
}
}
return Array.from(subdomains);
} catch (error) {
console.error('CT logs error:', error);
return [];
}
}
private async loadWordlist(wordlistPath: string): Promise<string[]> {
try {
const fs = require('fs').promises;
const content = await fs.readFile(wordlistPath, 'utf8');
return content.split('\n').filter((line: string) => line.trim() !== '');
} catch (error) {
console.error('Wordlist loading error:', error);
return [];
}
}
private detectFromHeaders(headers: any, technologies: TechDetectionResult[]): void {
// Server detection
if (headers.server) {
const server = headers.server.toLowerCase();
if (server.includes('apache')) {
technologies.push({
technology: 'Apache HTTP Server',
version: this.extractVersion(server, 'apache'),
confidence: 100,
category: 'Web Server'
});
} else if (server.includes('nginx')) {
technologies.push({
technology: 'Nginx',
version: this.extractVersion(server, 'nginx'),
confidence: 100,
category: 'Web Server'
});
} else if (server.includes('iis')) {
technologies.push({
technology: 'Microsoft IIS',
version: this.extractVersion(server, 'iis'),
confidence: 100,
category: 'Web Server'
});
}
}
// X-Powered-By detection
if (headers['x-powered-by']) {
const poweredBy = headers['x-powered-by'].toLowerCase();
if (poweredBy.includes('php')) {
technologies.push({
technology: 'PHP',
version: this.extractVersion(poweredBy, 'php'),
confidence: 100,
category: 'Programming Language'
});
} else if (poweredBy.includes('asp.net')) {
technologies.push({
technology: 'ASP.NET',
version: this.extractVersion(poweredBy, 'asp.net'),
confidence: 100,
category: 'Web Framework'
});
}
}
}
private detectFromHTML($: cheerio.CheerioAPI, technologies: TechDetectionResult[]): void {
const html = $.html();
// WordPress detection
if (html.includes('wp-content') || html.includes('wp-includes')) {
technologies.push({
technology: 'WordPress',
confidence: 95,
category: 'CMS'
});
}
// jQuery detection
if (html.includes('jquery') || $('script[src*="jquery"]').length > 0) {
technologies.push({
technology: 'jQuery',
confidence: 90,
category: 'JavaScript Library'
});
}
// Bootstrap detection
if (html.includes('bootstrap') || $('link[href*="bootstrap"]').length > 0) {
technologies.push({
technology: 'Bootstrap',
confidence: 85,
category: 'CSS Framework'
});
}
}
private detectFromMetaTags($: cheerio.CheerioAPI, technologies: TechDetectionResult[]): void {
const generator = $('meta[name="generator"]').attr('content');
if (generator) {
const gen = generator.toLowerCase();
if (gen.includes('wordpress')) {
technologies.push({
technology: 'WordPress',
version: this.extractVersion(generator, 'wordpress'),
confidence: 100,
category: 'CMS'
});
} else if (gen.includes('drupal')) {
technologies.push({
technology: 'Drupal',
version: this.extractVersion(generator, 'drupal'),
confidence: 100,
category: 'CMS'
});
}
}
}
private detectFromScripts($: cheerio.CheerioAPI, technologies: TechDetectionResult[]): void {
$('script[src]').each((_, element) => {
const src = $(element).attr('src');
if (src) {
const srcLower = src.toLowerCase();
if (srcLower.includes('react')) {
technologies.push({
technology: 'React',
confidence: 90,
category: 'JavaScript Framework'
});
} else if (srcLower.includes('angular')) {
technologies.push({
technology: 'Angular',
confidence: 90,
category: 'JavaScript Framework'
});
} else if (srcLower.includes('vue')) {
technologies.push({
technology: 'Vue.js',
confidence: 90,
category: 'JavaScript Framework'
});
}
}
});
}
private extractVersion(text: string, technology: string): string | undefined {
const versionRegex = new RegExp(`${technology}[\/\\s]+(\\d+(?:\\.\\d+)*(?:\\.\\d+)*(?:[a-z]\\d*)?)`, 'i');
const match = text.match(versionRegex);
return match ? match[1] : undefined;
}
private async fuzzSubdomains(domain: string, tool: 'ffuf' | 'wfuzz', wordlist?: string): Promise<string[]> {
const subdomains: string[] = [];
// Get wordlist for subdomain fuzzing
let fuzzWordlist: string;
if (wordlist) {
fuzzWordlist = wordlist;
} else {
// Try SecLists subdomain wordlists
const candidates = [
'/usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt',
'/usr/share/seclists/Discovery/DNS/fierce-hostlist.txt',
'/usr/share/seclists/Discovery/DNS/subdomains-top1million-20000.txt'
];
fuzzWordlist = candidates[0]; // Default to largest
for (const path of candidates) {
try {
const fs = require('fs');
if (fs.existsSync(path)) {
fuzzWordlist = path;
break;
}
} catch (_) {}
}
}
try {
let command: string;
if (tool === 'ffuf') {
// ffuf command for subdomain fuzzing
command = `ffuf -w ${fuzzWordlist} -u http://FUZZ.${domain} -H "Host: FUZZ.${domain}" -t 50 -fc 404,403 -fs 0 -timeout 10 -o /tmp/ffuf_subdomains.json -of json -s`;
} else if (tool === 'wfuzz') {
// wfuzz command for subdomain fuzzing
command = `wfuzz -w ${fuzzWordlist} -H "Host: FUZZ.${domain}" --hc 404,403 --hl 0 -t 50 -s 0.1 http://FUZZ.${domain}`;
} else {
throw new Error(`Unsupported fuzzing tool: ${tool}`);
}
console.error(`Running subdomain fuzzing: ${command}`);
const { stdout, stderr } = await execAsync(command, { timeout: 300000 }); // 5 min timeout
if (tool === 'ffuf') {
// Parse ffuf JSON output
try {
const fs = require('fs');
const jsonOutput = fs.readFileSync('/tmp/ffuf_subdomains.json', 'utf8');
const results = JSON.parse(jsonOutput);
if (results.results) {
for (const result of results.results) {
if (result.input && result.input.FUZZ) {
subdomains.push(`${result.input.FUZZ}.${domain}`);
}
}
}
// Cleanup temp file
fs.unlinkSync('/tmp/ffuf_subdomains.json');
} catch (e) {
console.error('Failed to parse ffuf output:', e);
}
} else if (tool === 'wfuzz') {
// Parse wfuzz output
const lines = stdout.split('\n');
for (const line of lines) {
// Look for successful responses in wfuzz output
const match = line.match(/^\d+.*?(\w+)\.${domain.replace('.', '\\.')}/);
if (match && match[1]) {
subdomains.push(`${match[1]}.${domain}`);
}
}
}
} catch (error) {
console.error(`Subdomain fuzzing with ${tool} failed:`, error);
}
return subdomains;
}
}