Compare commits

...
Author SHA1 Message Date
Jeppe B e2c2eb21cb Harden n8n webhook trigger URL validation 2026-06-02 00:32:35 +02:00
2 changed files with 48 additions and 4 deletions
+35 -4
View File
@@ -340,18 +340,49 @@ class n8n implements n8n_i
throw new Exception('Webhook target must not be empty.'); throw new Exception('Webhook target must not be empty.');
} }
if (filter_var($target, FILTER_VALIDATE_URL) !== false) {
return $target;
}
$baseUrl = trim((string)$this->config->webhook_base_url->getVariableValue()); $baseUrl = trim((string)$this->config->webhook_base_url->getVariableValue());
if ($baseUrl === '') { if ($baseUrl === '') {
throw new Exception('n8n webhook base URL is not configured.'); throw new Exception('n8n webhook base URL is not configured.');
} }
if (filter_var($target, FILTER_VALIDATE_URL) !== false) {
if (!$this->isAllowedWebhookAbsoluteUrl($target, $baseUrl)) {
throw new Exception('Webhook URL must use the configured n8n webhook host.');
}
return $target;
}
return rtrim($baseUrl, '/') . '/' . ltrim($target, '/'); return rtrim($baseUrl, '/') . '/' . ltrim($target, '/');
} }
private function isAllowedWebhookAbsoluteUrl(string $targetUrl, string $baseUrl): bool
{
$targetParts = parse_url($targetUrl);
$baseParts = parse_url($baseUrl);
if (!is_array($targetParts) || !is_array($baseParts)) {
return false;
}
$targetHost = strtolower((string)($targetParts['host'] ?? ''));
$baseHost = strtolower((string)($baseParts['host'] ?? ''));
if ($targetHost === '' || $baseHost === '' || $targetHost !== $baseHost) {
return false;
}
$targetScheme = strtolower((string)($targetParts['scheme'] ?? ''));
$baseScheme = strtolower((string)($baseParts['scheme'] ?? ''));
if ($targetScheme === '' || $baseScheme === '' || $targetScheme !== $baseScheme) {
return false;
}
$targetPort = (int)($targetParts['port'] ?? ($targetScheme === 'https' ? 443 : 80));
$basePort = (int)($baseParts['port'] ?? ($baseScheme === 'https' ? 443 : 80));
return $targetPort === $basePort;
}
/** /**
* @throws Exception * @throws Exception
*/ */
@@ -0,0 +1,13 @@
<?php
it('restricts absolute webhook URLs to configured n8n webhook host', function (): void {
$classFile = app_path('classes/n8n.php');
$content = file_get_contents($classFile);
expect($content)->not->toBeFalse();
expect($content)->toContain('isAllowedWebhookAbsoluteUrl');
expect($content)->toContain('Webhook URL must use the configured n8n webhook host.');
expect($content)->toContain("$targetHost !== $baseHost");
expect($content)->toContain("$targetScheme !== $baseScheme");
expect($content)->toContain('return $targetPort === $basePort;');
});