aikido-zen 0.2.0-arm64-darwin → 1.0.1.beta.2-arm64-darwin
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/.aikido +6 -0
- data/.simplecov +6 -0
- data/README.md +67 -83
- data/benchmarks/README.md +8 -12
- data/docs/rails.md +1 -1
- data/lib/aikido/zen/agent.rb +10 -8
- data/lib/aikido/zen/api_client.rb +14 -4
- data/lib/aikido/zen/background_worker.rb +52 -0
- data/lib/aikido/zen/collector.rb +12 -1
- data/lib/aikido/zen/config.rb +20 -0
- data/lib/aikido/zen/context.rb +4 -0
- data/lib/aikido/zen/detached_agent/agent.rb +78 -0
- data/lib/aikido/zen/detached_agent/front_object.rb +37 -0
- data/lib/aikido/zen/detached_agent/server.rb +41 -0
- data/lib/aikido/zen/detached_agent.rb +2 -0
- data/lib/aikido/zen/errors.rb +8 -0
- data/lib/aikido/zen/internals.rb +41 -7
- data/lib/aikido/zen/libzen-v0.1.39-arm64-darwin.dylib +0 -0
- data/lib/aikido/zen/middleware/rack_throttler.rb +9 -3
- data/lib/aikido/zen/middleware/request_tracker.rb +6 -4
- data/lib/aikido/zen/outbound_connection_monitor.rb +4 -0
- data/lib/aikido/zen/rails_engine.rb +8 -8
- data/lib/aikido/zen/rate_limiter/breaker.rb +3 -3
- data/lib/aikido/zen/rate_limiter.rb +6 -11
- data/lib/aikido/zen/request/heuristic_router.rb +6 -0
- data/lib/aikido/zen/request/rails_router.rb +6 -18
- data/lib/aikido/zen/request/schema/auth_schemas.rb +14 -0
- data/lib/aikido/zen/request/schema.rb +18 -0
- data/lib/aikido/zen/runtime_settings.rb +2 -2
- data/lib/aikido/zen/scanners/path_traversal_scanner.rb +4 -2
- data/lib/aikido/zen/scanners/shell_injection_scanner.rb +4 -2
- data/lib/aikido/zen/scanners/sql_injection_scanner.rb +4 -2
- data/lib/aikido/zen/scanners/ssrf/private_ip_checker.rb +33 -21
- data/lib/aikido/zen/scanners/ssrf_scanner.rb +6 -1
- data/lib/aikido/zen/scanners/stored_ssrf_scanner.rb +6 -0
- data/lib/aikido/zen/sink.rb +11 -1
- data/lib/aikido/zen/sinks/action_controller.rb +9 -4
- data/lib/aikido/zen/sinks/async_http.rb +35 -16
- data/lib/aikido/zen/sinks/curb.rb +52 -26
- data/lib/aikido/zen/sinks/em_http.rb +39 -25
- data/lib/aikido/zen/sinks/excon.rb +63 -45
- data/lib/aikido/zen/sinks/file.rb +67 -71
- data/lib/aikido/zen/sinks/http.rb +38 -19
- data/lib/aikido/zen/sinks/httpclient.rb +51 -22
- data/lib/aikido/zen/sinks/httpx.rb +37 -18
- data/lib/aikido/zen/sinks/kernel.rb +18 -57
- data/lib/aikido/zen/sinks/mysql2.rb +19 -7
- data/lib/aikido/zen/sinks/net_http.rb +37 -19
- data/lib/aikido/zen/sinks/patron.rb +41 -24
- data/lib/aikido/zen/sinks/pg.rb +50 -27
- data/lib/aikido/zen/sinks/resolv.rb +37 -16
- data/lib/aikido/zen/sinks/socket.rb +46 -17
- data/lib/aikido/zen/sinks/sqlite3.rb +31 -12
- data/lib/aikido/zen/sinks/trilogy.rb +19 -7
- data/lib/aikido/zen/sinks.rb +29 -20
- data/lib/aikido/zen/sinks_dsl.rb +226 -0
- data/lib/aikido/zen/version.rb +2 -2
- data/lib/aikido/zen/worker.rb +5 -0
- data/lib/aikido/zen.rb +59 -9
- data/placeholder/.gitignore +4 -0
- data/placeholder/README.md +11 -0
- data/placeholder/Rakefile +75 -0
- data/placeholder/lib/placeholder.rb.template +3 -0
- data/placeholder/placeholder.gemspec.template +20 -0
- data/tasklib/bench.rake +29 -6
- data/tasklib/libzen.rake +70 -66
- data/tasklib/wrk.rb +88 -0
- metadata +23 -13
- data/CHANGELOG.md +0 -25
- data/lib/aikido/zen/libzen-v0.1.37.aarch64.dylib +0 -0
- data/lib/aikido.rb +0 -3
@@ -3,118 +3,114 @@
|
|
3
3
|
module Aikido::Zen
|
4
4
|
module Sinks
|
5
5
|
module File
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
def self.load_sinks!
|
7
|
+
# Create a copy of the original method for internal use only to prevent
|
8
|
+
# recursion in PathTraversalScanner.
|
9
|
+
#
|
10
|
+
# IMPORTANT: The alias must be created before the method is overridden,
|
11
|
+
# when the extensions are prepended.
|
12
|
+
::File.singleton_class.alias_method(:expand_path__internal_for_aikido_zen, :expand_path)
|
13
|
+
|
14
|
+
::File.singleton_class.prepend(FileClassExtensions)
|
15
|
+
::File.prepend(FileExtensions)
|
16
|
+
end
|
17
|
+
|
18
|
+
SINK = Sinks.add("File", scanners: [Scanners::PathTraversalScanner])
|
9
19
|
|
10
|
-
module
|
11
|
-
def self.
|
20
|
+
module Helpers
|
21
|
+
def self.scan(filepath, operation)
|
12
22
|
SINK.scan(
|
13
23
|
filepath: filepath,
|
14
24
|
operation: operation
|
15
25
|
)
|
16
26
|
end
|
27
|
+
end
|
17
28
|
|
18
|
-
|
19
|
-
|
20
|
-
def initialize(filename, *, **)
|
21
|
-
Extensions.scan_path(filename, "new")
|
22
|
-
super
|
23
|
-
end
|
24
|
-
end
|
29
|
+
module FileClassExtensions
|
30
|
+
extend Sinks::DSL
|
25
31
|
|
26
|
-
|
27
|
-
|
28
|
-
super
|
32
|
+
sink_before :open do |path|
|
33
|
+
Helpers.scan(path, "open")
|
29
34
|
end
|
30
35
|
|
31
|
-
|
32
|
-
|
33
|
-
super
|
36
|
+
sink_before :read do |path|
|
37
|
+
Helpers.scan(path, "read")
|
34
38
|
end
|
35
39
|
|
36
|
-
|
37
|
-
|
38
|
-
super
|
40
|
+
sink_before :write do |path|
|
41
|
+
Helpers.scan(path, "write")
|
39
42
|
end
|
40
43
|
|
41
|
-
|
42
|
-
|
43
|
-
Extensions.scan_path(joined, "join")
|
44
|
-
joined
|
44
|
+
sink_before :truncate do |file_name|
|
45
|
+
Helpers.scan(file_name, "truncate")
|
45
46
|
end
|
46
47
|
|
47
|
-
|
48
|
-
|
49
|
-
|
48
|
+
sink_before :rename do |old_name, new_name|
|
49
|
+
Helpers.scan(old_name, "rename")
|
50
|
+
Helpers.scan(new_name, "rename")
|
50
51
|
end
|
51
52
|
|
52
|
-
|
53
|
-
|
54
|
-
|
53
|
+
sink_before :unlink do |*file_names|
|
54
|
+
file_names.each do |file_name|
|
55
|
+
Helpers.scan(file_name, "unlink")
|
56
|
+
end
|
55
57
|
end
|
56
58
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
59
|
+
sink_before :delete do |*file_names|
|
60
|
+
file_names.each do |file_name|
|
61
|
+
Helpers.scan(file_name, "delete")
|
62
|
+
end
|
61
63
|
end
|
62
64
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
super
|
65
|
+
sink_before :symlink do |old_name, new_name|
|
66
|
+
Helpers.scan(old_name, "symlink")
|
67
|
+
Helpers.scan(new_name, "symlink")
|
67
68
|
end
|
68
69
|
|
69
|
-
|
70
|
-
|
71
|
-
|
70
|
+
sink_before :chmod do |_mode_int, *file_names|
|
71
|
+
file_names.each do |file_name|
|
72
|
+
Helpers.scan(file_name, "chmod")
|
73
|
+
end
|
72
74
|
end
|
73
75
|
|
74
|
-
|
75
|
-
|
76
|
-
|
76
|
+
sink_before :chown do |_owner_int, group_int, *file_names|
|
77
|
+
file_names.each do |file_name|
|
78
|
+
Helpers.scan(file_name, "chown")
|
77
79
|
end
|
78
|
-
super
|
79
80
|
end
|
80
81
|
|
81
|
-
|
82
|
-
|
83
|
-
|
82
|
+
sink_before :utime do |_atime, _mtime, *file_names|
|
83
|
+
file_names.each do |file_name|
|
84
|
+
Helpers.scan(file_name, "utime")
|
84
85
|
end
|
85
|
-
super
|
86
86
|
end
|
87
87
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
88
|
+
sink_after :join do |result|
|
89
|
+
Helpers.scan(result, "join")
|
90
|
+
end
|
91
|
+
|
92
|
+
sink_before :expand_path do |file_name|
|
93
|
+
Helpers.scan(file_name, "expand_path")
|
93
94
|
end
|
94
95
|
|
95
|
-
|
96
|
-
|
97
|
-
super
|
96
|
+
sink_before :realpath do |file_name|
|
97
|
+
Helpers.scan(file_name, "realpath")
|
98
98
|
end
|
99
99
|
|
100
|
-
|
101
|
-
|
102
|
-
super
|
100
|
+
sink_before :realdirpath do |file_name|
|
101
|
+
Helpers.scan(file_name, "realdirpath")
|
103
102
|
end
|
103
|
+
end
|
104
|
+
|
105
|
+
module FileExtensions
|
106
|
+
extend Sinks::DSL
|
104
107
|
|
105
|
-
|
106
|
-
|
107
|
-
super
|
108
|
+
sink_before :initialize do |path|
|
109
|
+
Helpers.scan(path, "new")
|
108
110
|
end
|
109
111
|
end
|
110
112
|
end
|
111
113
|
end
|
112
114
|
end
|
113
115
|
|
114
|
-
|
115
|
-
# a copy of the original method, only to be used internally.
|
116
|
-
# It's important to keep this line before prepend the Extensions module, otherwise the alias will call
|
117
|
-
# the extended method.
|
118
|
-
::File.singleton_class.alias_method :expand_path__internal_for_aikido_zen, :expand_path
|
119
|
-
::File.singleton_class.prepend(Aikido::Zen::Sinks::File::Extensions)
|
120
|
-
::File.prepend Aikido::Zen::Sinks::File::Extensions::Initiliazer
|
116
|
+
Aikido::Zen::Sinks::File.load_sinks!
|
@@ -1,23 +1,31 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "../
|
3
|
+
require_relative "../scanners/ssrf_scanner"
|
4
4
|
require_relative "../outbound_connection_monitor"
|
5
5
|
|
6
6
|
module Aikido::Zen
|
7
7
|
module Sinks
|
8
8
|
module HTTP
|
9
|
+
def self.load_sinks!
|
10
|
+
if Gem.loaded_specs["http"]
|
11
|
+
require "http"
|
12
|
+
|
13
|
+
::HTTP::Client.prepend(ClientExtensions)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
9
17
|
SINK = Sinks.add("http", scanners: [
|
10
|
-
|
11
|
-
|
18
|
+
Scanners::SSRFScanner,
|
19
|
+
OutboundConnectionMonitor
|
12
20
|
])
|
13
21
|
|
14
|
-
module
|
22
|
+
module Helpers
|
15
23
|
# Maps an HTTP Request to an Aikido OutboundConnection.
|
16
24
|
#
|
17
25
|
# @param req [HTTP::Request]
|
18
26
|
# @return [Aikido::Zen::OutboundConnection]
|
19
27
|
def self.build_outbound(req)
|
20
|
-
|
28
|
+
OutboundConnection.new(
|
21
29
|
host: req.socket_host,
|
22
30
|
port: req.socket_port
|
23
31
|
)
|
@@ -28,7 +36,7 @@ module Aikido::Zen
|
|
28
36
|
# @param req [HTTP::Request]
|
29
37
|
# @return [Aikido::Zen::Scanners::SSRFScanner::Request]
|
30
38
|
def self.wrap_request(req)
|
31
|
-
|
39
|
+
Scanners::SSRFScanner::Request.new(
|
32
40
|
verb: req.verb,
|
33
41
|
uri: URI(req.uri.to_s),
|
34
42
|
headers: req.headers.to_h
|
@@ -36,32 +44,43 @@ module Aikido::Zen
|
|
36
44
|
end
|
37
45
|
|
38
46
|
def self.wrap_response(resp)
|
39
|
-
|
47
|
+
Scanners::SSRFScanner::Response.new(
|
40
48
|
status: resp.status,
|
41
49
|
headers: resp.headers.to_h
|
42
50
|
)
|
43
51
|
end
|
44
52
|
|
45
|
-
def
|
46
|
-
|
53
|
+
def self.scan(request, connection, operation)
|
54
|
+
SINK.scan(
|
55
|
+
request: request,
|
56
|
+
connection: connection,
|
57
|
+
operation: operation
|
58
|
+
)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
module ClientExtensions
|
63
|
+
extend Sinks::DSL
|
64
|
+
|
65
|
+
sink_around :perform do |super_call, req|
|
66
|
+
wrapped_request = Helpers.wrap_request(req)
|
47
67
|
|
48
68
|
# Store the request information so the DNS sinks can pick it up.
|
49
|
-
|
69
|
+
context = Aikido::Zen.current_context
|
70
|
+
if context
|
50
71
|
prev_request = context["ssrf.request"]
|
51
72
|
context["ssrf.request"] = wrapped_request
|
52
73
|
end
|
53
74
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
operation: "request"
|
58
|
-
)
|
75
|
+
connection = Helpers.build_outbound(req)
|
76
|
+
|
77
|
+
Helpers.scan(wrapped_request, connection, "request")
|
59
78
|
|
60
|
-
response =
|
79
|
+
response = super_call.call
|
61
80
|
|
62
|
-
|
81
|
+
Scanners::SSRFScanner.track_redirects(
|
63
82
|
request: wrapped_request,
|
64
|
-
response:
|
83
|
+
response: Helpers.wrap_response(response)
|
65
84
|
)
|
66
85
|
|
67
86
|
response
|
@@ -73,4 +92,4 @@ module Aikido::Zen
|
|
73
92
|
end
|
74
93
|
end
|
75
94
|
|
76
|
-
|
95
|
+
Aikido::Zen::Sinks::HTTP.load_sinks!
|
@@ -1,19 +1,27 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "../
|
3
|
+
require_relative "../scanners/ssrf_scanner"
|
4
4
|
require_relative "../outbound_connection_monitor"
|
5
5
|
|
6
6
|
module Aikido::Zen
|
7
7
|
module Sinks
|
8
8
|
module HTTPClient
|
9
|
+
def self.load_sinks!
|
10
|
+
if Gem.loaded_specs["httpclient"]
|
11
|
+
require "httpclient"
|
12
|
+
|
13
|
+
::HTTPClient.prepend(HTTPClient::HTTPClientExtensions)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
9
17
|
SINK = Sinks.add("httpclient", scanners: [
|
10
|
-
|
11
|
-
|
18
|
+
Scanners::SSRFScanner,
|
19
|
+
OutboundConnectionMonitor
|
12
20
|
])
|
13
21
|
|
14
|
-
module
|
22
|
+
module Helpers
|
15
23
|
def self.wrap_request(req)
|
16
|
-
|
24
|
+
Scanners::SSRFScanner::Request.new(
|
17
25
|
verb: req.http_header.request_method,
|
18
26
|
uri: req.http_header.request_uri,
|
19
27
|
headers: req.headers
|
@@ -21,48 +29,69 @@ module Aikido::Zen
|
|
21
29
|
end
|
22
30
|
|
23
31
|
def self.wrap_response(resp)
|
24
|
-
|
32
|
+
# Code coverage is disabled here because `do_get_header` is not called,
|
33
|
+
# because WebMock does not mock it.
|
34
|
+
# :nocov:
|
35
|
+
Scanners::SSRFScanner::Response.new(
|
25
36
|
status: resp.http_header.status_code,
|
26
37
|
headers: resp.headers
|
27
38
|
)
|
39
|
+
# :nocov:
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.scan(request, connection, operation)
|
43
|
+
SINK.scan(
|
44
|
+
request: request,
|
45
|
+
connection: connection,
|
46
|
+
operation: operation
|
47
|
+
)
|
28
48
|
end
|
29
49
|
|
30
|
-
def self.
|
50
|
+
def self.sink(req, &block)
|
31
51
|
wrapped_request = wrap_request(req)
|
32
|
-
connection =
|
52
|
+
connection = OutboundConnection.from_uri(req.http_header.request_uri)
|
33
53
|
|
34
54
|
# Store the request information so the DNS sinks can pick it up.
|
35
|
-
|
55
|
+
context = Aikido::Zen.current_context
|
56
|
+
if context
|
36
57
|
prev_request = context["ssrf.request"]
|
37
58
|
context["ssrf.request"] = wrapped_request
|
38
59
|
end
|
39
60
|
|
40
|
-
|
61
|
+
scan(wrapped_request, connection, "request")
|
41
62
|
|
42
63
|
yield
|
43
64
|
ensure
|
44
65
|
context["ssrf.request"] = prev_request if context
|
45
66
|
end
|
67
|
+
end
|
68
|
+
|
69
|
+
module HTTPClientExtensions
|
70
|
+
extend Sinks::DSL
|
46
71
|
|
47
|
-
|
48
|
-
|
72
|
+
private
|
73
|
+
|
74
|
+
sink_around :do_get_block do |super_call, req|
|
75
|
+
Helpers.sink(req, &super_call)
|
49
76
|
end
|
50
77
|
|
51
|
-
|
52
|
-
|
78
|
+
sink_around :do_get_stream do |super_call, req|
|
79
|
+
Helpers.sink(req, &super_call)
|
53
80
|
end
|
54
81
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
)
|
61
|
-
|
82
|
+
sink_after :do_get_header do |_result, req, res, _sess|
|
83
|
+
# Code coverage is disabled here because `do_get_header` is not called,
|
84
|
+
# because WebMock does not mock it.
|
85
|
+
# :nocov:
|
86
|
+
Scanners::SSRFScanner.track_redirects(
|
87
|
+
request: Helpers.wrap_request(req),
|
88
|
+
response: Helpers.wrap_response(res)
|
89
|
+
)
|
90
|
+
# :nocov:
|
62
91
|
end
|
63
92
|
end
|
64
93
|
end
|
65
94
|
end
|
66
95
|
end
|
67
96
|
|
68
|
-
|
97
|
+
Aikido::Zen::Sinks::HTTPClient.load_sinks!
|
@@ -1,19 +1,27 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "../
|
3
|
+
require_relative "../scanners/ssrf_scanner"
|
4
4
|
require_relative "../outbound_connection_monitor"
|
5
5
|
|
6
6
|
module Aikido::Zen
|
7
7
|
module Sinks
|
8
8
|
module HTTPX
|
9
|
+
def self.load_sinks!
|
10
|
+
if Gem.loaded_specs["httpx"]
|
11
|
+
require "httpx"
|
12
|
+
|
13
|
+
::HTTPX::Session.prepend(HTTPX::SessionExtensions)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
9
17
|
SINK = Sinks.add("httpx", scanners: [
|
10
|
-
|
11
|
-
|
18
|
+
Scanners::SSRFScanner,
|
19
|
+
OutboundConnectionMonitor
|
12
20
|
])
|
13
21
|
|
14
|
-
module
|
22
|
+
module Helpers
|
15
23
|
def self.wrap_request(request)
|
16
|
-
|
24
|
+
Scanners::SSRFScanner::Request.new(
|
17
25
|
verb: request.verb,
|
18
26
|
uri: request.uri,
|
19
27
|
headers: request.headers.to_hash
|
@@ -21,35 +29,46 @@ module Aikido::Zen
|
|
21
29
|
end
|
22
30
|
|
23
31
|
def self.wrap_response(response)
|
24
|
-
|
32
|
+
Scanners::SSRFScanner::Response.new(
|
25
33
|
status: response.status,
|
26
34
|
headers: response.headers.to_hash
|
27
35
|
)
|
28
36
|
end
|
29
37
|
|
30
|
-
def
|
31
|
-
|
38
|
+
def self.scan(request, connection, operation)
|
39
|
+
SINK.scan(
|
40
|
+
request: request,
|
41
|
+
connection: connection,
|
42
|
+
operation: operation
|
43
|
+
)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
module SessionExtensions
|
48
|
+
extend Sinks::DSL
|
49
|
+
|
50
|
+
sink_around :send_request do |super_call, request|
|
51
|
+
wrapped_request = Helpers.wrap_request(request)
|
32
52
|
|
33
53
|
# Store the request information so the DNS sinks can pick it up.
|
34
|
-
|
54
|
+
context = Aikido::Zen.current_context
|
55
|
+
if context
|
35
56
|
prev_request = context["ssrf.request"]
|
36
57
|
context["ssrf.request"] = wrapped_request
|
37
58
|
end
|
38
59
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
operation: "request"
|
43
|
-
)
|
60
|
+
connection = OutboundConnection.from_uri(request.uri)
|
61
|
+
|
62
|
+
Helpers.scan(wrapped_request, connection, "request")
|
44
63
|
|
45
64
|
request.on(:response) do |response|
|
46
|
-
|
65
|
+
Scanners::SSRFScanner.track_redirects(
|
47
66
|
request: wrapped_request,
|
48
|
-
response:
|
67
|
+
response: Helpers.wrap_response(response)
|
49
68
|
)
|
50
69
|
end
|
51
70
|
|
52
|
-
|
71
|
+
super_call.call
|
53
72
|
ensure
|
54
73
|
context["ssrf.request"] = prev_request if context
|
55
74
|
end
|
@@ -58,4 +77,4 @@ module Aikido::Zen
|
|
58
77
|
end
|
59
78
|
end
|
60
79
|
|
61
|
-
|
80
|
+
Aikido::Zen::Sinks::HTTPX.load_sinks!
|
@@ -3,71 +3,32 @@
|
|
3
3
|
module Aikido::Zen
|
4
4
|
module Sinks
|
5
5
|
module Kernel
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
module Extensions
|
11
|
-
# Checks if the user introduced input is trying to execute other commands
|
12
|
-
# using Shell Injection kind of attacks.
|
13
|
-
#
|
14
|
-
# @param command [String] the _full command_ that will be executed.
|
15
|
-
# @param context [Aikido::Zen::Context]
|
16
|
-
# @param sink [Aikido::Zen::Sink] the Sink that is running the scan.
|
17
|
-
# @param operation [Symbol, String] name of the method being scanned.
|
18
|
-
#
|
19
|
-
# @return [Aikido::Zen::Attacks::ShellInjectionAttack, nil] an Attack if any
|
20
|
-
# user input is detected as part of a Shell Injection Attack, or +nil+ if it's safe.
|
21
|
-
def self.scan_command(command, operation)
|
22
|
-
SINK.scan(
|
23
|
-
command: command,
|
24
|
-
operation: operation
|
25
|
-
)
|
26
|
-
end
|
6
|
+
def self.load_sinks!
|
7
|
+
::Kernel.singleton_class.prepend(KernelExtensions)
|
8
|
+
::Kernel.prepend(KernelExtensions)
|
9
|
+
end
|
27
10
|
|
28
|
-
|
29
|
-
# see [the documentation](https://ruby-doc.org/3.4.1/Kernel.html#method-i-spawn)
|
30
|
-
#
|
31
|
-
# In our context, we care primarily about two common scenarios:
|
32
|
-
# - one argument (String)
|
33
|
-
# e.g.: system("ls"), system("echo something")
|
34
|
-
# - two arguments (Hash, String)
|
35
|
-
# e.g.: system({"foo" => "bar"}, "ls"), system({"foo" => "bar"}, "echo something")
|
36
|
-
#
|
37
|
-
# In all other cases, we do not protect against shell argument injections. Specifically:
|
38
|
-
#
|
39
|
-
# If a user input contains something like $(whoami) and is passed as part of the command
|
40
|
-
# arguments (e.g., user_input = "$(whoami)"):
|
41
|
-
#
|
42
|
-
# system("echo", user_input) This is safe because Ruby automatically escapes arguments
|
43
|
-
# passed to system/spawn in this form.
|
44
|
-
#
|
45
|
-
# system("echo #{user_input}") This is not safe because Ruby interpolates the user_input
|
46
|
-
# into the command string, resulting in a potentially harmful
|
47
|
-
# command like `echo $(whoami)`.
|
48
|
-
def send_arg_to_scan(args, operation)
|
49
|
-
if args.size == 1 && args[0].is_a?(String)
|
50
|
-
Extensions.scan_command(args[0], operation)
|
51
|
-
end
|
11
|
+
SINK = Sinks.add("Kernel", scanners: [Scanners::ShellInjectionScanner])
|
52
12
|
|
53
|
-
|
54
|
-
|
55
|
-
|
13
|
+
module Helpers
|
14
|
+
def self.scan(command, operation)
|
15
|
+
SINK.scan(command: command, operation: operation)
|
56
16
|
end
|
17
|
+
end
|
57
18
|
|
58
|
-
|
59
|
-
|
60
|
-
super
|
61
|
-
end
|
19
|
+
module KernelExtensions
|
20
|
+
extend Sinks::DSL
|
62
21
|
|
63
|
-
|
64
|
-
|
65
|
-
|
22
|
+
%i[system spawn].each do |method_name|
|
23
|
+
sink_before method_name do |*args|
|
24
|
+
# Remove the optional environment argument before the command-line.
|
25
|
+
args.shift if args.first.is_a?(Hash)
|
26
|
+
Helpers.scan(args.first, method_name)
|
27
|
+
end
|
66
28
|
end
|
67
29
|
end
|
68
30
|
end
|
69
31
|
end
|
70
32
|
end
|
71
33
|
|
72
|
-
|
73
|
-
::Kernel.prepend Aikido::Zen::Sinks::Kernel::Extensions
|
34
|
+
Aikido::Zen::Sinks::Kernel.load_sinks!
|
@@ -1,21 +1,33 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "../sink"
|
4
|
-
|
5
3
|
module Aikido::Zen
|
6
4
|
module Sinks
|
7
5
|
module Mysql2
|
6
|
+
def self.load_sinks!
|
7
|
+
if Gem.loaded_specs["mysql2"]
|
8
|
+
require "mysql2"
|
9
|
+
|
10
|
+
::Mysql2::Client.prepend(ClientExtensions)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
8
14
|
SINK = Sinks.add("mysql2", scanners: [Scanners::SQLInjectionScanner])
|
9
15
|
|
10
|
-
module
|
11
|
-
def
|
12
|
-
SINK.scan(query: query, dialect: :mysql, operation:
|
16
|
+
module Helpers
|
17
|
+
def self.scan(query, operation)
|
18
|
+
SINK.scan(query: query, dialect: :mysql, operation: operation)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module ClientExtensions
|
23
|
+
extend Sinks::DSL
|
13
24
|
|
14
|
-
|
25
|
+
sink_before :query do |sql|
|
26
|
+
Helpers.scan(sql, "query")
|
15
27
|
end
|
16
28
|
end
|
17
29
|
end
|
18
30
|
end
|
19
31
|
end
|
20
32
|
|
21
|
-
|
33
|
+
Aikido::Zen::Sinks::Mysql2.load_sinks!
|