Files
Redmine/lib/webhook_endpoint_validator.rb

173 lines
4.2 KiB
Ruby
Raw Normal View History

# frozen_string_literal: true
require 'uri'
class WebhookEndpointValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
return if value.blank?
unless self.class.safe_webhook_uri?(value)
record.errors.add attribute, :invalid
end
end
def self.safe_webhook_uri?(value)
uri = value.is_a?(URI) ? value : URI.parse(value)
return false if uri.nil?
return false unless valid_scheme?(uri.scheme)
return false unless valid_host?(uri.host)
return false unless valid_port?(uri.port)
true
rescue
Rails.logger.warn { "URI failed webhook safety checks: #{uri}" }
false
end
def self.valid_port?(port)
!BAD_PORTS.include?(port)
end
def self.valid_scheme?(scheme)
%w[http https].include?(scheme)
end
def self.blocked_hosts
@blocked_hosts ||= begin
ips = []
wildcards = []
hosts = []
Array(Redmine::Configuration['webhook_blocklist']).map(&:to_s).each do |block|
# We try to parse the block as an IP address first...
ips << IPAddr.new(block)
rescue IPAddr::Error
# If that failed, we assume it is a (wildcard) hostname
if block.start_with?('*.')
wildcards << Regexp.escape(block[2..])
else
hosts << Regexp.escape(block)
end
end
regex_parts = []
regex_parts << "(?:#{hosts.join('|')})" if hosts.any?
regex_parts << "(?:.*\\.)?(?:#{wildcards.join('|')})" if wildcards.any?
{
ips: ips.freeze,
host: regex_parts.any? ? /\A(?:#{regex_parts.join('|')})\z/i : nil
}.freeze
end
end
def self.valid_host?(host)
return false if host.blank?
return false if blocked_hosts[:host]&.match?(host)
Resolv.each_address(host) do |ip|
ipaddr = IPAddr.new(ip)
return false if ipaddr.link_local? || ipaddr.loopback?
return false if IPAddr.new('224.0.0.0/24').include?(ipaddr) # multicast
return false if blocked_hosts[:ips].any? { |net| net.include?(ipaddr) }
end
true
end
# A general port blacklist. Connections to these ports will not be allowed
# unless the protocol overrides.
#
# This list is to be kept in sync with "bad ports" as defined in the
# WHATWG Fetch standard at https://fetch.spec.whatwg.org/#port-blocking
#
# see also: https://github.com/mozilla/gecko-dev/blob/d55e89d48a8053ce45a74b0ec92c0ff6a9dcc43d/netwerk/base/nsIOService.cpp#L109-L199
#
BAD_PORTS = Set[
1, # tcpmux
7, # echo
9, # discard
11, # systat
13, # daytime
15, # netstat
17, # qotd
19, # chargen
20, # ftp-data
21, # ftp
22, # ssh
23, # telnet
25, # smtp
37, # time
42, # name
43, # nicname
53, # domain
69, # tftp
77, # priv-rjs
79, # finger
87, # ttylink
95, # supdup
101, # hostriame
102, # iso-tsap
103, # gppitnp
104, # acr-nema
109, # pop2
110, # pop3
111, # sunrpc
113, # auth
115, # sftp
117, # uucp-path
119, # nntp
123, # ntp
135, # loc-srv / epmap
137, # netbios
139, # netbios
143, # imap2
161, # snmp
179, # bgp
389, # ldap
427, # afp (alternate)
465, # smtp (alternate)
512, # print / exec
513, # login
514, # shell
515, # printer
526, # tempo
530, # courier
531, # chat
532, # netnews
540, # uucp
548, # afp
554, # rtsp
556, # remotefs
563, # nntp+ssl
587, # smtp (outgoing)
601, # syslog-conn
636, # ldap+ssl
989, # ftps-data
990, # ftps
993, # imap+ssl
995, # pop3+ssl
1719, # h323gatestat
1720, # h323hostcall
1723, # pptp
2049, # nfs
3659, # apple-sasl
4045, # lockd
4190, # sieve
5060, # sip
5061, # sips
6000, # x11
6566, # sane-port
6665, # irc (alternate)
6666, # irc (alternate)
6667, # irc (default)
6668, # irc (alternate)
6669, # irc (alternate)
6679, # osaut
6697, # irc+tls
10080 # amanda
].freeze
end