runJohnTheRipper
Crack password hashes using John the Ripper on Pentest MCP. Input hash data and specify command-line options to perform brute-force or dictionary attacks for penetration testing.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| hashData | Yes | String containing the password hashes, one per line. | |
| options | No | Array of command-line options for JtR. |
Implementation Reference
- src/index.ts:294-342 (handler)Core handler function for executing John the Ripper: sanitizes options, writes hashes to /tmp file, spawns 'john' for cracking and '--show' to retrieve cracked passwords, parses output, logs if professional mode, handles errors and cleanup.async function runJtR(hashData: string, rawOptions: string[] = []): Promise<{ fullOutput: string; cracked: string[] }> { let options: string[]; try { options = sanitizeOptions(rawOptions); } catch (error: any) { throw error; } if (!hashData) { throw new Error("Error: Hash data is required."); } // Use /tmp directory for temp files to avoid read-only directory issues const tempHashFile = path.join('/tmp', `jtr_hashes_${Date.now()}.txt`); let fullOutput = ""; let crackedPasswords: string[] = []; try { await fs.writeFile(tempHashFile, hashData); const crackingArgs = [...options, tempHashFile]; fullOutput += `--- Cracking Attempt ---\nExecuting: john ${crackingArgs.join(' ')}\n`; try { const crackResult = await runSpawnCommand('john', crackingArgs); fullOutput += `Exit Code: ${crackResult.code}\nStdout:\n${crackResult.stdout}\nStderr:\n${crackResult.stderr}\n`; } catch (error: any) { fullOutput += `Cracking command failed to execute: ${error.message}\n`; } const showArgs = ['--show', tempHashFile]; fullOutput += `--- Show Attempt ---\nExecuting: john ${showArgs.join(' ')}\n`; try { const showResult = await runSpawnCommand('john', showArgs); fullOutput += `Exit Code: ${showResult.code}\nStdout:\n${showResult.stdout}\nStderr:\n${showResult.stderr}\n`; crackedPasswords = showResult.stdout.split('\n').map(line => line.trim()).filter(line => line && !line.startsWith('0 passwords cracked') && !line.includes('guesses remaining')); } catch (error: any) { fullOutput += `Show command failed to execute: ${error.message}\n`; } await fs.unlink(tempHashFile); if (currentUserSession.mode === UserMode.PROFESSIONAL) { await logMessage(`Ran John the Ripper.\nOptions: ${options.join(' ')}\nCracked: ${crackedPasswords.length}.`); } return { fullOutput, cracked: crackedPasswords }; } catch (error: any) { console.error("Fatal error setting up John the Ripper execution:", error); try { await fs.unlink(tempHashFile); } catch { /* ignore */ } if (currentUserSession.mode === UserMode.PROFESSIONAL) { await logMessage(`John the Ripper FAILED fatally before execution.\nOptions: ${options.join(' ')}\nError: ${error.message}`); } throw new Error(`John the Ripper setup failed fatally: ${error.message}`); } }
- src/index.ts:832-838 (schema)Zod schema defining the input parameters for the runJohnTheRipper tool: hashData (required string of hashes) and options (optional array of JtR flags). Includes description with usage example.const jtrToolSchema = z.object({ hashData: z.string().describe("String containing the password hashes, one per line."), options: z.array(z.string()).optional().describe("Array of command-line options for JtR.") }).describe( "Crack password hashes using John the Ripper. Provide hashes and any JtR options." + " Run this after generating a wordlist. Example: `{\"hashData\":\"user:$1$hash\", \"options\":[\"--wordlist=/tmp/list.txt\"]}`" );
- src/index.ts:839-849 (registration)MCP server.tool registration for 'runJohnTheRipper': binds the schema, extracts args, calls runJtR, formats response with cracked passwords and full output based on user mode, handles errors.server.tool("runJohnTheRipper", jtrToolSchema.shape, async ({ hashData, options } /*, extra */) => { console.error(`Received JtR:`, { hashData: `len=${hashData.length}`, options }); if (currentUserSession.mode === UserMode.STUDENT) console.warn("[Student Mode] Executing JtR."); try { const { fullOutput, cracked } = await runJtR(hashData, options || []); const responseContent: any[] = [ { type: "text", text: `JtR finished. Found ${cracked.length} cracked.` } ]; if (cracked.length > 0) responseContent.push({ type: "text", text: "\n**Cracked:**\n" + cracked.join("\n") }); responseContent.push({ type: "text", text: "\n--- Full JtR Output ---\n" + fullOutput }); // Keep full output return { content: responseContent }; } catch (error: any) { return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true }; } });
- src/index.ts:273-291 (helper)Helper function to safely spawn external commands using child_process.spawn, captures stdout/stderr, handles errors like command not found, used by runJtR to execute 'john'.async function runSpawnCommand(command: string, args: string[]): Promise<{ stdout: string; stderr: string; code: number | null }> { return new Promise((resolve, reject) => { console.error(`Attempting to spawn: ${command} ${args.join(' ')}`); // Added for debugging const process = spawn(command, args); let stdout = ''; let stderr = ''; process.stdout.on('data', (data) => { stdout += data.toString(); }); process.stderr.on('data', (data) => { stderr += data.toString(); }); process.on('error', (error) => { // Explicitly catch spawn errors (e.g., command not found) console.error(`Spawn error for command "${command}": ${error.message}`); reject(new Error(`Failed to start command "${command}": ${error.message}`)); }); process.on('close', (code) => { console.error(`Command "${command}" exited with code ${code}`); // Added for debugging resolve({ stdout, stderr, code }); }); }); }
- src/index.ts:258-270 (helper)Helper to sanitize input options against SAFE_OPTION_REGEX to prevent command injection, used in runJtR to validate rawOptions.const SAFE_OPTION_REGEX = /^(?:-[a-zA-Z0-9]+|--[a-zA-Z0-9\-]+(?:=[^;&|`$\s\(\)\<\>\\]+)?|[^;&|`$\s\(\)\<\>\\]+)$/; function sanitizeOptions(options: string[]): string[] { const sanitized: string[] = []; for (const opt of options) { if (SAFE_OPTION_REGEX.test(opt)) { sanitized.push(opt); } else { throw new Error(`Invalid or potentially unsafe option detected: "${opt}". Only standard flags and simple arguments are allowed.`); } } return sanitized; }