mirror of
https://github.com/basecamp/once-campfire.git
synced 2026-08-29 01:52:38 +09:00
Guard push-subscription endpoints against SSRF
Web push delivery POSTed to the endpoint URL a user supplied when registering a subscription, with no scheme, host, or private-network check -- unlike the OpenGraph unfurl path, which already routes through the shared SSRF address policy (surfguard). Any authenticated user could register a subscription whose endpoint pointed at an internal address and have the server fetch it on every chat message: blind SSRF for internal recon and reachability probing, plus a thread-pool DoS on the delivery pool. Validate the endpoint when the subscription is saved: it must be HTTPS, its host must belong to a known browser push service (allowlist), and it must resolve to a public IP. On every delivery, re-resolve the host and pin the connection to that public IP so a later DNS rebind can't redirect the request to an internal address. If no public IP resolves at delivery time -- a rebind, or a subscription that predates this validation -- delivery is skipped rather than falling back to re-resolving the raw host. Adds model, controller, and delivery-pinning tests, plus a DNS stub helper for deterministic resolution in tests.
This commit is contained in:
@@ -3,10 +3,11 @@ require "test_helper"
|
||||
class Users::PushSubscriptionsControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
sign_in :david
|
||||
stub_web_push_dns_resolution
|
||||
end
|
||||
|
||||
test "create new push subscription" do
|
||||
subscription_params = { "endpoint" => "https://apple", "p256dh_key" => "123", "auth_key" => "456" }
|
||||
subscription_params = { "endpoint" => "https://fcm.googleapis.com/fcm/send/abc123", "p256dh_key" => "123", "auth_key" => "456" }
|
||||
|
||||
post user_push_subscriptions_url,
|
||||
params: { push_subscription: subscription_params }, headers: { "HTTP_USER_AGENT" => "Mozilla/5.0" }
|
||||
@@ -29,6 +30,27 @@ class Users::PushSubscriptionsControllerTest < ActionDispatch::IntegrationTest
|
||||
assert_response :ok
|
||||
end
|
||||
|
||||
test "rejects subscription with non-permitted endpoint" do
|
||||
subscription_params = { "endpoint" => "https://attacker.example.com/steal", "p256dh_key" => "123", "auth_key" => "456" }
|
||||
|
||||
assert_no_difference -> { Push::Subscription.count } do
|
||||
post user_push_subscriptions_url, params: { push_subscription: subscription_params }
|
||||
end
|
||||
|
||||
assert_response :unprocessable_entity
|
||||
end
|
||||
|
||||
test "rejects subscription with endpoint resolving to a private IP" do
|
||||
stub_dns_resolution("169.254.169.254")
|
||||
subscription_params = { "endpoint" => "https://fcm.googleapis.com/fcm/send/abc123", "p256dh_key" => "123", "auth_key" => "456" }
|
||||
|
||||
assert_no_difference -> { Push::Subscription.count } do
|
||||
post user_push_subscriptions_url, params: { push_subscription: subscription_params }
|
||||
end
|
||||
|
||||
assert_response :unprocessable_entity
|
||||
end
|
||||
|
||||
test "destroy a push subscription via dev mode" do
|
||||
assert_difference -> { Push::Subscription.count }, -1 do
|
||||
delete user_push_subscription_url(push_subscriptions(:david_chrome))
|
||||
|
||||
Reference in New Issue
Block a user