

Author: ICARAX Engineering Team
Topic: Ghost Account Abuse in Mass Reconnaissance Campaigns
Classification: Security Research / API Architecture
In modern reconnaissance campaigns, threat actors utilize "Ghost Accounts"—newly created, low-activity GitHub accounts—to perform wide-scale scraping of public repositories. By rotating these accounts, they attempt to bypass GitHub's standard rate limits to map out organizational structures, leaked secrets, and developer workflows.
This guide provides a technical framework for understanding how these patterns are implemented and, more importantly, how to architect systems to detect and mitigate such patterns.
To replicate the environment for security testing and pattern analysis, you will need:
public_repo scope only.venv (Python) or nvm (Node).# Create a dedicated research environment
python3 -m venv recon_env
source recon_env/bin/activate
# Install essential libraries
# requests: for HTTP calls
# python-dotenv: for secure env management
# ratelimit: to simulate/control request pacing
pip install requests python-dotenv ratelimit
# Initialize project
mkdir github-recon-study && cd github-recon-study
npm init -y
# Install dependencies
# axios: promise-based HTTP client
# dotenv: for environment variables
# p-limit: to manage concurrency
npm install axios dotenv p-limit
The following implementations demonstrate the Token Rotation Pattern, which is the core mechanism used by ghost accounts to circumvent rate limiting.
This script demonstrates how an automated system rotates through a pool of tokens to maintain high throughput.
import requests
import time
import os
from dotenv import load_dotenv
load_dotenv()
class GitHubReconEngine:
def __init__(self, tokens):
"""
Initializes the engine with a pool of tokens.
:param tokens: List of GitHub Personal Access Tokens
"""
self.tokens = tokens
self.token_index = 0
self.session = requests.Session()
def _get_next_token(self):
"""Rotates to the next available token in the pool."""
token = self.tokens[self.token_index]
self.token_index = (self.token_index + 1) % len(self.tokens)
return token
def search_repos(self, query: str):
"""
Performs a search across public repositories using the current token.
"""
token = self._get_next_token()
url = f"https://api.github.com/search/repositories?q={query}"
headers = {
"Authorization": f"token {token}",
"Accept": "application/vnd.github.v3+json"
}
try:
response = self.session.get(url, headers=headers)
# Handle Rate Limiting specifically
if response.status_code == 403:
print(f"[!] Rate limit hit or forbidden. Rotating token...")
return self.search_repos(query) # Recursive rotation
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"[-] API Error: {e}")
return None
# --- Usage Example ---
if __name__ == "__main__":
# In a real scenario, these would be loaded from a secure vault
GHOST_TOKENS = [
"ghp_your_token_1",
"ghp_your_token_2",
"ghp_your_token_3"
]
engine = GitHubReconEngine(GHOST_TOKENS)
# Searching for 'api_key' in repository names
results = engine.search_repos("filename:config.json extension:yml")
if results:
print(f"Found {results.get('total_count')} potential targets.")
This implementation uses an asynchronous approach with concurrency control, typical of high-performance reconnaissance tools.
import axios, { AxiosInstance } from 'axios';
import * as dotenv from 'dotenv';
dotenv.config();
interface ReconResult {
id: number;
name: string;
html_url: string;
}
class GhostAPIClient {
private tokens: string[];
private currentTokenIndex: number = 0;
private client: AxiosInstance;
constructor(tokens: string[]) {
this.tokens = tokens;
this.client = axios.create({
baseURL: 'https://api.github.com',
headers: { 'Accept': 'application/vnd.github.v3+json' }
});
}
private getNextToken(): string {
const token = this.tokens[this.currentTokenIndex];
this.currentTokenIndex = (this.currentTokenIndex + 1) % this.tokens.length;
return token;
}
/**
* Executes a search with automatic token rotation on 403 errors
*/
async searchRepositories(query: string): Promise<ReconResult[]> {
const token = this.getNextToken();
try {
const response = await this.client.get(`/search/repositories?q=${encodeURIComponent(query)}`, {
headers: { 'Authorization': `token ${token}` }
});
return response.data.items.map((item: any) => ({
id: item.id,
name: item.full_name,
html_url: item.html_url
}));
} catch (error: any) {
if (error.response?.status === 403) {
console.warn(`[!] 403 Detected. Rotating token...`);
return this.searchRepositories(query);
}
console.error(`[-] Search failed: ${error.message}`);
return [];
}
}
}
// --- Execution ---
async function runRecon() {
const tokens = process.env.GITHUB_TOKENS?.split(',') || [];
if (tokens.length === 0) {
console.error("No tokens provided in environment.");
return;
}
const recon = new GhostAPIClient(tokens);
const results = await recon.searchRepositories("secret_key");
console.log(`Found ${results.length} items.`);
results.forEach(r => console.log(`- ${r.name}: ${r.html_url}`));
}
runRecon();
Never hardcode tokens. Use a .env file for local development and environment variables in production.
File: .env
# Comma-separated list of ghost tokens
GITHUB_TOKENS=ghp_token1,ghp_token2,ghp_token3,ghp_token4
# Maximum concurrent requests per token
MAX_CONCURRENCY=5
# Timeout in milliseconds
API_TIMEOUT=5000
When analyzing reconnaissance traffic, look for these specific patterns:
keyword, then keyword + "password", then keyword + "config".User-Agent header remains identical across multiple accounts, a major indicator of automated botnets.| Error | Cause | Fix |
|---|---|---|
403 Forbidden | Rate limit reached or IP block. | Implement token rotation or increase delay (jitter). |
422 Unprocessable Entity | Invalid search query syntax. | Sanitize input and ensure encodeURIComponent is used. |
401 Unauthorized | Token expired or revoked. | Implement a validation check before adding token to pool. |
ETIMEDOUT | Network congestion or target blocking. | Increase timeout settings and implement exponential backoff. |
Before deploying any API-heavy system (whether for research or production):
X-RateLimit-Remaining headers..env files.Source: Security Week AI
Follow ICARAX for more AI insights and tutorials.
