aikido-zen 1.0.2-aarch64-linux

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. checksums.yaml +7 -0
  2. data/.aikido +6 -0
  3. data/.ruby-version +1 -0
  4. data/.simplecov +32 -0
  5. data/.standard.yml +3 -0
  6. data/LICENSE +674 -0
  7. data/README.md +148 -0
  8. data/Rakefile +67 -0
  9. data/benchmarks/README.md +22 -0
  10. data/benchmarks/rails7.1_benchmark.js +1 -0
  11. data/benchmarks/rails7.1_sql_injection.js +102 -0
  12. data/docs/banner.svg +202 -0
  13. data/docs/config.md +133 -0
  14. data/docs/proxy.md +10 -0
  15. data/docs/rails.md +112 -0
  16. data/docs/troubleshooting.md +62 -0
  17. data/lib/aikido/zen/actor.rb +146 -0
  18. data/lib/aikido/zen/agent/heartbeats_manager.rb +66 -0
  19. data/lib/aikido/zen/agent.rb +181 -0
  20. data/lib/aikido/zen/api_client.rb +145 -0
  21. data/lib/aikido/zen/attack.rb +217 -0
  22. data/lib/aikido/zen/attack_wave/helpers.rb +457 -0
  23. data/lib/aikido/zen/attack_wave.rb +88 -0
  24. data/lib/aikido/zen/background_worker.rb +52 -0
  25. data/lib/aikido/zen/cache.rb +91 -0
  26. data/lib/aikido/zen/capped_collections.rb +86 -0
  27. data/lib/aikido/zen/collector/event.rb +238 -0
  28. data/lib/aikido/zen/collector/hosts.rb +30 -0
  29. data/lib/aikido/zen/collector/routes.rb +71 -0
  30. data/lib/aikido/zen/collector/sink_stats.rb +95 -0
  31. data/lib/aikido/zen/collector/stats.rb +122 -0
  32. data/lib/aikido/zen/collector/users.rb +32 -0
  33. data/lib/aikido/zen/collector.rb +223 -0
  34. data/lib/aikido/zen/config.rb +312 -0
  35. data/lib/aikido/zen/context/rack_request.rb +27 -0
  36. data/lib/aikido/zen/context/rails_request.rb +47 -0
  37. data/lib/aikido/zen/context.rb +145 -0
  38. data/lib/aikido/zen/detached_agent/agent.rb +79 -0
  39. data/lib/aikido/zen/detached_agent/front_object.rb +41 -0
  40. data/lib/aikido/zen/detached_agent/server.rb +78 -0
  41. data/lib/aikido/zen/detached_agent.rb +2 -0
  42. data/lib/aikido/zen/errors.rb +107 -0
  43. data/lib/aikido/zen/event.rb +116 -0
  44. data/lib/aikido/zen/helpers.rb +24 -0
  45. data/lib/aikido/zen/internals.rb +123 -0
  46. data/lib/aikido/zen/libzen-v0.1.48-aarch64-linux.so +0 -0
  47. data/lib/aikido/zen/middleware/allowed_address_checker.rb +26 -0
  48. data/lib/aikido/zen/middleware/attack_wave_protector.rb +46 -0
  49. data/lib/aikido/zen/middleware/context_setter.rb +26 -0
  50. data/lib/aikido/zen/middleware/fork_detector.rb +23 -0
  51. data/lib/aikido/zen/middleware/middleware.rb +11 -0
  52. data/lib/aikido/zen/middleware/rack_throttler.rb +50 -0
  53. data/lib/aikido/zen/middleware/request_tracker.rb +197 -0
  54. data/lib/aikido/zen/outbound_connection.rb +62 -0
  55. data/lib/aikido/zen/outbound_connection_monitor.rb +23 -0
  56. data/lib/aikido/zen/package.rb +22 -0
  57. data/lib/aikido/zen/payload.rb +50 -0
  58. data/lib/aikido/zen/rails_engine.rb +53 -0
  59. data/lib/aikido/zen/rate_limiter/breaker.rb +61 -0
  60. data/lib/aikido/zen/rate_limiter/bucket.rb +76 -0
  61. data/lib/aikido/zen/rate_limiter/result.rb +31 -0
  62. data/lib/aikido/zen/rate_limiter.rb +50 -0
  63. data/lib/aikido/zen/request/heuristic_router.rb +115 -0
  64. data/lib/aikido/zen/request/rails_router.rb +92 -0
  65. data/lib/aikido/zen/request/schema/auth_discovery.rb +86 -0
  66. data/lib/aikido/zen/request/schema/auth_schemas.rb +54 -0
  67. data/lib/aikido/zen/request/schema/builder.rb +121 -0
  68. data/lib/aikido/zen/request/schema/definition.rb +107 -0
  69. data/lib/aikido/zen/request/schema/empty_schema.rb +28 -0
  70. data/lib/aikido/zen/request/schema.rb +87 -0
  71. data/lib/aikido/zen/request.rb +88 -0
  72. data/lib/aikido/zen/route.rb +96 -0
  73. data/lib/aikido/zen/runtime_settings/endpoints.rb +78 -0
  74. data/lib/aikido/zen/runtime_settings/ip_set.rb +36 -0
  75. data/lib/aikido/zen/runtime_settings/protection_settings.rb +62 -0
  76. data/lib/aikido/zen/runtime_settings/rate_limit_settings.rb +47 -0
  77. data/lib/aikido/zen/runtime_settings.rb +66 -0
  78. data/lib/aikido/zen/scan.rb +75 -0
  79. data/lib/aikido/zen/scanners/path_traversal/helpers.rb +68 -0
  80. data/lib/aikido/zen/scanners/path_traversal_scanner.rb +64 -0
  81. data/lib/aikido/zen/scanners/shell_injection/helpers.rb +159 -0
  82. data/lib/aikido/zen/scanners/shell_injection_scanner.rb +65 -0
  83. data/lib/aikido/zen/scanners/sql_injection_scanner.rb +94 -0
  84. data/lib/aikido/zen/scanners/ssrf/dns_lookups.rb +27 -0
  85. data/lib/aikido/zen/scanners/ssrf/private_ip_checker.rb +97 -0
  86. data/lib/aikido/zen/scanners/ssrf_scanner.rb +266 -0
  87. data/lib/aikido/zen/scanners/stored_ssrf_scanner.rb +55 -0
  88. data/lib/aikido/zen/scanners.rb +7 -0
  89. data/lib/aikido/zen/sink.rb +118 -0
  90. data/lib/aikido/zen/sinks/action_controller.rb +85 -0
  91. data/lib/aikido/zen/sinks/async_http.rb +80 -0
  92. data/lib/aikido/zen/sinks/curb.rb +113 -0
  93. data/lib/aikido/zen/sinks/em_http.rb +83 -0
  94. data/lib/aikido/zen/sinks/excon.rb +118 -0
  95. data/lib/aikido/zen/sinks/file.rb +153 -0
  96. data/lib/aikido/zen/sinks/http.rb +93 -0
  97. data/lib/aikido/zen/sinks/httpclient.rb +95 -0
  98. data/lib/aikido/zen/sinks/httpx.rb +78 -0
  99. data/lib/aikido/zen/sinks/kernel.rb +33 -0
  100. data/lib/aikido/zen/sinks/mysql2.rb +31 -0
  101. data/lib/aikido/zen/sinks/net_http.rb +101 -0
  102. data/lib/aikido/zen/sinks/patron.rb +103 -0
  103. data/lib/aikido/zen/sinks/pg.rb +72 -0
  104. data/lib/aikido/zen/sinks/resolv.rb +62 -0
  105. data/lib/aikido/zen/sinks/socket.rb +85 -0
  106. data/lib/aikido/zen/sinks/sqlite3.rb +46 -0
  107. data/lib/aikido/zen/sinks/trilogy.rb +31 -0
  108. data/lib/aikido/zen/sinks/typhoeus.rb +78 -0
  109. data/lib/aikido/zen/sinks.rb +36 -0
  110. data/lib/aikido/zen/sinks_dsl.rb +250 -0
  111. data/lib/aikido/zen/synchronizable.rb +24 -0
  112. data/lib/aikido/zen/system_info.rb +80 -0
  113. data/lib/aikido/zen/version.rb +10 -0
  114. data/lib/aikido/zen/worker.rb +87 -0
  115. data/lib/aikido/zen.rb +303 -0
  116. data/lib/aikido-zen.rb +3 -0
  117. data/placeholder/.gitignore +4 -0
  118. data/placeholder/README.md +11 -0
  119. data/placeholder/Rakefile +75 -0
  120. data/placeholder/lib/placeholder.rb.template +3 -0
  121. data/placeholder/placeholder.gemspec.template +20 -0
  122. data/tasklib/bench.rake +94 -0
  123. data/tasklib/libzen.rake +133 -0
  124. data/tasklib/wrk.rb +88 -0
  125. metadata +214 -0
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aikido::Zen::Scanners::ShellInjection
4
+ module Helpers
5
+ ESCAPE_CHARS = %W[' "]
6
+ DANGEROUS_CHARS_INSIDE_DOUBLE_QUOTES = %W[$ ` \\ !]
7
+ DANGEROUS_CHARS = [
8
+ "#", "!", '"', "$", "&", "'", "(", ")", "*", ";", "<", "=", ">", "?",
9
+ "[", "\\", "]", "^", "`", "{", "|", "}", " ", "\n", "\t", "~"
10
+ ]
11
+
12
+ COMMANDS = %w[sleep shutdown reboot poweroff halt ifconfig chmod chown ping
13
+ ssh scp curl wget telnet kill killall rm mv cp touch echo cat head
14
+ tail grep find awk sed sort uniq wc ls env ps who whoami id w df du
15
+ pwd uname hostname netstat passwd arch printenv logname pstree hostnamectl
16
+ set lsattr killall5 dmesg history free uptime finger top shopt :]
17
+
18
+ PATH_PREFIXES = %w[/bin/ /sbin/ /usr/bin/ /usr/sbin/ /usr/local/bin/ /usr/local/sbin/]
19
+
20
+ SEPARATORS = [" ", "\t", "\n", ";", "&", "|", "(", ")", "<", ">"]
21
+
22
+ # @param command [string]
23
+ # @param user_input [string]
24
+ def self.is_safely_encapsulated(command, user_input)
25
+ segments = command.split(user_input)
26
+
27
+ # The next condition is merely here to be compliant with what javascript does when splitting strings:
28
+ # From js doc https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split
29
+ # > If separator appears at the beginning (or end) of the string, it still has the effect of splitting,
30
+ # > resulting in an empty (i.e. zero length) string appearing at the first (or last) position of
31
+ # > the returned array.
32
+ # This is necessary because this code is ported form the firewall-node code.
33
+ if user_input.length > 1
34
+ if command.start_with? user_input
35
+ segments.unshift ""
36
+ end
37
+
38
+ if command.end_with? user_input
39
+ segments << ""
40
+ end
41
+ end
42
+
43
+ # Call the helper function to get current and next segments
44
+ get_current_and_next_segments(segments).all? do |segments_pair|
45
+ char_before_user_input = segments_pair[:current_segment][-1]
46
+ char_after_user_input = segments_pair[:next_segment][0]
47
+
48
+ # Check if the character before is an escape character
49
+ is_escape_char = ESCAPE_CHARS.include?(char_before_user_input)
50
+
51
+ unless is_escape_char
52
+ next false
53
+ end
54
+
55
+ # If characters before and after the user input do not match, return false
56
+ next false if char_before_user_input != char_after_user_input
57
+
58
+ # If user input contains the escape character, return false
59
+ next false if user_input.include?(char_before_user_input)
60
+
61
+ # Handle dangerous characters inside double quotes
62
+ if char_before_user_input == '"' && DANGEROUS_CHARS_INSIDE_DOUBLE_QUOTES.any? { |char| user_input.include?(char) }
63
+ next false
64
+ end
65
+
66
+ next true
67
+ end
68
+ end
69
+
70
+ def self.get_current_and_next_segments(segments)
71
+ segments.each_cons(2).map { |current_segment, next_segment| {current_segment: current_segment, next_segment: next_segment} }
72
+ end
73
+
74
+ # Helper function for sorting commands by length (longer commands first)
75
+ def self.by_length(a, b)
76
+ b.length - a.length
77
+ end
78
+
79
+ # Escape characters with special meaning either inside or outside character sets.
80
+ # Use a simple backslash escape when it’s always valid, and a `\xnn` escape when the simpler
81
+ # form would be disallowed by Unicode patterns’ stricter grammar.
82
+ #
83
+ # Inspired by https://github.com/sindresorhus/escape-string-regexp/
84
+ def self.escape_string_regexp(string)
85
+ string.gsub(/[|\\{}()\[\]^$+*?.]/) { "\\#{$&}" }.gsub("-", '\\x2d')
86
+ end
87
+
88
+ # Construct the regex for commands
89
+ COMMANDS_REGEX = Regexp.new(
90
+ "([/.]*((#{PATH_PREFIXES.map { |p| Helpers.escape_string_regexp(p) }.join("|")})?((#{COMMANDS.sort(&method(:by_length)).join("|")}))))",
91
+ Regexp::IGNORECASE
92
+ )
93
+
94
+ def self.contains_shell_syntax(command, user_input)
95
+ # Check if input is only whitespace
96
+ return false if user_input.strip.empty?
97
+
98
+ # Check if the user input contains any dangerous characters
99
+ if DANGEROUS_CHARS.any? { |c| user_input.include?(c) }
100
+ return true
101
+ end
102
+
103
+ # If the command is exactly the same as the user input, check if it matches the regex
104
+ if command == user_input
105
+ return match_all(command, COMMANDS_REGEX).any? do |match|
106
+ match[:match].length == command.length && match[:match] == command
107
+ end
108
+ end
109
+
110
+ # Check if the command contains a commonly used command
111
+ match_all(command, COMMANDS_REGEX).each do |match|
112
+ # We found a command like `rm` or `/sbin/shutdown` in the command
113
+ # Check if the command is the same as the user input
114
+ # If it's not the same, continue searching
115
+ next if user_input != match[:match]
116
+
117
+ # Otherwise, we'll check if the command is surrounded by separators
118
+ # These separators are used to separate commands and arguments
119
+ # e.g. `rm<space>-rf`
120
+ # e.g. `ls<newline>whoami`
121
+ # e.g. `echo<tab>hello` Check if the command is surrounded by separators
122
+ char_before = if match[:index] - 1 < 0
123
+ nil
124
+ else
125
+ command[match[:index] - 1]
126
+ end
127
+
128
+ char_after = if match[:index] + match[:match].length >= command.length
129
+ nil
130
+ else
131
+ command[match[:index] + match[:match].length]
132
+ end
133
+
134
+ # e.g. `<separator>rm<separator>`
135
+ if SEPARATORS.include?(char_before) && SEPARATORS.include?(char_after)
136
+ return true
137
+ end
138
+
139
+ # e.g. `<separator>rm`
140
+ if SEPARATORS.include?(char_before) && char_after.nil?
141
+ return true
142
+ end
143
+
144
+ # e.g. `rm<separator>`
145
+ if char_before.nil? && SEPARATORS.include?(char_after)
146
+ return true
147
+ end
148
+ end
149
+
150
+ false
151
+ end
152
+
153
+ def self.match_all(string, regex)
154
+ string.enum_for(:scan, regex).map do |match|
155
+ {match: match[0], index: $~.begin(0)}
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "shell_injection/helpers"
4
+
5
+ module Aikido::Zen
6
+ module Scanners
7
+ class ShellInjectionScanner
8
+ def self.skips_on_nil_context?
9
+ true
10
+ end
11
+
12
+ # @param command [String]
13
+ # @param sink [Aikido::Zen::Sink]
14
+ # @param context [Aikido::Zen::Context]
15
+ # @param operation [Symbol, String]
16
+ #
17
+ def self.call(command:, sink:, context:, operation:)
18
+ context.payloads.each do |payload|
19
+ next unless new(command, payload.value.to_s).attack?
20
+
21
+ return Attacks::ShellInjectionAttack.new(
22
+ sink: sink,
23
+ input: payload,
24
+ command: command,
25
+ context: context,
26
+ operation: "#{sink.operation}.#{operation}",
27
+ stack: Aikido::Zen.clean_stack_trace
28
+ )
29
+ end
30
+
31
+ nil
32
+ end
33
+
34
+ # @param command [String]
35
+ # @param input [String]
36
+ def initialize(command, input)
37
+ @command = command
38
+ @input = input
39
+ end
40
+
41
+ def attack?
42
+ # Block single ~ character. For example `echo ~`
43
+ if @input == "~"
44
+ if @command.size > 1 && @command.include?("~")
45
+ return true
46
+ end
47
+ end
48
+
49
+ # we ignore single character since they don't pose a big threat.
50
+ # They are only able to crash the shell, not execute arbitraty commands.
51
+ return false if @input.size <= 1
52
+
53
+ # We ignore cases where the user input is longer than the command because
54
+ # the user input can't be part of the command
55
+ return false if @input.size > @command.size
56
+
57
+ return false unless @command.include?(@input)
58
+
59
+ return false if ShellInjection::Helpers.is_safely_encapsulated @command, @input
60
+
61
+ ShellInjection::Helpers.contains_shell_syntax @command, @input
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../attack"
4
+ require_relative "../internals"
5
+
6
+ module Aikido::Zen
7
+ module Scanners
8
+ class SQLInjectionScanner
9
+ def self.skips_on_nil_context?
10
+ true
11
+ end
12
+
13
+ # Checks if the given SQL query may have dangerous user input injected,
14
+ # and returns an Attack if so, based on the current request.
15
+ #
16
+ # @param query [String]
17
+ # @param context [Aikido::Zen::Context]
18
+ # @param sink [Aikido::Zen::Sink] the Sink that is running the scan.
19
+ # @param dialect [Symbol] one of +:mysql+, +:postgesql+, or +:sqlite+.
20
+ # @param operation [Symbol, String] name of the method being scanned.
21
+ # Expects +sink.operation+ being set to get the full module/name combo.
22
+ #
23
+ # @return [Aikido::Zen::Attack, nil] an Attack if any user input is
24
+ # detected to be attempting a SQL injection, or nil if this is safe.
25
+ #
26
+ # @raise [Aikido::Zen::InternalsError] if an error occurs when loading or
27
+ # calling zenlib. See Sink#scan.
28
+ def self.call(query:, dialect:, sink:, context:, operation:)
29
+ dialect = DIALECTS.fetch(dialect) do
30
+ Aikido::Zen.config.logger.warn "Unknown SQL dialect #{dialect.inspect}"
31
+ DIALECTS[:common]
32
+ end
33
+
34
+ context.payloads.each do |payload|
35
+ next unless new(query, payload.value.to_s, dialect).attack?
36
+
37
+ return Attacks::SQLInjectionAttack.new(
38
+ sink: sink,
39
+ query: query,
40
+ input: payload,
41
+ dialect: dialect,
42
+ context: context,
43
+ operation: "#{sink.operation}.#{operation}",
44
+ stack: Aikido::Zen.clean_stack_trace
45
+ )
46
+ end
47
+
48
+ nil
49
+ end
50
+
51
+ def initialize(query, input, dialect)
52
+ @query = query.downcase
53
+ @input = input.downcase
54
+ @dialect = dialect
55
+ end
56
+
57
+ def attack?
58
+ # Ignore single char inputs since they shouldn't be able to do much harm
59
+ return false if @input.length <= 1
60
+
61
+ # If the input is longer than the query, then it is not part of it
62
+ return false if @input.length > @query.length
63
+
64
+ # If the input is not included in the query at all, then we are safe
65
+ return false unless @query.include?(@input)
66
+
67
+ # If the input is solely alphanumeric, we can ignore it
68
+ return false if /\A[[:alnum:]_]+\z/i.match?(@input)
69
+
70
+ # If the input is a comma-separated list of numbers, ignore it.
71
+ return false if /\A(?:\d+(?:,\s*)?)+\z/i.match?(@input)
72
+
73
+ Internals.detect_sql_injection(@query, @input, @dialect)
74
+ end
75
+
76
+ # @api private
77
+ Dialect = Struct.new(:name, :internals_key, keyword_init: true) do
78
+ alias_method :to_s, :name
79
+ alias_method :to_int, :internals_key
80
+ end
81
+
82
+ # Maps easy-to-use Symbols to a struct that keeps both the name and the
83
+ # internal identifier used by libzen.
84
+ #
85
+ # @see https://github.com/AikidoSec/zen-internals/blob/main/src/sql_injection/helpers/select_dialect_based_on_enum.rs
86
+ DIALECTS = {
87
+ common: Dialect.new(name: "SQL", internals_key: 0),
88
+ mysql: Dialect.new(name: "MySQL", internals_key: 8),
89
+ postgresql: Dialect.new(name: "PostgreSQL", internals_key: 9),
90
+ sqlite: Dialect.new(name: "SQLite", internals_key: 12)
91
+ }
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "delegate"
4
+
5
+ module Aikido::Zen
6
+ module Scanners
7
+ module SSRF
8
+ # Simple per-request cache of all DNS lookups performed for a given host.
9
+ # We can store this in the context after performing a lookup, and have the
10
+ # SSRF scanner make sure the hostname being inspected doesn't actually
11
+ # resolve to an internal/dangerous IP.
12
+ class DNSLookups < SimpleDelegator
13
+ def initialize
14
+ super(Hash.new { |h, k| h[k] = [] })
15
+ end
16
+
17
+ def add(hostname, addresses)
18
+ self[hostname].concat(Array(addresses))
19
+ end
20
+
21
+ def ===(hostname)
22
+ key?(hostname)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "resolv"
4
+ require "ipaddr"
5
+
6
+ module Aikido::Zen
7
+ module Scanners
8
+ module SSRF
9
+ # Little helper to check if a given hostname or address is to be
10
+ # considered "dangerous" when used for an outbound HTTP request.
11
+ #
12
+ # When given a hostname:
13
+ #
14
+ # * If any DNS lookups have been performed and stored in the current Zen
15
+ # context (under the "dns.lookups" metadata key), we will map it to the
16
+ # list of IPs that we've resolved it to.
17
+ #
18
+ # * If not, we'll still try to map it to any statically defined address in
19
+ # the system hosts file (e.g. /etc/hosts).
20
+ #
21
+ # Once we mapped the hostname to an IP address (or, if given an IP
22
+ # address), this will check that it's not a loopback address, a private IP
23
+ # address (as defined by RFCs 1918 and 4193), or in one of the
24
+ # "special-use" IP ranges defined in RFC 5735.
25
+ class PrivateIPChecker
26
+ def initialize(resolver = Resolv::Hosts.new)
27
+ @resolver = resolver
28
+ end
29
+
30
+ # @param hostname_or_address [String]
31
+ # @return [Boolean]
32
+ def private?(hostname_or_address)
33
+ resolve(hostname_or_address).any? do |ip|
34
+ PRIVATE_RANGES.any? { |range| range === ip }
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ # Source: https://github.com/AikidoSec/firewall-node/blob/main/library/vulnerabilities/ssrf/isPrivateIP.ts
41
+ PRIVATE_IPV4_RANGES = [
42
+ IPAddr.new("0.0.0.0/8"), # "This" network (RFC 1122)
43
+ IPAddr.new("10.0.0.0/8"), # Private-Use Networks (RFC 1918)
44
+ IPAddr.new("100.64.0.0/10"), # Shared Address Space (RFC 6598)
45
+ IPAddr.new("127.0.0.0/8"), # Loopback (RFC 1122)
46
+ IPAddr.new("169.254.0.0/16"), # Link Local (RFC 3927)
47
+ IPAddr.new("172.16.0.0/12"), # Private-Use Networks (RFC 1918)
48
+ IPAddr.new("192.0.0.0/24"), # IETF Protocol Assignments (RFC 5736)
49
+ IPAddr.new("192.0.2.0/24"), # TEST-NET-1 (RFC 5737)
50
+ IPAddr.new("192.31.196.0/24"), # AS112 Redirection Anycast (RFC 7535)
51
+ IPAddr.new("192.52.193.0/24"), # Automatic Multicast Tunneling (RFC 7450)
52
+ IPAddr.new("192.88.99.0/24"), # 6to4 Relay Anycast (RFC 3068)
53
+ IPAddr.new("192.168.0.0/16"), # Private-Use Networks (RFC 1918)
54
+ IPAddr.new("192.175.48.0/24"), # AS112 Redirection Anycast (RFC 7535)
55
+ IPAddr.new("198.18.0.0/15"), # Network Interconnect Device Benchmark Testing (RFC 2544)
56
+ IPAddr.new("198.51.100.0/24"), # TEST-NET-2 (RFC 5737)
57
+ IPAddr.new("203.0.113.0/24"), # TEST-NET-3 (RFC 5737)
58
+ IPAddr.new("224.0.0.0/4"), # Multicast (RFC 3171)
59
+ IPAddr.new("240.0.0.0/4"), # Reserved for Future Use (RFC 1112)
60
+ IPAddr.new("255.255.255.255/32") # Limited Broadcast (RFC 919)
61
+ ]
62
+
63
+ PRIVATE_IPV6_RANGES = [
64
+ IPAddr.new("::/128"), # Unspecified address (RFC 4291)
65
+ IPAddr.new("::1/128"), # Loopback address (RFC 4291)
66
+ IPAddr.new("fc00::/7"), # Unique local address (ULA) (RFC 4193
67
+ IPAddr.new("fe80::/10"), # Link-local address (LLA) (RFC 4291)
68
+ IPAddr.new("100::/64"), # Discard prefix (RFC 6666)
69
+ IPAddr.new("2001:db8::/32"), # Documentation prefix (RFC 3849)
70
+ IPAddr.new("3fff::/20") # Documentation prefix (RFC 9637)
71
+ ]
72
+
73
+ PRIVATE_RANGES = PRIVATE_IPV4_RANGES + PRIVATE_IPV6_RANGES + PRIVATE_IPV4_RANGES.map(&:ipv4_mapped)
74
+
75
+ def resolved_in_current_context
76
+ context = Aikido::Zen.current_context
77
+ context && context["dns.lookups"]
78
+ end
79
+
80
+ def resolve(hostname_or_address)
81
+ return [] if hostname_or_address.nil?
82
+
83
+ case hostname_or_address
84
+ when Resolv::AddressRegex
85
+ [IPAddr.new(hostname_or_address)]
86
+ when resolved_in_current_context
87
+ resolved_in_current_context[hostname_or_address]
88
+ .map { |address| IPAddr.new(address) }
89
+ else
90
+ @resolver.getaddresses(hostname_or_address.to_s)
91
+ .map { |address| IPAddr.new(address) }
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end