Files
osint/app/Osint/Runners/TheHarvesterRunner.php
T

82 lines
2.8 KiB
PHP
Raw Normal View History

2026-07-20 10:22:05 +03:00
<?php
namespace App\Osint\Runners;
use App\Models\Target;
use App\Osint\Runner;
use App\Osint\RunnerException;
use App\Osint\RunnerResult;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Process;
use Illuminate\Support\Str;
class TheHarvesterRunner implements Runner
{
public function run(Target $target): RunnerResult
{
if ($target->type !== 'domain') {
throw new RunnerException('theHarvester only supports "domain" targets.');
}
if (! preg_match('/^([a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/', $target->value)) {
throw new RunnerException('Domain contains characters that are not safe to look up.');
}
$python = config('osint.theharvester_venv').'/bin/python3';
$script = config('osint.theharvester_script');
if (! is_executable($python) || ! is_file($script)) {
throw new RunnerException('theHarvester not set up. Run scripts/python/setup.sh.');
}
$runDir = storage_path('app/osint/runs/'.Str::uuid());
File::ensureDirectoryExists($runDir);
try {
$result = Process::path($runDir)
->timeout((int) config('osint.run_timeout'))
->run([
$python,
$script,
'-d', $target->value,
'-b', config('osint.theharvester_sources'),
'-f', 'result',
]);
if (! $result->successful()) {
throw new RunnerException('theHarvester exited with an error: '.$result->errorOutput());
}
$jsonPath = $runDir.'/result.json';
if (! File::exists($jsonPath)) {
// No results at all means theHarvester skips writing a file.
return new RunnerResult(raw: [], summary: 'No results found.');
}
$data = json_decode(File::get($jsonPath), true) ?? [];
$hosts = collect($data['hosts'] ?? [])
->map(fn (string $entry) => explode(':', $entry, 2)[0])
->unique()
->sort()
->values();
$emails = collect($data['emails'] ?? [])->unique()->sort()->values();
$ips = collect($data['ips'] ?? [])->unique()->sort()->values();
$summary = sprintf(
'Found %d host%s, %d email%s, %d IP%s.',
$hosts->count(), $hosts->count() === 1 ? '' : 's',
$emails->count(), $emails->count() === 1 ? '' : 's',
$ips->count(), $ips->count() === 1 ? '' : 's',
);
return new RunnerResult(
raw: ['hosts' => $hosts->all(), 'emails' => $emails->all(), 'ips' => $ips->all()],
summary: $summary,
);
} finally {
File::deleteDirectory($runDir);
}
}
}