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
271 lines (226 loc) • 11.9 kB
JavaScript
// GitLab user reconnaissance
const GitLabApi = require('./gitlabApi');
const FileUtils = require('../../utils/fileUtils');
const ColorUtils = require('../../utils/colors');
// Function to run GitLab reconnaissance
const runGitlabRecon = async (username, options = {}) => {
const { downloadAvatarFlag = false, outputFormat = null } = options;
console.info(ColorUtils.green(`Running GitLab 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(),
leaked_emails: [],
email_details: [],
keys: []
};
// Fetch user ID first
const userData = await GitLabApi.findUserByUsername(username);
if (!userData || userData.error || !Array.isArray(userData) || userData.length === 0) {
console.error(ColorUtils.red(`Error: GitLab user "${username}" not found`));
return null;
}
const userId = userData[0].id;
console.log(ColorUtils.green(`Found GitLab user ID: ${ColorUtils.yellow(userId)}`));
// Fetch profile info
const userInfo = await GitLabApi.getUserById(userId);
if (userInfo.error) {
console.error(ColorUtils.red(`Error fetching user info: ${userInfo.message}`));
return null;
}
// Update result with user info
Object.assign(result, {
username: userInfo.username,
name: userInfo.name,
id: userInfo.id,
avatar_url: userInfo.avatar_url,
public_email: userInfo.public_email,
location: userInfo.location,
bio: userInfo.bio,
organization: userInfo.organization,
job_title: userInfo.job_title,
web_url: userInfo.web_url,
state: userInfo.state,
twitter: userInfo.twitter,
linkedin: userInfo.linkedin,
skype: userInfo.skype,
created_at: userInfo.created_at
});
// Save initial data
FileUtils.saveRealTime(result, username, 'gitlab', outputDir);
console.log(ColorUtils.green(`Found GitLab user: ${ColorUtils.yellow(userInfo.username || username)} (${ColorUtils.yellow(userInfo.name || 'No name')})`));
// Fetch status
const userStatus = await GitLabApi.getUserStatus(userId);
if (userStatus && !userStatus.error) {
result.status = userStatus.message;
FileUtils.saveRealTime(result, username, 'gitlab', outputDir);
}
// Fetch keys
const keys = await GitLabApi.getUserKeys(userId);
if (Array.isArray(keys) && keys.length > 0) {
console.log(ColorUtils.green(`Found ${ColorUtils.yellow(keys.length)} public SSH keys`));
// Update result with keys
result.keys = keys.map(key => ({
title: key.title,
created_at: key.created_at,
expires_at: key.expires_at,
key: key.key
}));
FileUtils.saveRealTime(result, username, 'gitlab', outputDir);
} else {
console.log(ColorUtils.green('No public SSH keys found'));
}
// For email leaks, we need to check projects
const projects = await GitLabApi.getUserProjects(userId);
let allLeakedEmails = [];
const emailsToName = new Map();
const emailsToProject = new Map();
if (Array.isArray(projects)) {
console.log(ColorUtils.green(`Found ${ColorUtils.yellow(projects.length)} public projects`));
// Limit to 10 projects to avoid rate limiting
const projectsToScan = projects.slice(0, 10);
const totalProjects = projectsToScan.length;
for (let i = 0; i < totalProjects; i++) {
const project = projectsToScan[i];
process.stdout.write(ColorUtils.green(`Scanning project ${ColorUtils.yellow(`${i + 1}/${totalProjects}`)}: ${ColorUtils.cyan(project.name || `Project ${project.id}`)}...`));
try {
const commits = await GitLabApi.getProjectCommits(project.id);
process.stdout.write('\r' + ' '.repeat(100) + '\r'); // Clear the line
// Handle empty repositories or API errors
if (!Array.isArray(commits)) {
if (commits && commits.message) {
if (commits.message.includes("404 Project Not Found") ||
commits.message.includes("Empty repository") ||
commits.message.includes("No commits")) {
console.log(ColorUtils.yellow(`Project ${ColorUtils.cyan(project.name || `Project ${project.id}`)} is empty or not accessible - skipping`));
} else {
console.warn(ColorUtils.yellow(`Error for project ${ColorUtils.cyan(project.name || `Project ${project.id}`)}: ${commits.message}`));
}
} else {
console.warn(ColorUtils.yellow(`Error fetching commits for project ${ColorUtils.cyan(project.name || `Project ${project.id}`)}`));
}
continue;
}
let newEmailsCount = 0;
for (const commit of commits) {
if (commit.author_email) {
// Track the project where this email was found
if (!emailsToProject.has(commit.author_email)) {
emailsToProject.set(commit.author_email, new Set());
}
emailsToProject.get(commit.author_email).add(project.name || `Project ${project.id}`);
// Track all names associated with this email
if (!emailsToName.has(commit.author_email)) {
emailsToName.set(commit.author_email, new Set());
if (commit.author_name === userInfo.name && !allLeakedEmails.includes(commit.author_email)) {
allLeakedEmails.push(commit.author_email);
newEmailsCount++;
}
}
// Add author name
emailsToName.get(commit.author_email).add(commit.author_name || "Unknown");
}
}
// Save progress periodically
if (newEmailsCount > 0 || i % 5 === 0) {
// Update and save result with new emails
const emailDetails = Array.from(emailsToName.entries())
.filter(([email]) => allLeakedEmails.includes(email))
.map(([email, namesSet]) => ({
email,
names: Array.from(namesSet),
sources: Array.from(emailsToProject.get(email) || [])
}));
result.leaked_emails = allLeakedEmails;
result.email_details = emailDetails;
result.scan_progress = `${i + 1}/${totalProjects} projects scanned`;
FileUtils.saveRealTime(result, username, 'gitlab', outputDir);
}
if (newEmailsCount > 0) {
console.log(ColorUtils.green(`Found ${ColorUtils.yellow(newEmailsCount)} new emails in ${ColorUtils.cyan(project.name || `Project ${project.id}`)}`));
} else {
console.log(ColorUtils.green(`Scanned project ${ColorUtils.yellow(`${i + 1}/${totalProjects}`)}: ${ColorUtils.cyan(project.name || `Project ${project.id}`)} - No new emails found`));
}
} catch (error) {
process.stdout.write('\r' + ' '.repeat(100) + '\r'); // Clear the line
console.error(ColorUtils.red(`Error scanning project ${project.id}: ${error.message}`));
// Save progress on error
const emailDetails = Array.from(emailsToName.entries())
.filter(([email]) => allLeakedEmails.includes(email))
.map(([email, namesSet]) => ({
email,
names: Array.from(namesSet),
sources: Array.from(emailsToProject.get(email) || [])
}));
result.leaked_emails = allLeakedEmails;
result.email_details = emailDetails;
result.scan_progress = `${i + 1}/${totalProjects} projects scanned`;
result.last_error = {
project: project.name || `Project ${project.id}`,
message: error.message,
timestamp: new Date().toISOString()
};
FileUtils.saveRealTime(result, username, 'gitlab', outputDir);
}
}
} else {
console.log(ColorUtils.yellow('No projects found or error fetching projects'));
}
// Prepare email details for display and output
const emailDetails = Array.from(emailsToName.entries())
.filter(([email]) => allLeakedEmails.includes(email))
.map(([email, namesSet]) => ({
email,
names: Array.from(namesSet),
sources: Array.from(emailsToProject.get(email) || [])
}));
// Display results
console.log(`\n${ColorUtils.green('Reconnaissance completed:')}`);
console.log(ColorUtils.green(`User: ${ColorUtils.yellow(`${userInfo.username} (${userInfo.name || 'No name'})`)}`));
console.log(ColorUtils.green(`URL: ${ColorUtils.cyan(userInfo.web_url)}`));
console.log(ColorUtils.green(`Public Keys: ${ColorUtils.yellow(Array.isArray(keys) ? keys.length : 0)}`));
console.log(ColorUtils.green(`Leaked Emails: ${ColorUtils.yellow(allLeakedEmails.length)}`));
if (Array.isArray(keys) && keys.length > 0) {
console.log(`\n${ColorUtils.yellow('Public SSH Keys:')}`);
keys.forEach((key, index) => {
console.log(ColorUtils.cyan(`Key #${index + 1}:`));
console.log(`Title: ${key.title || 'No title'}`);
console.log(`${key.key.substring(0, 40)}...`);
if (key.created_at) {
console.log(`Created: ${key.created_at}`);
}
if (key.expires_at) {
console.log(`Expires: ${key.expires_at}`);
}
});
}
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);
}
// Final update to result
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, 'gitlab');
}
// Save final output if requested
if (outputFormat) {
FileUtils.saveOutput(result, outputFormat, username, 'gitlab');
}
return result;
};
class GitLabUser {
static runRecon = runGitlabRecon;
}
module.exports = GitLabUser;