Prevent webhooks to private addresses (SSRF) [add gem]

Best reviewed with whitespace hidden.

Unfortunately the spec isn't allowed in CI. But it worked on my environment, I promise.
I chose `xit` so that it doesn't run unnecessarily. Perhaps we could use `pending` instead, which would execute, and notify us if it suddenly started working one day. But I doubt it.
This commit is contained in:
David Cook
2022-09-16 14:42:34 +10:00
parent 9741935955
commit de9546587a
4 changed files with 53 additions and 3 deletions

View File

@@ -137,6 +137,7 @@ gem 'view_component_reflex', '3.1.14.pre9'
gem 'mini_portile2', '~> 2.8'
gem "faraday"
gem "private_address_check"
group :production, :staging do
gem 'ddtrace'

View File

@@ -474,6 +474,7 @@ GEM
ttfunk
pg (1.2.3)
power_assert (2.0.2)
private_address_check (0.5.0)
pry (0.13.1)
coderay (~> 1.1)
method_source (~> 1.0)
@@ -844,6 +845,7 @@ DEPENDENCIES
paypal-sdk-merchant (= 1.117.2)
pdf-reader
pg (~> 1.2.3)
private_address_check
pry (~> 0.13.0)
puma
rack-mini-profiler (< 3.0.0)

View File

@@ -1,6 +1,8 @@
# frozen_string_literal: true
require "faraday"
require "private_address_check"
require "private_address_check/tcpsocket_ext"
# Deliver a webhook payload
# As a delayed job, it can run asynchronously and handle retries.
@@ -19,7 +21,16 @@ class WebhookDeliveryJob < ApplicationJob
data: payload,
}
notify_endpoint(url, body)
# Request user-submitted url, preventing any private connections being made
# (SSRF).
# This method may allow the socket to open, but is necessary in order to
# protect from TOC/TOU.
# Note that private_address_check provides some methods for pre-validating,
# but they're not as comprehensive and so unnecessary here. Simply
# momentarily opening sockets probably can't cause DoS or other damage.
PrivateAddressCheck.only_public_connections do
notify_endpoint(url, body)
end
end
def notify_endpoint(url, body)

View File

@@ -36,8 +36,44 @@ describe WebhookDeliveryJob do
end
end
# To be implemented in following commits
pending "can't access local secrets" # see https://medium.com/in-the-weeds/all-about-paperclips-cve-2017-0889-server-side-request-forgery-ssrf-vulnerability-8cb2b1c96fe8
# Ensure responses from a local network aren't allowed, to prevent a user
# seeing a private response or initiating an unauthorised action (SSRF).
# Currently, we're not doing anything with responses. When we do, we should
# update this to confirm the response isn't exposed.
describe "server side request forgery" do
describe "private addresses" do
private_addresses = [
"http://127.0.0.1/all_the_secrets",
"http://localhost/all_the_secrets",
]
private_addresses.each do |url|
# Unfortunately this isn't allowed in CI, but it should work in your
# development environment.
xit "rejects private address #{url}" do
expect {
WebhookDeliveryJob.perform_now(url, event, data)
}.to raise_error(PrivateAddressCheck::PrivateConnectionAttemptedError)
end
end
end
describe "redirects" do
it "doesn't follow a redirect" do
other_url = 'http://localhost/all_the_secrets'
stub_request(:post, url).
to_return(status: 302, headers: { 'Location' => other_url })
stub_request(:any, other_url)
expect {
subject.perform_now
}.to raise_error(StandardError, "302")
expect(a_request(:any, other_url)).not_to have_been_made
end
end
end
# Exceptions are considered a job failure, which the job runner
# (Sidekiq) and/or ActiveJob will handle and retry later.