mirror of
https://github.com/basecamp/once-campfire.git
synced 2026-08-23 07:21:06 +09:00
c860b51109
* Take the SSRF address policy from surfguard instead of keeping our own copy Four other apps carried this same classification and the five had drifted into four different ideas of what "internal" means. It now comes from the surfguard gem, which is their union. resolve and Violation keep their shapes, so the opengraph callers are unchanged. Two verdicts change. SIIT (::ffff:0:0:0/96) is now recognised. It is the third way an IPv4 address rides inside an IPv6 one and the only one Ruby has no predicate for -- ipv4_mapped?, ipv4_compat?, private?, loopback? and link_local? are all false for ::ffff:0:a9fe:a9fe, so it fell through to the IPv6 branch unrecognised and reached the metadata endpoint. Note the extra group: ::ffff:0:0:0/96 is not the IPv4-mapped ::ffff:0:0/96 the guard already refused, and the two do not overlap. The RFC 8215 local-use NAT64 block is now refused whole rather than decoded. Reading its low 32 bits as an embedded IPv4 is only correct for a /96 Pref64; the block can host any length from /32 to /96 and the position is not recoverable from the address alone (RFC 6052 2.2), so the decode reads the wrong octets. It is never globally routed, so refusing it costs nothing. The well-known /96 is still decoded and re-checked, so DNS64 for public sites on IPv6-only hosts keeps working. Resolution moves from Resolv.getaddress to Resolv.getaddresses, so the guard sees every address a host answers with rather than only the first. * Distinguish a DNS lookup failure from a private-IP block in the guard Advance the surfguard pin so resolve_public_ips raises Unresolvable when a host resolves to nothing and returns an empty list only when it resolves to a blocked address. The shim lets Unresolvable propagate as a lookup failure -- matching the old Resolv.getaddress behavior -- and reserves Violation for a resolved-but-blocked address, so a transient DNS miss is no longer reported as an SSRF attempt.
30 lines
1.2 KiB
Ruby
30 lines
1.2 KiB
Ruby
require "surfguard"
|
|
|
|
module RestrictedHTTP
|
|
class Violation < StandardError; end
|
|
|
|
# The address policy lives in the surfguard gem so this app, Basecamp, HEY and
|
|
# Fizzy classify "internal" the same way instead of each keeping a copy that
|
|
# drifts. The callers here hand a bare hostname in and pin the address that
|
|
# comes back, so this stays a hostname-in, address-out shim.
|
|
module PrivateNetworkGuard
|
|
extend self
|
|
|
|
# A hostname that resolves to nothing (NXDOMAIN, timeout, empty answer)
|
|
# raises Surfguard::Unresolvable, which we let propagate as a lookup failure
|
|
# -- the same way the old Resolv.getaddress guard raised Resolv::ResolvError,
|
|
# and the callers here already treat it as a fetch failure. The Violation is
|
|
# reserved for a host that resolves to a blocked address (an empty list back
|
|
# from resolve_public_ips), so a transient DNS miss is never misreported as
|
|
# an SSRF attempt.
|
|
def resolve(hostname)
|
|
Surfguard.resolve_public_ips(hostname).first or
|
|
raise Violation.new("Attempt to access private IP via #{hostname}")
|
|
end
|
|
|
|
def private_ip?(ip)
|
|
Surfguard.blocked_address?(ip)
|
|
end
|
|
end
|
|
end
|