Initial commit: Laravel OSINT platform
Multi-user OSINT investigation tool with owner-controlled per-user and per-group access grants. Includes Sherlock, theHarvester, crt.sh, WHOIS, and ip-api.com collectors wired through a pluggable Runner/queue pipeline, investigation search, and printable/CSV/JSON reporting. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
<?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 SherlockRunner implements Runner
|
||||
{
|
||||
public function run(Target $target): RunnerResult
|
||||
{
|
||||
if ($target->type !== 'username') {
|
||||
throw new RunnerException('Sherlock only supports "username" targets.');
|
||||
}
|
||||
|
||||
if (! preg_match('/^[a-zA-Z0-9._-]+$/', $target->value)) {
|
||||
throw new RunnerException('Username contains characters that are not safe to check.');
|
||||
}
|
||||
|
||||
$binary = config('osint.python_venv').'/bin/sherlock';
|
||||
if (! is_executable($binary)) {
|
||||
throw new RunnerException("Sherlock binary not found or not executable at [{$binary}]. Run the setup in scripts/python/README.md.");
|
||||
}
|
||||
|
||||
$runDir = storage_path('app/osint/runs/'.Str::uuid());
|
||||
File::ensureDirectoryExists($runDir);
|
||||
|
||||
try {
|
||||
$result = Process::path($runDir)
|
||||
->timeout((int) config('osint.run_timeout'))
|
||||
->run([
|
||||
$binary,
|
||||
$target->value,
|
||||
'--csv',
|
||||
'--no-color',
|
||||
'--no-txt',
|
||||
'--timeout', '30',
|
||||
]);
|
||||
|
||||
if (! $result->successful()) {
|
||||
throw new RunnerException('Sherlock exited with an error: '.$result->errorOutput());
|
||||
}
|
||||
|
||||
$csvPath = $runDir.'/'.$target->value.'.csv';
|
||||
if (! File::exists($csvPath)) {
|
||||
throw new RunnerException('Sherlock did not produce the expected CSV output.');
|
||||
}
|
||||
|
||||
$rows = $this->parseCsv($csvPath);
|
||||
|
||||
$summary = count($rows) > 0
|
||||
? sprintf('Found on %d site%s: %s', count($rows), count($rows) === 1 ? '' : 's', collect($rows)->pluck('name')->implode(', '))
|
||||
: 'No matching profiles found.';
|
||||
|
||||
return new RunnerResult(raw: ['matches' => $rows], summary: $summary);
|
||||
} finally {
|
||||
File::deleteDirectory($runDir);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array{name: string, url: string, http_status: int|null}>
|
||||
*/
|
||||
private function parseCsv(string $path): array
|
||||
{
|
||||
$handle = fopen($path, 'r');
|
||||
$header = fgetcsv($handle);
|
||||
$rows = [];
|
||||
|
||||
while (($row = fgetcsv($handle)) !== false) {
|
||||
$data = array_combine($header, $row);
|
||||
$rows[] = [
|
||||
'name' => $data['name'],
|
||||
'url' => $data['url_user'],
|
||||
'http_status' => $data['http_status'] !== '' ? (int) $data['http_status'] : null,
|
||||
];
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
|
||||
return $rows;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user