UNPKG

gitrecon

Version:

This CLI tool is designed to retrieve exposed email addresses from a GitHub user’s public activity and to identify a GitHub username associated with a given email address. It is intended strictly for educational use and ethical security research. Please u

256 lines (213 loc) 10.7 kB
// GitHub user reconnaissance const GitHubApi = require('./githubApi'); const FileUtils = require('../../utils/fileUtils'); const ColorUtils = require('../../utils/colors'); // Function to run GitHub reconnaissance - orijinal koddan const runGithubRecon = async (username, options = {}) => { const { downloadAvatarFlag = false, saveFork = false, outputFormat = null } = options; console.info(ColorUtils.green(`Running GitHub reconnaissance on user "${ColorUtils.yellow(username)}"`)); // Create output directory if it doesn't exist const outputDir = FileUtils.createOutputDirectory(); // Prepare result object with basic structure let result = { username: username, scan_started_at: new Date().toISOString(), organizations: [], leaked_emails: [], email_details: [], keys: [] }; // Check rate limit before starting try { const rateLimitData = await GitHubApi.getRateLimit(); if (rateLimitData && rateLimitData.resources && rateLimitData.resources.core && rateLimitData.resources.core.remaining < 50) { console.warn(ColorUtils.yellow(`Warning: You have only ${rateLimitData.resources.core.remaining} GitHub API requests remaining.`)); console.warn(ColorUtils.yellow('Consider using a GitHub token with --token option.')); } } catch (error) { // Continue anyway if rate limit check fails } // Fetch profile info const userInfo = await GitHubApi.getUserProfile(username); if (!userInfo || userInfo.error || (userInfo.message && userInfo.message.includes('Not Found'))) { console.error(ColorUtils.red(`Error: GitHub user "${username}" not found`)); return null; } // Update result with user info Object.assign(result, { username: userInfo.login, name: userInfo.name, id: userInfo.id, avatar_url: userInfo.avatar_url, email: userInfo.email, location: userInfo.location, bio: userInfo.bio, company: userInfo.company, blog: userInfo.blog, twitter_username: userInfo.twitter_username, followers: userInfo.followers, following: userInfo.following, created_at: userInfo.created_at, updated_at: userInfo.updated_at }); // Save initial data FileUtils.saveRealTime(result, username, 'github', outputDir); console.log(ColorUtils.green(`Found GitHub user: ${ColorUtils.yellow(userInfo.login || username)} (${ColorUtils.yellow(userInfo.name || 'No name')})`)); // Fetch organizations const orgsData = await GitHubApi.getUserOrganizations(username); let orgs = []; if (Array.isArray(orgsData)) { orgs = orgsData.map(org => org.login); if (orgs.length > 0) { console.log(ColorUtils.green(`Found ${ColorUtils.yellow(orgs.length)} organizations: ${ColorUtils.yellow(orgs.join(', '))}`)); } else { console.log(ColorUtils.green('No organizations found')); } // Update result with organizations result.organizations = orgs; FileUtils.saveRealTime(result, username, 'github', outputDir); } else { console.warn(ColorUtils.yellow(`Error fetching organizations: ${orgsData.message || 'Unknown error'}`)); } // Fetch public SSH keys const keysData = await GitHubApi.getUserKeys(username); let keys = []; if (Array.isArray(keysData)) { keys = keysData; if (keys.length > 0) { console.log(ColorUtils.green(`Found ${ColorUtils.yellow(keys.length)} public SSH keys`)); } else { console.log(ColorUtils.green('No public SSH keys found')); } // Update result with keys result.keys = keys.map(key => ({ id: key.id, key: key.key })); FileUtils.saveRealTime(result, username, 'github', outputDir); } else { console.warn(ColorUtils.yellow(`Error fetching public keys: ${keysData.message || 'Unknown error'}`)); } // Gather repositories const repositories = await GitHubApi.getRepositories(username); console.log(ColorUtils.green(`Found ${ColorUtils.yellow(repositories.length)} public repositories`)); // Filter out forks if needed (using non-forked repos first) const reposToScan = repositories .sort((a, b) => (a.isFork ? 1 : -1)) .filter(repo => !repo.isFork || saveFork) .map(repo => repo.name); console.log(ColorUtils.green(`Scanning ${ColorUtils.yellow(reposToScan.length)} repositories for leaked emails`)); // Initialize email tracking const emailsToName = new Map(); const emailsToRepo = new Map(); let allLeakedEmails = []; // Scan each repository for commits const totalRepos = reposToScan.length; for (let i = 0; i < totalRepos; i++) { const repo = reposToScan[i]; process.stdout.write(ColorUtils.green(`Scanning repository ${ColorUtils.yellow(`${i + 1}/${totalRepos}`)}: ${ColorUtils.cyan(repo)}...`)); try { const newEmails = await GitHubApi.getEmails(username, repo); process.stdout.write('\r' + ' '.repeat(100) + '\r'); // Clear the line let newEmailsCount = 0; for (const [email, names] of newEmails.entries()) { // Track the repository where this email was found if (!emailsToRepo.has(email)) { emailsToRepo.set(email, new Set()); } emailsToRepo.get(email).add(repo); // Track all names associated with this email if (!emailsToName.has(email)) { emailsToName.set(email, new Set()); allLeakedEmails.push(email); newEmailsCount++; } // Add all names from this repository names.forEach(name => emailsToName.get(email).add(name)); } if (newEmailsCount > 0) { console.log(ColorUtils.green(`Found ${ColorUtils.yellow(newEmailsCount)} new emails in ${ColorUtils.cyan(repo)}`)); // Update result with email details const emailDetails = Array.from(emailsToName.entries()).map(([email, namesSet]) => ({ email, names: Array.from(namesSet), sources: Array.from(emailsToRepo.get(email) || []) })); result.leaked_emails = allLeakedEmails; result.email_details = emailDetails; result.scan_progress = `${i + 1}/${totalRepos} repositories scanned`; // Save after each repository that yields new emails FileUtils.saveRealTime(result, username, 'github', outputDir); } else { // Periodically save progress, every 5 repositories if (i % 5 === 0 && i > 0) { result.scan_progress = `${i + 1}/${totalRepos} repositories scanned`; FileUtils.saveRealTime(result, username, 'github', outputDir); } } } catch (error) { process.stdout.write('\r' + ' '.repeat(100) + '\r'); // Clear the line console.error(ColorUtils.red(`Error scanning ${repo}: ${error.message}`)); // Save progress on error const emailDetails = Array.from(emailsToName.entries()).map(([email, namesSet]) => ({ email, names: Array.from(namesSet), sources: Array.from(emailsToRepo.get(email) || []) })); result.leaked_emails = allLeakedEmails; result.email_details = emailDetails; result.scan_progress = `${i + 1}/${totalRepos} repositories scanned`; result.last_error = { repository: repo, message: error.message, timestamp: new Date().toISOString() }; FileUtils.saveRealTime(result, username, 'github', outputDir); } } // Prepare email details for display and output const emailDetails = Array.from(emailsToName.entries()).map(([email, namesSet]) => ({ email, names: Array.from(namesSet), sources: Array.from(emailsToRepo.get(email) || []) })); // Display results console.log(`\n${ColorUtils.green('Reconnaissance completed:')}`); console.log(ColorUtils.green(`User: ${ColorUtils.yellow(`${userInfo.login} (${userInfo.name || 'No name'})`)}`)); console.log(ColorUtils.green(`URL: ${ColorUtils.cyan(`https://github.com/${username}`)}`)); console.log(ColorUtils.green(`Organizations: ${ColorUtils.yellow(orgs.length > 0 ? orgs.join(', ') : 'None')}`)); console.log(ColorUtils.green(`Public Keys: ${ColorUtils.yellow(keys.length)}`)); console.log(ColorUtils.green(`Leaked Emails: ${ColorUtils.yellow(allLeakedEmails.length)}`)); if (keys.length > 0) { console.log(`\n${ColorUtils.yellow('Public SSH Keys:')}`); keys.forEach((key, index) => { console.log(ColorUtils.cyan(`Key #${index + 1}:`)); console.log(`${key.key.substring(0, 40)}...`); }); } if (allLeakedEmails.length > 0) { console.log(`\n${ColorUtils.yellow('Leaked Emails:')}`); // Create a more organized table format for output const emailTable = emailDetails.map(detail => ({ email: detail.email, names: Array.from(detail.names).join(', ').substring(0, 30) + (Array.from(detail.names).join(', ').length > 30 ? '...' : ''), sources: Array.from(detail.sources).length })); console.table(emailTable); } // Build full result object with final data result.scan_completed_at = new Date().toISOString(); result.scan_progress = "completed"; // Download avatar if requested if (downloadAvatarFlag && userInfo.avatar_url) { await FileUtils.downloadAvatar(userInfo.avatar_url, username, 'github'); } // Save final output if requested if (outputFormat) { FileUtils.saveOutput(result, outputFormat, username, 'github'); } return result; }; class GitHubUser { static runRecon = runGithubRecon; } module.exports = GitHubUser;