aikido-zen 1.0.2.beta.9-x86_64-linux-musl → 1.0.2-x86_64-linux-musl
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.
- checksums.yaml +4 -4
- data/README.md +1 -0
- data/docs/config.md +9 -1
- data/docs/troubleshooting.md +62 -0
- data/lib/aikido/zen/agent.rb +2 -2
- data/lib/aikido/zen/attack.rb +8 -6
- data/lib/aikido/zen/attack_wave/helpers.rb +457 -0
- data/lib/aikido/zen/attack_wave.rb +88 -0
- data/lib/aikido/zen/cache.rb +91 -0
- data/lib/aikido/zen/capped_collections.rb +22 -4
- data/lib/aikido/zen/collector/event.rb +29 -0
- data/lib/aikido/zen/collector/hosts.rb +16 -1
- data/lib/aikido/zen/collector/stats.rb +17 -3
- data/lib/aikido/zen/collector/users.rb +2 -2
- data/lib/aikido/zen/collector.rb +14 -0
- data/lib/aikido/zen/config.rb +35 -6
- data/lib/aikido/zen/context/rack_request.rb +3 -0
- data/lib/aikido/zen/context/rails_request.rb +3 -0
- data/lib/aikido/zen/context.rb +35 -3
- data/lib/aikido/zen/event.rb +47 -2
- data/lib/aikido/zen/helpers.rb +24 -0
- data/lib/aikido/zen/middleware/{check_allowed_addresses.rb → allowed_address_checker.rb} +1 -1
- data/lib/aikido/zen/middleware/attack_wave_protector.rb +46 -0
- data/lib/aikido/zen/middleware/{set_context.rb → context_setter.rb} +1 -1
- data/lib/aikido/zen/middleware/rack_throttler.rb +3 -1
- data/lib/aikido/zen/middleware/request_tracker.rb +8 -3
- data/lib/aikido/zen/outbound_connection.rb +11 -1
- data/lib/aikido/zen/rails_engine.rb +3 -2
- data/lib/aikido/zen/request/rails_router.rb +17 -2
- data/lib/aikido/zen/request.rb +2 -36
- data/lib/aikido/zen/route.rb +50 -0
- data/lib/aikido/zen/runtime_settings/endpoints.rb +37 -8
- data/lib/aikido/zen/runtime_settings.rb +5 -4
- data/lib/aikido/zen/scanners/path_traversal_scanner.rb +3 -2
- data/lib/aikido/zen/scanners/shell_injection_scanner.rb +3 -2
- data/lib/aikido/zen/scanners/sql_injection_scanner.rb +3 -2
- data/lib/aikido/zen/scanners/ssrf_scanner.rb +2 -1
- data/lib/aikido/zen/scanners/stored_ssrf_scanner.rb +5 -1
- data/lib/aikido/zen/sinks/action_controller.rb +3 -1
- data/lib/aikido/zen/sinks/file.rb +34 -32
- data/lib/aikido/zen/sinks/socket.rb +7 -0
- data/lib/aikido/zen/system_info.rb +1 -5
- data/lib/aikido/zen/version.rb +1 -1
- data/lib/aikido/zen.rb +55 -6
- data/tasklib/bench.rake +1 -1
- metadata +10 -4
|
@@ -33,8 +33,10 @@ module Aikido::Zen
|
|
|
33
33
|
private
|
|
34
34
|
|
|
35
35
|
def should_throttle?(request)
|
|
36
|
+
# Bypass rate limiting for allowed IPs
|
|
37
|
+
return false if @settings.allowed_ips.include?(request.ip)
|
|
38
|
+
|
|
36
39
|
return false unless @settings.endpoints[request.route].rate_limiting.enabled?
|
|
37
|
-
return false if @settings.skip_protection_for_ips.include?(request.ip)
|
|
38
40
|
|
|
39
41
|
result = @detached_agent.calculate_rate_limits(request)
|
|
40
42
|
|
|
@@ -5,8 +5,9 @@ module Aikido::Zen
|
|
|
5
5
|
# Rack middleware used to track request
|
|
6
6
|
# It implements the logic under that which is considered worthy of being tracked.
|
|
7
7
|
class RequestTracker
|
|
8
|
-
def initialize(app)
|
|
8
|
+
def initialize(app, settings: Aikido::Zen.runtime_settings)
|
|
9
9
|
@app = app
|
|
10
|
+
@settings = settings
|
|
10
11
|
end
|
|
11
12
|
|
|
12
13
|
def call(env)
|
|
@@ -16,7 +17,8 @@ module Aikido::Zen
|
|
|
16
17
|
if request.route && track?(
|
|
17
18
|
status_code: response[0],
|
|
18
19
|
route: request.route.path,
|
|
19
|
-
http_method: request.request_method
|
|
20
|
+
http_method: request.request_method,
|
|
21
|
+
ip: request.ip
|
|
20
22
|
)
|
|
21
23
|
Aikido::Zen.track_request(request)
|
|
22
24
|
|
|
@@ -126,7 +128,10 @@ module Aikido::Zen
|
|
|
126
128
|
# @param status_code [Integer]
|
|
127
129
|
# @param route [String]
|
|
128
130
|
# @param http_method [String]
|
|
129
|
-
def track?(status_code:, route:, http_method:)
|
|
131
|
+
def track?(status_code:, route:, http_method:, ip: nil)
|
|
132
|
+
# Bypass request and route tracking for allowed IPs
|
|
133
|
+
return false if @settings.allowed_ips.include?(ip)
|
|
134
|
+
|
|
130
135
|
# In the UI we want to show only successful (2xx) or redirect (3xx) responses
|
|
131
136
|
# anything else is discarded.
|
|
132
137
|
return false unless status_code >= 200 && status_code <= 399
|
|
@@ -25,13 +25,23 @@ module Aikido::Zen
|
|
|
25
25
|
# @return [Integer] the port number to which the connection was attempted.
|
|
26
26
|
attr_reader :port
|
|
27
27
|
|
|
28
|
+
# @return [Integer] the number of times that this connection was seen by
|
|
29
|
+
# the hosts collector.
|
|
30
|
+
attr_reader :hits
|
|
31
|
+
|
|
28
32
|
def initialize(host:, port:)
|
|
29
33
|
@host = host
|
|
30
34
|
@port = port
|
|
31
35
|
end
|
|
32
36
|
|
|
37
|
+
def hit
|
|
38
|
+
# Lazy initialize @hits, so it stays nil until the connection is tracked.
|
|
39
|
+
@hits ||= 0
|
|
40
|
+
@hits += 1
|
|
41
|
+
end
|
|
42
|
+
|
|
33
43
|
def as_json
|
|
34
|
-
{hostname: host, port: port}
|
|
44
|
+
{hostname: host, port: port, hits: hits}.compact
|
|
35
45
|
end
|
|
36
46
|
|
|
37
47
|
def ==(other)
|
|
@@ -12,8 +12,9 @@ module Aikido::Zen
|
|
|
12
12
|
initializer "aikido.add_middleware" do |app|
|
|
13
13
|
app.middleware.insert_before 0, Aikido::Zen::Middleware::ForkDetector
|
|
14
14
|
|
|
15
|
-
app.middleware.use Aikido::Zen::Middleware::
|
|
16
|
-
app.middleware.use Aikido::Zen::Middleware::
|
|
15
|
+
app.middleware.use Aikido::Zen::Middleware::ContextSetter
|
|
16
|
+
app.middleware.use Aikido::Zen::Middleware::AllowedAddressChecker
|
|
17
|
+
app.middleware.use Aikido::Zen::Middleware::AttackWaveProtector
|
|
17
18
|
# Request Tracker stats do not consider failed request or 40x, so the middleware
|
|
18
19
|
# must be the last one wrapping the request.
|
|
19
20
|
app.middleware.use Aikido::Zen::Middleware::RequestTracker
|
|
@@ -62,16 +62,31 @@ module Aikido::Zen
|
|
|
62
62
|
nil
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
-
private
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def build_route(route, request, prefix: request.script_name)
|
|
66
68
|
route_wrapper = ActionDispatch::Routing::RouteWrapper.new(route)
|
|
67
69
|
|
|
68
70
|
path = if prefix.present?
|
|
69
|
-
|
|
71
|
+
prefix_route_path(prefix.to_s, route_wrapper.path)
|
|
70
72
|
else
|
|
71
73
|
route_wrapper.path
|
|
72
74
|
end
|
|
73
75
|
|
|
74
76
|
Aikido::Zen::Route.new(verb: request.request_method, path: path)
|
|
75
77
|
end
|
|
78
|
+
|
|
79
|
+
def prefix_route_path(string1, string2)
|
|
80
|
+
# The strings appear to start with "/", allowing them to be concatenated
|
|
81
|
+
# directly after removing trailing "/". However, as it is not currently
|
|
82
|
+
# known whether this is guaranteed, we insert a separator when necessary.
|
|
83
|
+
|
|
84
|
+
separator = string2.start_with?("/") ? "" : "/"
|
|
85
|
+
|
|
86
|
+
string1 = string1.chomp("/")
|
|
87
|
+
string2 = string2.chomp("/")
|
|
88
|
+
|
|
89
|
+
"#{string1}#{separator}#{string2}"
|
|
90
|
+
end
|
|
76
91
|
end
|
|
77
92
|
end
|
data/lib/aikido/zen/request.rb
CHANGED
|
@@ -22,13 +22,11 @@ module Aikido::Zen
|
|
|
22
22
|
@config = config
|
|
23
23
|
@framework = framework
|
|
24
24
|
@router = router
|
|
25
|
-
@body_read = false
|
|
26
25
|
end
|
|
27
26
|
|
|
28
27
|
def __setobj__(delegate) # :nodoc:
|
|
29
28
|
super
|
|
30
|
-
@
|
|
31
|
-
@route = @normalized_header = @truncated_body = nil
|
|
29
|
+
@route = @normalized_header = nil
|
|
32
30
|
end
|
|
33
31
|
|
|
34
32
|
# @return [Aikido::Zen::Route] the framework route being requested.
|
|
@@ -41,8 +39,6 @@ module Aikido::Zen
|
|
|
41
39
|
@schema ||= Aikido::Zen::Request::Schema.build
|
|
42
40
|
end
|
|
43
41
|
|
|
44
|
-
# @api private
|
|
45
|
-
#
|
|
46
42
|
# @return [String] the IP address of the client making the request.
|
|
47
43
|
def client_ip
|
|
48
44
|
return @client_ip if @client_ip
|
|
@@ -74,42 +70,12 @@ module Aikido::Zen
|
|
|
74
70
|
}
|
|
75
71
|
end
|
|
76
72
|
|
|
77
|
-
# @api private
|
|
78
|
-
#
|
|
79
|
-
# Reads the first 16KiB of the request body, to include in attack reports
|
|
80
|
-
# back to the Aikido server. This method should only be called if an attack
|
|
81
|
-
# is detected during the current request.
|
|
82
|
-
#
|
|
83
|
-
# If the underlying IO object has been partially (or fully) read before,
|
|
84
|
-
# this will attempt to restore the previous cursor position after reading it
|
|
85
|
-
# if possible, or leave if rewund if not.
|
|
86
|
-
#
|
|
87
|
-
# @param max_size [Integer] number of bytes to read at most.
|
|
88
|
-
#
|
|
89
|
-
# @return [String]
|
|
90
|
-
def truncated_body(max_size: 16384)
|
|
91
|
-
return @truncated_body if @body_read
|
|
92
|
-
return nil if body.nil?
|
|
93
|
-
|
|
94
|
-
begin
|
|
95
|
-
initial_pos = body.pos if body.respond_to?(:pos)
|
|
96
|
-
body.rewind
|
|
97
|
-
@truncated_body = body.read(max_size)
|
|
98
|
-
ensure
|
|
99
|
-
@body_read = true
|
|
100
|
-
body.rewind
|
|
101
|
-
body.seek(initial_pos) if initial_pos && body.respond_to?(:seek)
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
|
|
105
73
|
def as_json
|
|
106
74
|
{
|
|
107
|
-
method: request_method.
|
|
75
|
+
method: request_method.upcase,
|
|
108
76
|
url: url,
|
|
109
77
|
ipAddress: client_ip,
|
|
110
78
|
userAgent: user_agent,
|
|
111
|
-
headers: normalized_headers.reject { |_, val| val.to_s.empty? },
|
|
112
|
-
body: truncated_body,
|
|
113
79
|
source: framework,
|
|
114
80
|
route: route&.path
|
|
115
81
|
}
|
data/lib/aikido/zen/route.rb
CHANGED
|
@@ -39,8 +39,58 @@ module Aikido::Zen
|
|
|
39
39
|
[verb, path].hash
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
+
# Sort routes by wildcard matching order deterministically:
|
|
43
|
+
#
|
|
44
|
+
# 1. Exact path before wildcard path
|
|
45
|
+
# 2. Fewer wildcards in path relative to path length
|
|
46
|
+
# 3. Earliest wildcard position in path
|
|
47
|
+
# 4. Exact verb before wildcard verb
|
|
48
|
+
# 5. Lexicographic path (tie-break)
|
|
49
|
+
# 6. Lexicographic verb (tie-break)
|
|
50
|
+
#
|
|
51
|
+
# @return [Array] the sort key
|
|
52
|
+
def sort_key
|
|
53
|
+
@sort_key ||= begin
|
|
54
|
+
stars = []
|
|
55
|
+
i = -1
|
|
56
|
+
while (i = path.index("*", i + 1))
|
|
57
|
+
stars << i
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
[
|
|
61
|
+
stars.empty? ? 0 : 1,
|
|
62
|
+
stars.length - path.length,
|
|
63
|
+
stars,
|
|
64
|
+
(verb == "*") ? 1 : 0,
|
|
65
|
+
path,
|
|
66
|
+
verb
|
|
67
|
+
].freeze
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def match?(other)
|
|
72
|
+
other.is_a?(Route) &&
|
|
73
|
+
pattern(verb).match?(other.verb) &&
|
|
74
|
+
pattern(path).match?(other.path)
|
|
75
|
+
end
|
|
76
|
+
|
|
42
77
|
def inspect
|
|
43
78
|
"#<#{self.class.name} #{verb} #{path.inspect}>"
|
|
44
79
|
end
|
|
80
|
+
|
|
81
|
+
# Construct a regular expression equivalent to the wildcard string,
|
|
82
|
+
# where '*' is the wildcard operator.
|
|
83
|
+
#
|
|
84
|
+
# The resulting pattern matches the entire input, allows an optional
|
|
85
|
+
# trailing slash, and is case-insensitive.
|
|
86
|
+
#
|
|
87
|
+
# All other special characters in the regular expression are escaped
|
|
88
|
+
# so that they are treated literally.
|
|
89
|
+
#
|
|
90
|
+
# @param string [String] wildcard string
|
|
91
|
+
# @return [Regexp] regular expression matching the wildcard string
|
|
92
|
+
private def pattern(string)
|
|
93
|
+
/^#{Regexp.escape(string).gsub("\\*", ".*")}\/?$/i
|
|
94
|
+
end
|
|
45
95
|
end
|
|
46
96
|
end
|
|
@@ -16,24 +16,53 @@ module Aikido::Zen
|
|
|
16
16
|
# @param data [Array<Hash>]
|
|
17
17
|
# @return [Aikido::Zen::RuntimeSettings::Endpoints]
|
|
18
18
|
def self.from_json(data)
|
|
19
|
-
|
|
20
|
-
route = Route.new(verb:
|
|
21
|
-
settings = RuntimeSettings::ProtectionSettings.from_json(
|
|
19
|
+
endpoint_pairs = Array(data).map do |value|
|
|
20
|
+
route = Route.new(verb: value["method"], path: value["route"])
|
|
21
|
+
settings = RuntimeSettings::ProtectionSettings.from_json(value)
|
|
22
22
|
[route, settings]
|
|
23
|
-
|
|
23
|
+
end
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
# Sort endpoints by wildcard matching order
|
|
26
|
+
endpoint_pairs.sort_by! do |route, settings|
|
|
27
|
+
route.sort_key
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
new(endpoint_pairs.to_h)
|
|
26
31
|
end
|
|
27
32
|
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
# @param endpoints [Hash] the endpoints in wildcard matching order
|
|
34
|
+
# @return [Aikido::Zen::RuntimeSettings::Endpoints]
|
|
35
|
+
def initialize(endpoints = {})
|
|
36
|
+
@endpoints = endpoints
|
|
30
37
|
@endpoints.default = RuntimeSettings::ProtectionSettings.none
|
|
31
38
|
end
|
|
32
39
|
|
|
33
40
|
# @param route [Aikido::Zen::Route]
|
|
34
41
|
# @return [Aikido::Zen::RuntimeSettings::ProtectionSettings]
|
|
35
42
|
def [](route)
|
|
36
|
-
@endpoints[route]
|
|
43
|
+
return @endpoints[route] if @endpoints.key?(route)
|
|
44
|
+
|
|
45
|
+
# Wildcard endpoint matching
|
|
46
|
+
|
|
47
|
+
@endpoints.each do |pattern, settings|
|
|
48
|
+
return settings if pattern.match?(route)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
@endpoints.default
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @param route [Aikido::Zen::Route]
|
|
55
|
+
# @return [Array<Aikido::Zen::RuntimeSettings::ProtectionSettings>]
|
|
56
|
+
def match(route)
|
|
57
|
+
matches = []
|
|
58
|
+
|
|
59
|
+
@endpoints.each do |pattern, settings|
|
|
60
|
+
matches << settings if pattern.match?(route)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
matches << @endpoints.default if matches.empty?
|
|
64
|
+
|
|
65
|
+
matches
|
|
37
66
|
end
|
|
38
67
|
|
|
39
68
|
# @!visibility private
|
|
@@ -11,11 +11,11 @@ module Aikido::Zen
|
|
|
11
11
|
#
|
|
12
12
|
# You can subscribe to changes with +#add_observer(object, func_name)+, which
|
|
13
13
|
# will call the function passing the settings as an argument.
|
|
14
|
-
RuntimeSettings = Struct.new(:updated_at, :heartbeat_interval, :endpoints, :blocked_user_ids, :
|
|
14
|
+
RuntimeSettings = Struct.new(:updated_at, :heartbeat_interval, :endpoints, :blocked_user_ids, :allowed_ips, :received_any_stats, :blocking_mode) do
|
|
15
15
|
def initialize(*)
|
|
16
16
|
super
|
|
17
17
|
self.endpoints ||= RuntimeSettings::Endpoints.new
|
|
18
|
-
self.
|
|
18
|
+
self.allowed_ips ||= RuntimeSettings::IPSet.new
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
# @!attribute [rw] updated_at
|
|
@@ -35,7 +35,7 @@ module Aikido::Zen
|
|
|
35
35
|
# @!attribute [rw] blocked_user_ids
|
|
36
36
|
# @return [Array]
|
|
37
37
|
|
|
38
|
-
# @!attribute [rw]
|
|
38
|
+
# @!attribute [rw] allowed_ips
|
|
39
39
|
# @return [Aikido::Zen::RuntimeSettings::IPSet]
|
|
40
40
|
|
|
41
41
|
# Parse and interpret the JSON response from the core API with updated
|
|
@@ -53,8 +53,9 @@ module Aikido::Zen
|
|
|
53
53
|
self.heartbeat_interval = data["heartbeatIntervalInMS"].to_i / 1000
|
|
54
54
|
self.endpoints = RuntimeSettings::Endpoints.from_json(data["endpoints"])
|
|
55
55
|
self.blocked_user_ids = data["blockedUserIds"]
|
|
56
|
-
self.
|
|
56
|
+
self.allowed_ips = RuntimeSettings::IPSet.from_json(data["allowedIPAddresses"])
|
|
57
57
|
self.received_any_stats = data["receivedAnyStats"]
|
|
58
|
+
self.blocking_mode = data["block"]
|
|
58
59
|
|
|
59
60
|
updated_at != last_updated_at
|
|
60
61
|
end
|
|
@@ -21,14 +21,15 @@ module Aikido::Zen
|
|
|
21
21
|
# user input is detected to be attempting a Path Traversal Attack, or +nil+ if not.
|
|
22
22
|
def self.call(filepath:, sink:, context:, operation:)
|
|
23
23
|
context.payloads.each do |payload|
|
|
24
|
-
next unless new(filepath, payload.value).attack?
|
|
24
|
+
next unless new(filepath, payload.value.to_s).attack?
|
|
25
25
|
|
|
26
26
|
return Attacks::PathTraversalAttack.new(
|
|
27
27
|
sink: sink,
|
|
28
28
|
input: payload,
|
|
29
29
|
filepath: filepath,
|
|
30
30
|
context: context,
|
|
31
|
-
operation: "#{sink.operation}.#{operation}"
|
|
31
|
+
operation: "#{sink.operation}.#{operation}",
|
|
32
|
+
stack: Aikido::Zen.clean_stack_trace
|
|
32
33
|
)
|
|
33
34
|
end
|
|
34
35
|
|
|
@@ -16,14 +16,15 @@ module Aikido::Zen
|
|
|
16
16
|
#
|
|
17
17
|
def self.call(command:, sink:, context:, operation:)
|
|
18
18
|
context.payloads.each do |payload|
|
|
19
|
-
next unless new(command, payload.value).attack?
|
|
19
|
+
next unless new(command, payload.value.to_s).attack?
|
|
20
20
|
|
|
21
21
|
return Attacks::ShellInjectionAttack.new(
|
|
22
22
|
sink: sink,
|
|
23
23
|
input: payload,
|
|
24
24
|
command: command,
|
|
25
25
|
context: context,
|
|
26
|
-
operation: "#{sink.operation}.#{operation}"
|
|
26
|
+
operation: "#{sink.operation}.#{operation}",
|
|
27
|
+
stack: Aikido::Zen.clean_stack_trace
|
|
27
28
|
)
|
|
28
29
|
end
|
|
29
30
|
|
|
@@ -32,7 +32,7 @@ module Aikido::Zen
|
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
context.payloads.each do |payload|
|
|
35
|
-
next unless new(query, payload.value, dialect).attack?
|
|
35
|
+
next unless new(query, payload.value.to_s, dialect).attack?
|
|
36
36
|
|
|
37
37
|
return Attacks::SQLInjectionAttack.new(
|
|
38
38
|
sink: sink,
|
|
@@ -40,7 +40,8 @@ module Aikido::Zen
|
|
|
40
40
|
input: payload,
|
|
41
41
|
dialect: dialect,
|
|
42
42
|
context: context,
|
|
43
|
-
operation: "#{sink.operation}.#{operation}"
|
|
43
|
+
operation: "#{sink.operation}.#{operation}",
|
|
44
|
+
stack: Aikido::Zen.clean_stack_trace
|
|
44
45
|
)
|
|
45
46
|
end
|
|
46
47
|
|
|
@@ -20,7 +20,8 @@ module Aikido::Zen
|
|
|
20
20
|
address: offending_address,
|
|
21
21
|
sink: sink,
|
|
22
22
|
context: context,
|
|
23
|
-
operation: "#{sink.operation}.#{operation}"
|
|
23
|
+
operation: "#{sink.operation}.#{operation}",
|
|
24
|
+
stack: Aikido::Zen.clean_stack_trace
|
|
24
25
|
)
|
|
25
26
|
end
|
|
26
27
|
|
|
@@ -44,6 +45,9 @@ module Aikido::Zen
|
|
|
44
45
|
|
|
45
46
|
DANGEROUS_ADDRESSES = [
|
|
46
47
|
IPAddr.new("169.254.169.254"),
|
|
48
|
+
IPAddr.new("100.100.100.200"),
|
|
49
|
+
IPAddr.new("::ffff:169.254.169.254"),
|
|
50
|
+
IPAddr.new("::ffff:100.100.100.200"),
|
|
47
51
|
IPAddr.new("fd00:ec2::254")
|
|
48
52
|
]
|
|
49
53
|
end
|
|
@@ -43,8 +43,10 @@ module Aikido::Zen
|
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
private def should_throttle?(request)
|
|
46
|
+
# Bypass rate limiting for allowed IPs
|
|
47
|
+
return false if @settings.allowed_ips.include?(request.ip)
|
|
48
|
+
|
|
46
49
|
return false unless @settings.endpoints[request.route].rate_limiting.enabled?
|
|
47
|
-
return false if @settings.skip_protection_for_ips.include?(request.ip)
|
|
48
50
|
|
|
49
51
|
result = @detached_agent.calculate_rate_limits(request)
|
|
50
52
|
return false unless result
|
|
@@ -82,38 +82,40 @@ module Aikido::Zen
|
|
|
82
82
|
end
|
|
83
83
|
|
|
84
84
|
def join(*args, **kwargs, &blk)
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
85
|
+
if Aikido::Zen.config.harden?
|
|
86
|
+
# IMPORTANT: THE BEHAVIOR OF THIS METHOD IS CHANGED!
|
|
87
|
+
#
|
|
88
|
+
# File.join has undocumented behavior:
|
|
89
|
+
#
|
|
90
|
+
# File.join recursively joins nested string arrays.
|
|
91
|
+
#
|
|
92
|
+
# This prevents path traversal detection when an array originates
|
|
93
|
+
# from user input that was assumed to be a string.
|
|
94
|
+
#
|
|
95
|
+
# This undocumented behavior has been restricted to support path
|
|
96
|
+
# traversal detection.
|
|
97
|
+
#
|
|
98
|
+
# File.join no longer joins nested string arrays, but still accepts
|
|
99
|
+
# a single string array argument.
|
|
100
|
+
|
|
101
|
+
# File.join is often incorrectly called with a single array argument.
|
|
102
|
+
#
|
|
103
|
+
# i.e.
|
|
104
|
+
#
|
|
105
|
+
# File.join(["prefix", "filename"])
|
|
106
|
+
#
|
|
107
|
+
# This is considered acceptable.
|
|
108
|
+
#
|
|
109
|
+
# Calling File.join with a single string argument returns the string
|
|
110
|
+
# argument itself, having no practical effect. Therefore, it can be
|
|
111
|
+
# presumed that if File.join is called with a single array argument
|
|
112
|
+
# then this was its intended usage, and the array did not originate
|
|
113
|
+
# from user input that was assumed to be a string.
|
|
114
|
+
strings = args
|
|
115
|
+
strings = args.first if args.size == 1 && args.first.is_a?(Array)
|
|
116
|
+
strings.each do |string|
|
|
117
|
+
raise TypeError.new("Zen prevented implicit conversion of Array to String in hardened method. Visit https://github.com/AikidoSec/firewall-ruby for more information.") if string.is_a?(Array)
|
|
118
|
+
end
|
|
117
119
|
end
|
|
118
120
|
|
|
119
121
|
result = join__internal_for_aikido_zen(*args, **kwargs, &blk)
|
|
@@ -68,6 +68,13 @@ module Aikido::Zen
|
|
|
68
68
|
# :nocov:
|
|
69
69
|
Helpers.scan(remote_host, socket, "open")
|
|
70
70
|
# :nocov:
|
|
71
|
+
rescue Aikido::Zen::UnderAttackError, Aikido::Zen::Sinks::DSL::PresafeError
|
|
72
|
+
# If the scan raises an exception that will escape the safe block,
|
|
73
|
+
# the open socket must be closed because it will not be returned,
|
|
74
|
+
# so the user cannot close it.
|
|
75
|
+
socket.close
|
|
76
|
+
|
|
77
|
+
raise
|
|
71
78
|
end
|
|
72
79
|
end
|
|
73
80
|
end
|
|
@@ -8,12 +8,8 @@ require_relative "package"
|
|
|
8
8
|
module Aikido::Zen
|
|
9
9
|
# Provides information about the currently running Agent.
|
|
10
10
|
class SystemInfo
|
|
11
|
-
def initialize(config = Aikido::Zen.config)
|
|
12
|
-
@config = config
|
|
13
|
-
end
|
|
14
|
-
|
|
15
11
|
def attacks_block_requests?
|
|
16
|
-
|
|
12
|
+
!!Aikido::Zen.blocking_mode?
|
|
17
13
|
end
|
|
18
14
|
|
|
19
15
|
def attacks_are_only_reported?
|