75 lines
2.3 KiB
PHP
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 => '',
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|