-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge remote-tracking branch 'upstream/master' into rules-list
- Loading branch information
Showing
7 changed files
with
335 additions
and
107 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,89 +1,64 @@ | ||
# Basic localhost patterns | ||
# RFC 2606 - Reserved Domain Names | ||
# https://datatracker.ietf.org/doc/html/rfc2606 | ||
*.test | ||
*.example | ||
*.invalid | ||
localhost | ||
127. | ||
::1 | ||
0.0.0.0 | ||
:: | ||
*.local | ||
local.* | ||
localhost.* | ||
*.localhost | ||
example.com | ||
example.net | ||
example.org | ||
|
||
# IPv4 patterns | ||
127.0.0.[0-9]+ | ||
# RFC 6761 - Special-Use Domain Names | ||
# https://datatracker.ietf.org/doc/html/rfc6761 | ||
*.local | ||
*.onion | ||
*.home.arpa | ||
|
||
# Private networks | ||
# RFC 1918 - Private Address Space | ||
# https://datatracker.ietf.org/doc/html/rfc1918 | ||
10.* | ||
172.(1[6-9]|2[0-9]|3[0-1]).* | ||
192.168.* | ||
169.254.* | ||
|
||
# Alternative representations | ||
0177.0000.0000.0001 | ||
0x7f000001 | ||
|
||
# Dotless decimal | ||
2130706433 # 127.0.0.1 | ||
3232235521 # 192.168.0.1 | ||
3232235777 # 192.168.1.1 | ||
# RFC 3330 - Special-Use IPv4 Addresses | ||
# https://datatracker.ietf.org/doc/html/rfc3330 | ||
127.* | ||
169.254.* | ||
0.0.0.0 | ||
|
||
# IPv6 patterns | ||
0:0:0:0:0:0:0:1 | ||
::0:1 | ||
[0:]+:[0:]*:[0:]*:[0:]*:[0:]*:[0:]*:[0:]*:1 | ||
# RFC 4291 - IPv6 Addressing Architecture | ||
# https://datatracker.ietf.org/doc/html/rfc4291 | ||
::1 | ||
fe80:* | ||
fc00:* | ||
fd* | ||
2001:db8:* | ||
::ffff:127.* | ||
::ffff:0:127.* | ||
64:ff9b::127.* | ||
::ffff:0:0:0 | ||
:: | ||
::* | ||
::ffff:* | ||
|
||
# RFC 4193 - Unique Local IPv6 Unicast Addresses | ||
# https://datatracker.ietf.org/doc/html/rfc4193 | ||
fc00:* | ||
fd00:* | ||
|
||
# Domain variants | ||
intranet | ||
internal | ||
# Common Internal Network Patterns (based on RFC 2606 and 6761) | ||
*.intranet | ||
*.internal | ||
*.localhost | ||
*.workgroup | ||
*.corp | ||
*.lan | ||
*.private | ||
*.home | ||
site | ||
*.dev | ||
*.test | ||
*.example | ||
*.invalid | ||
*.localdomain | ||
*.domain.local | ||
|
||
# mDNS and Bonjour | ||
*.local.mesh | ||
*.local.net | ||
*.local.home | ||
intranet.* | ||
internal.* | ||
corp.* | ||
lan.* | ||
|
||
# Additional internal patterns | ||
*.virtual | ||
*.vmware | ||
*.virtualbox | ||
*.docker | ||
*.vagrant | ||
*.vm | ||
*.development | ||
*.staging | ||
*.testing | ||
*.sandbox | ||
# Development Environments (based on RFC 2606) | ||
*.dev.test | ||
*.staging.test | ||
*.qa.test | ||
dev.example.* | ||
staging.example.* | ||
qa.example.* | ||
|
||
# Encoded variants | ||
%6C%6F%63%61%6C%68%6F%73%74 | ||
%6c%6f%63%61%6c%68%6f%73%74 | ||
bG9jYWxob3N0 | ||
# RFC 6052 - IPv6 Translation | ||
64:ff9b::* | ||
|
||
# DNS rebinding protection | ||
*.0sngrc.* | ||
*.1u0n.* | ||
*.10minutemail.* | ||
*.burpcollaborator.* | ||
*.interact.sh | ||
# RFC 3986 - URI Encoded Forms | ||
%6C%6F%63%61%6C%68%6F%73%74 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,79 +1,140 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'uri' | ||
require 'resolv' | ||
require_relative '../detectors/public_domain_detector' | ||
require 'ipaddr' | ||
require 'public_suffix' | ||
|
||
class MiniDefender::Rules::Url < MiniDefender::Rule | ||
ALLOWED_MODIFIERS = %w[https_only public no_ip] | ||
ALLOWED_MODIFIERS = %w[https public not_ip not_private] | ||
|
||
def initialize(modifiers = []) | ||
@modifiers = Array(modifiers).map(&:to_s) | ||
validate_modifiers! unless @modifiers.empty? | ||
|
||
unless @modifiers.empty? | ||
validate_modifiers! | ||
end | ||
|
||
@validation_error = nil | ||
end | ||
|
||
def self.signature | ||
'url' | ||
end | ||
|
||
def self.make(args) | ||
new(args) | ||
def self.make(modifiers) # no need to raise an error when no modifier is entered; as 'url' rule checks URL structure on its own | ||
new(modifiers) | ||
end | ||
|
||
def passes?(attribute, value, validator) | ||
# TODO: warning: URI.regexp is obsolete; use URI::DEFAULT_PARSER.make_regexp instead | ||
return false unless value.is_a?(String) && URI.regexp(%w[http https]).match?(value) | ||
unless value.is_a?(String) | ||
return false | ||
end | ||
|
||
begin | ||
uri = URI.parse(value) | ||
|
||
return true if @modifiers.empty? | ||
if uri.host.blank? || uri.scheme.blank? | ||
return false | ||
end | ||
|
||
unless %w[http https].include?(uri.scheme) | ||
@validation_error = 'URL protocol must be HTTP or HTTPS.' | ||
return false | ||
end | ||
|
||
if @modifiers.empty? | ||
return true | ||
end | ||
|
||
if @modifiers.include?('https') && uri.scheme != 'https' | ||
@validation_error = 'The URL must use HTTPS.' | ||
return false | ||
end | ||
|
||
if @modifiers.include?('public') && (!PublicSuffix.valid?(uri.host) || private_network?(uri.host)) | ||
@validation_error = 'The URL must use a valid public domain.' | ||
return false | ||
end | ||
|
||
if @modifiers.include?('not_ip') && ip_address?(uri.host) | ||
@validation_error = 'IP addresses are not allowed in URLs.' | ||
return false | ||
end | ||
|
||
return false if @modifiers.include?('https_only') && uri.scheme != 'https' | ||
return false if @modifiers.include?('public') && | ||
!MiniDefender::Detectors::PublicDomainDetector.public_domain?(uri.host) | ||
return false if @modifiers.include?('no_ip') && ip_address?(uri.host) | ||
if @modifiers.include?('not_private') && private_network?(uri.host) | ||
@validation_error = 'Private or reserved resources are not allowed.' | ||
return false | ||
end | ||
|
||
true | ||
rescue URI::InvalidURIError | ||
@validation_error = 'The field must contain a valid URL.' | ||
false | ||
rescue PublicSuffix::Error | ||
false | ||
end | ||
end | ||
|
||
def message(_attribute, value, _validator) | ||
return 'The field must contain a valid URL.' unless value.is_a?(String) | ||
|
||
begin | ||
uri = URI.parse(value) | ||
return 'The field must contain a valid URL.' if @modifiers.empty? | ||
|
||
return 'The URL must use HTTPS.' if @modifiers.include?('https_only') && uri.scheme != 'https' | ||
def message(attribute, value, validator) | ||
@validation_error || 'The field must contain a valid URL.' | ||
end | ||
|
||
if @modifiers.include?('public') && | ||
!MiniDefender::Detectors::PublicDomainDetector.public_domain?(uri.host) | ||
return 'The URL must use a valid public domain.' | ||
end | ||
def private_network?(host) | ||
unless host | ||
return false | ||
end | ||
|
||
return 'IP addresses are not allowed in URLs.' if @modifiers.include?('no_ip') && ip_address?(uri.host) | ||
host = host.downcase | ||
|
||
'The field must contain a valid URL.' | ||
rescue URI::InvalidURIError | ||
'The field must contain a valid URL.' | ||
end | ||
private_patterns.any? { |pattern| pattern.match?(host) } | ||
end | ||
|
||
private | ||
|
||
def validate_modifiers! | ||
invalid_modifiers = @modifiers - ALLOWED_MODIFIERS | ||
return if invalid_modifiers.empty? | ||
if invalid_modifiers.empty? | ||
return | ||
end | ||
|
||
raise ArgumentError, "Invalid URL modifiers: #{invalid_modifiers.join(', ')}" | ||
end | ||
|
||
def ip_address?(host) | ||
return false unless host | ||
unless host | ||
return false | ||
end | ||
|
||
begin | ||
IPAddr.new(host) | ||
true | ||
rescue IPAddr::InvalidAddressError | ||
false | ||
end | ||
end | ||
|
||
def private_patterns | ||
# Cache the result in class variable to avoid loading again | ||
# across multiple instances | ||
@@private_patterns ||= begin | ||
pattern_file = File.expand_path('../data/private_network_patterns.txt', __dir__) | ||
|
||
!!(host =~ Resolv::IPv4::Regex || host =~ Resolv::IPv6::Regex) | ||
File.readlines(pattern_file).filter_map do |line| | ||
line = line.strip | ||
|
||
if line.empty? || line.start_with?('#') | ||
next | ||
end | ||
|
||
# Pattern => regex (once) | ||
pattern = line | ||
.gsub('.', '\.') # escape dots | ||
.gsub('*', '.*') # wildcards => regex | ||
.gsub('[0-9]+', '\d+') # convert number ranges | ||
.gsub(/\[(.+?)\]/, '(\1)') # convert chars classes | ||
|
||
Regexp.new("^#{pattern}$", Regexp::IGNORECASE) | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
# frozen_string_literal: true | ||
|
||
module MiniDefender | ||
VERSION = "0.6.6" | ||
VERSION = "0.6.8" | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.