مراية لـ
https://github.com/postalserver/postal.git
تم المزامنة 2026-06-03 21:45:48 +00:00
fix(http): prevent SSRF in outbound webhook and HTTP endpoint requests
Webhook and HTTP message endpoint deliveries both flow through Postal::HTTP, which parsed the user-supplied URL and connected to its host with no address validation. An authenticated user could point a webhook or endpoint at a private, loopback or link-local address (e.g. 127.0.0.1, 169.254.169.254 cloud metadata, RFC1918 hosts) and make the server issue requests into its own internal network. Add Postal::HTTP::AddressGuard, which resolves the destination host and rejects private/loopback/link-local/reserved/multicast IPv4 and IPv6 addresses, then pins the connection to the validated address so it cannot be redirected via a DNS-rebinding race. Administrators can permit specific destinations via the new postal.allowed_request_destinations config option (hostnames or IP/CIDR ranges). Address selection only uses families this server can actually reach so we do not pin to an IPv6 address on a host without IPv6 connectivity; IPv4 is preferred for predictability. HTTPEndpoint now validates that its URL is a well-formed HTTP(S) URL with a host.
هذا الالتزام موجود في:
@@ -13,6 +13,7 @@ RSpec.describe WebhookDeliveryService do
|
||||
let(:response_body) { "OK" }
|
||||
|
||||
before do
|
||||
allow(Resolv).to receive(:getaddresses).with("example.com").and_return(["93.184.216.34"])
|
||||
stub_request(:post, webhook.url).to_return(status: response_status, body: response_body)
|
||||
end
|
||||
|
||||
@@ -116,5 +117,26 @@ RSpec.describe WebhookDeliveryService do
|
||||
expect { webhook_request.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
end
|
||||
|
||||
context "when the webhook URL resolves to a blocked (private) address" do
|
||||
let(:webhook_request) do
|
||||
create(:webhook_request, :locked, webhook: webhook, url: "http://internal.example.com/hook")
|
||||
end
|
||||
|
||||
before do
|
||||
allow(Resolv).to receive(:getaddresses).with("internal.example.com").and_return(["127.0.0.1"])
|
||||
end
|
||||
|
||||
it "does not make a request to the destination" do
|
||||
service.call
|
||||
expect(WebMock).not_to have_requested(:post, "http://internal.example.com/hook")
|
||||
end
|
||||
|
||||
it "records the failure and schedules a retry" do
|
||||
service.call
|
||||
expect(webhook_request.reload.attempts).to eq(1)
|
||||
expect(webhook_request.retry_after).to be_present
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
المرجع في مشكلة جديدة
حظر مستخدم