Files
osint/app/Osint/Runners/DnsRunner.php
T
cedry2k1992andClaude Sonnet 5 86f73c46b2 Add Gravatar and DNS Records collectors
Two pure-PHP collectors, no Python/venv/subprocess needed:
- GravatarRunner (email): checks gravatar.com/avatar/{md5}?d=404 for a
  200 vs 404. Scoped to that binary signal since Gravatar's profile
  JSON endpoint now requires API auth even for real, public profiles
  (confirmed against Gravatar's own creator's email).
- DnsRunner (domain): dns_get_record() for A/AAAA/MX/NS/TXT/CNAME. Raw
  payload is naturally keyed by record type, which the existing
  generic list-renderer on the investigation page turns into one
  collapsible section per type with no UI changes needed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-20 11:59:20 +03:00

75 lines
2.3 KiB
PHP

<?php
namespace App\Osint\Runners;
use App\Models\Target;
use App\Osint\Runner;
use App\Osint\RunnerException;
use App\Osint\RunnerResult;
class DnsRunner implements Runner
{
/**
* @var array<string, int>
*/
private const RECORD_TYPES = [
'A' => DNS_A,
'AAAA' => DNS_AAAA,
'MX' => DNS_MX,
'NS' => DNS_NS,
'TXT' => DNS_TXT,
'CNAME' => DNS_CNAME,
];
public function run(Target $target): RunnerResult
{
if ($target->type !== 'domain') {
throw new RunnerException('DNS lookup 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.');
}
$records = [];
foreach (self::RECORD_TYPES as $label => $type) {
// dns_get_record() emits a warning (not an exception) when a
// record type doesn't resolve for the domain — expected for
// most domains on most types, not a real error.
$found = @dns_get_record($target->value, $type);
if ($found === false || empty($found)) {
continue;
}
$records[$label] = array_values(array_unique(array_map(
fn (array $record) => $this->formatRecord($label, $record),
$found
)));
}
$total = array_sum(array_map('count', $records));
$summary = $total > 0
? sprintf('Found %d DNS record%s across %d type%s (%s).',
$total, $total === 1 ? '' : 's',
count($records), count($records) === 1 ? '' : 's',
implode(', ', array_keys($records)))
: 'No DNS records found.';
return new RunnerResult(raw: $records, summary: $summary);
}
private function formatRecord(string $label, array $record): string
{
return match ($label) {
'A', 'AAAA' => $record['ip'] ?? '',
'MX' => sprintf('%d %s', $record['pri'] ?? 0, $record['target'] ?? ''),
'NS', 'CNAME' => $record['target'] ?? '',
'TXT' => $record['txt'] ?? '',
default => '',
};
}
}