Steuerkram/tests/74_SsrfPinningDualStackTest.php

72 lines
2.8 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare(strict_types=1);
use Steuerkram\Services\SsrfGuard;
/**
* Test 74: IP-Pinning funktioniert auch bei Dual-Stack-Hosts (A + AAAA).
*
* Regression zum Nextcloud-Import: resolvePinned() lieferte pro IP einen eigenen
* CURLOPT_RESOLVE-Eintrag. libcurl behandelt mehrere Einträge für dieselbe
* HOST:PORT-Kombination aber nicht als Alternativen – der zuletzt gesetzte
* verdrängt die vorherigen. Fiel dabei die IPv6-Adresse an und hatte der
* Container keine IPv6-Konnektivität, scheiterte jeder Request mit
* "Could not connect to server". Alle Adressen müssen daher in genau einem
* Eintrag als Liste stehen.
*/
class SsrfPinningDualStackTest extends TestCase
{
protected string $testName = 'Test 74: IP-Pinning bei Dual-Stack-Hosts (A + AAAA)';
public function run(): void
{
$entries = SsrfGuard::resolvePinned('https://example.com/dav');
$this->assertEquals(1, count($entries), 'resolvePinned() liefert genau einen CURLOPT_RESOLVE-Eintrag');
$entry = $entries[0];
$this->assert(
str_starts_with($entry, 'example.com:443:'),
'Eintrag beginnt mit "example.com:443:" (Eintrag: ' . $entry . ')'
);
$addresses = explode(',', substr($entry, strlen('example.com:443:')));
$this->assertNotEmpty($addresses, 'Der Eintrag enthält mindestens eine Adresse');
foreach ($addresses as $address) {
// IPv6 muss geklammert sein, sonst sind seine Doppelpunkte in der Liste nicht abgrenzbar
$isBracketed = str_starts_with($address, '[') && str_ends_with($address, ']');
$ip = $isBracketed ? substr($address, 1, -1) : $address;
$this->assert(
filter_var($ip, FILTER_VALIDATE_IP) !== false,
'Adresse "' . $address . '" ist eine gültige IP'
);
$this->assertEquals(
str_contains($ip, ':'),
$isBracketed,
'Genau IPv6-Adressen stehen in eckigen Klammern (Adresse: ' . $address . ')'
);
}
// Ein Dual-Stack-Host darf nicht dazu führen, dass eine Adressfamilie verlorengeht:
// beide aufgelösten Familien müssen im selben Eintrag landen.
$resolved = [];
foreach ((array)@dns_get_record('example.com', DNS_A + DNS_AAAA) as $record) {
if (!empty($record['ip'])) {
$resolved[] = $record['ip'];
}
if (!empty($record['ipv6'])) {
$resolved[] = $record['ipv6'];
}
}
foreach ($resolved as $ip) {
$expected = str_contains($ip, ':') ? '[' . $ip . ']' : $ip;
$this->assert(
in_array($expected, $addresses, true),
'Aufgelöste Adresse ' . $ip . ' ist im Pinning-Eintrag enthalten'
);
}
}
}