aikido-zen 1.0.2.beta.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 (116) hide show
  1. checksums.yaml +7 -0
  2. data/.aikido +6 -0
  3. data/.ruby-version +1 -0
  4. data/.simplecov +26 -0
  5. data/.standard.yml +3 -0
  6. data/LICENSE +674 -0
  7. data/README.md +146 -0
  8. data/Rakefile +67 -0
  9. data/benchmarks/README.md +23 -0
  10. data/benchmarks/rails7.1_sql_injection.js +70 -0
  11. data/docs/banner.svg +202 -0
  12. data/docs/config.md +125 -0
  13. data/docs/proxy.md +10 -0
  14. data/docs/rails.md +114 -0
  15. data/lib/aikido/zen/actor.rb +116 -0
  16. data/lib/aikido/zen/agent/heartbeats_manager.rb +66 -0
  17. data/lib/aikido/zen/agent.rb +179 -0
  18. data/lib/aikido/zen/api_client.rb +145 -0
  19. data/lib/aikido/zen/attack.rb +207 -0
  20. data/lib/aikido/zen/background_worker.rb +52 -0
  21. data/lib/aikido/zen/capped_collections.rb +68 -0
  22. data/lib/aikido/zen/collector/hosts.rb +15 -0
  23. data/lib/aikido/zen/collector/routes.rb +66 -0
  24. data/lib/aikido/zen/collector/sink_stats.rb +95 -0
  25. data/lib/aikido/zen/collector/stats.rb +111 -0
  26. data/lib/aikido/zen/collector/users.rb +30 -0
  27. data/lib/aikido/zen/collector.rb +144 -0
  28. data/lib/aikido/zen/config.rb +282 -0
  29. data/lib/aikido/zen/context/rack_request.rb +24 -0
  30. data/lib/aikido/zen/context/rails_request.rb +44 -0
  31. data/lib/aikido/zen/context.rb +112 -0
  32. data/lib/aikido/zen/detached_agent/agent.rb +78 -0
  33. data/lib/aikido/zen/detached_agent/front_object.rb +37 -0
  34. data/lib/aikido/zen/detached_agent/server.rb +78 -0
  35. data/lib/aikido/zen/detached_agent.rb +2 -0
  36. data/lib/aikido/zen/errors.rb +107 -0
  37. data/lib/aikido/zen/event.rb +71 -0
  38. data/lib/aikido/zen/internals.rb +103 -0
  39. data/lib/aikido/zen/libzen-v0.1.39-aarch64-linux.so +0 -0
  40. data/lib/aikido/zen/middleware/check_allowed_addresses.rb +26 -0
  41. data/lib/aikido/zen/middleware/middleware.rb +11 -0
  42. data/lib/aikido/zen/middleware/rack_throttler.rb +48 -0
  43. data/lib/aikido/zen/middleware/request_tracker.rb +192 -0
  44. data/lib/aikido/zen/middleware/set_context.rb +26 -0
  45. data/lib/aikido/zen/outbound_connection.rb +45 -0
  46. data/lib/aikido/zen/outbound_connection_monitor.rb +23 -0
  47. data/lib/aikido/zen/package.rb +22 -0
  48. data/lib/aikido/zen/payload.rb +50 -0
  49. data/lib/aikido/zen/rails_engine.rb +56 -0
  50. data/lib/aikido/zen/rate_limiter/breaker.rb +61 -0
  51. data/lib/aikido/zen/rate_limiter/bucket.rb +76 -0
  52. data/lib/aikido/zen/rate_limiter/result.rb +31 -0
  53. data/lib/aikido/zen/rate_limiter.rb +50 -0
  54. data/lib/aikido/zen/request/heuristic_router.rb +115 -0
  55. data/lib/aikido/zen/request/rails_router.rb +77 -0
  56. data/lib/aikido/zen/request/schema/auth_discovery.rb +86 -0
  57. data/lib/aikido/zen/request/schema/auth_schemas.rb +54 -0
  58. data/lib/aikido/zen/request/schema/builder.rb +121 -0
  59. data/lib/aikido/zen/request/schema/definition.rb +107 -0
  60. data/lib/aikido/zen/request/schema/empty_schema.rb +28 -0
  61. data/lib/aikido/zen/request/schema.rb +87 -0
  62. data/lib/aikido/zen/request.rb +122 -0
  63. data/lib/aikido/zen/route.rb +39 -0
  64. data/lib/aikido/zen/runtime_settings/endpoints.rb +49 -0
  65. data/lib/aikido/zen/runtime_settings/ip_set.rb +36 -0
  66. data/lib/aikido/zen/runtime_settings/protection_settings.rb +62 -0
  67. data/lib/aikido/zen/runtime_settings/rate_limit_settings.rb +47 -0
  68. data/lib/aikido/zen/runtime_settings.rb +65 -0
  69. data/lib/aikido/zen/scan.rb +75 -0
  70. data/lib/aikido/zen/scanners/path_traversal/helpers.rb +65 -0
  71. data/lib/aikido/zen/scanners/path_traversal_scanner.rb +63 -0
  72. data/lib/aikido/zen/scanners/shell_injection/helpers.rb +159 -0
  73. data/lib/aikido/zen/scanners/shell_injection_scanner.rb +64 -0
  74. data/lib/aikido/zen/scanners/sql_injection_scanner.rb +93 -0
  75. data/lib/aikido/zen/scanners/ssrf/dns_lookups.rb +27 -0
  76. data/lib/aikido/zen/scanners/ssrf/private_ip_checker.rb +97 -0
  77. data/lib/aikido/zen/scanners/ssrf_scanner.rb +265 -0
  78. data/lib/aikido/zen/scanners/stored_ssrf_scanner.rb +49 -0
  79. data/lib/aikido/zen/scanners.rb +7 -0
  80. data/lib/aikido/zen/sink.rb +118 -0
  81. data/lib/aikido/zen/sinks/action_controller.rb +83 -0
  82. data/lib/aikido/zen/sinks/async_http.rb +80 -0
  83. data/lib/aikido/zen/sinks/curb.rb +113 -0
  84. data/lib/aikido/zen/sinks/em_http.rb +83 -0
  85. data/lib/aikido/zen/sinks/excon.rb +118 -0
  86. data/lib/aikido/zen/sinks/file.rb +112 -0
  87. data/lib/aikido/zen/sinks/http.rb +93 -0
  88. data/lib/aikido/zen/sinks/httpclient.rb +95 -0
  89. data/lib/aikido/zen/sinks/httpx.rb +78 -0
  90. data/lib/aikido/zen/sinks/kernel.rb +33 -0
  91. data/lib/aikido/zen/sinks/mysql2.rb +31 -0
  92. data/lib/aikido/zen/sinks/net_http.rb +101 -0
  93. data/lib/aikido/zen/sinks/patron.rb +103 -0
  94. data/lib/aikido/zen/sinks/pg.rb +72 -0
  95. data/lib/aikido/zen/sinks/resolv.rb +62 -0
  96. data/lib/aikido/zen/sinks/socket.rb +78 -0
  97. data/lib/aikido/zen/sinks/sqlite3.rb +46 -0
  98. data/lib/aikido/zen/sinks/trilogy.rb +31 -0
  99. data/lib/aikido/zen/sinks/typhoeus.rb +78 -0
  100. data/lib/aikido/zen/sinks.rb +36 -0
  101. data/lib/aikido/zen/sinks_dsl.rb +250 -0
  102. data/lib/aikido/zen/synchronizable.rb +24 -0
  103. data/lib/aikido/zen/system_info.rb +84 -0
  104. data/lib/aikido/zen/version.rb +10 -0
  105. data/lib/aikido/zen/worker.rb +87 -0
  106. data/lib/aikido/zen.rb +246 -0
  107. data/lib/aikido-zen.rb +3 -0
  108. data/placeholder/.gitignore +4 -0
  109. data/placeholder/README.md +11 -0
  110. data/placeholder/Rakefile +75 -0
  111. data/placeholder/lib/placeholder.rb.template +3 -0
  112. data/placeholder/placeholder.gemspec.template +20 -0
  113. data/tasklib/bench.rake +94 -0
  114. data/tasklib/libzen.rake +133 -0
  115. data/tasklib/wrk.rb +88 -0
  116. metadata +205 -0
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../scanners/ssrf_scanner"
4
+ require_relative "../outbound_connection_monitor"
5
+
6
+ module Aikido::Zen
7
+ module Sinks
8
+ module HTTPClient
9
+ SINK = Sinks.add("httpclient", scanners: [
10
+ Scanners::SSRFScanner,
11
+ OutboundConnectionMonitor
12
+ ])
13
+
14
+ module Helpers
15
+ def self.wrap_request(req)
16
+ Scanners::SSRFScanner::Request.new(
17
+ verb: req.http_header.request_method,
18
+ uri: req.http_header.request_uri,
19
+ headers: req.headers
20
+ )
21
+ end
22
+
23
+ def self.wrap_response(resp)
24
+ # Code coverage is disabled here because `do_get_header` is not called,
25
+ # because WebMock does not mock it.
26
+ # :nocov:
27
+ Scanners::SSRFScanner::Response.new(
28
+ status: resp.http_header.status_code,
29
+ headers: resp.headers
30
+ )
31
+ # :nocov:
32
+ end
33
+
34
+ def self.scan(request, connection, operation)
35
+ SINK.scan(
36
+ request: request,
37
+ connection: connection,
38
+ operation: operation
39
+ )
40
+ end
41
+
42
+ def self.sink(req, &block)
43
+ wrapped_request = wrap_request(req)
44
+ connection = OutboundConnection.from_uri(req.http_header.request_uri)
45
+
46
+ # Store the request information so the DNS sinks can pick it up.
47
+ context = Aikido::Zen.current_context
48
+ if context
49
+ prev_request = context["ssrf.request"]
50
+ context["ssrf.request"] = wrapped_request
51
+ end
52
+
53
+ scan(wrapped_request, connection, "request")
54
+
55
+ yield
56
+ ensure
57
+ context["ssrf.request"] = prev_request if context
58
+ end
59
+ end
60
+
61
+ def self.load_sinks!
62
+ if Aikido::Zen.satisfy "httpclient", ">= 2.0"
63
+ require "httpclient"
64
+
65
+ ::HTTPClient.class_eval do
66
+ extend Sinks::DSL
67
+
68
+ private
69
+
70
+ sink_around :do_get_block do |original_call, req|
71
+ Helpers.sink(req, &original_call)
72
+ end
73
+
74
+ sink_around :do_get_stream do |original_call, req|
75
+ Helpers.sink(req, &original_call)
76
+ end
77
+
78
+ sink_after :do_get_header do |_result, req, res, _sess|
79
+ # Code coverage is disabled here because `do_get_header` is not called,
80
+ # because WebMock does not mock it.
81
+ # :nocov:
82
+ Scanners::SSRFScanner.track_redirects(
83
+ request: Helpers.wrap_request(req),
84
+ response: Helpers.wrap_response(res)
85
+ )
86
+ # :nocov:
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ Aikido::Zen::Sinks::HTTPClient.load_sinks!
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../scanners/ssrf_scanner"
4
+ require_relative "../outbound_connection_monitor"
5
+
6
+ module Aikido::Zen
7
+ module Sinks
8
+ module HTTPX
9
+ SINK = Sinks.add("httpx", scanners: [
10
+ Scanners::SSRFScanner,
11
+ OutboundConnectionMonitor
12
+ ])
13
+
14
+ module Helpers
15
+ def self.wrap_request(request)
16
+ Scanners::SSRFScanner::Request.new(
17
+ verb: request.verb,
18
+ uri: request.uri,
19
+ headers: request.headers.to_hash
20
+ )
21
+ end
22
+
23
+ def self.wrap_response(response)
24
+ Scanners::SSRFScanner::Response.new(
25
+ status: response.status,
26
+ headers: response.headers.to_hash
27
+ )
28
+ end
29
+
30
+ def self.scan(request, connection, operation)
31
+ SINK.scan(
32
+ request: request,
33
+ connection: connection,
34
+ operation: operation
35
+ )
36
+ end
37
+ end
38
+
39
+ def self.load_sinks!
40
+ if Aikido::Zen.satisfy "httpx", ">= 1.1.3"
41
+ require "httpx"
42
+
43
+ ::HTTPX::Session.class_eval do
44
+ extend Sinks::DSL
45
+
46
+ sink_around :send_request do |original_call, request|
47
+ wrapped_request = Helpers.wrap_request(request)
48
+
49
+ # Store the request information so the DNS sinks can pick it up.
50
+ context = Aikido::Zen.current_context
51
+ if context
52
+ prev_request = context["ssrf.request"]
53
+ context["ssrf.request"] = wrapped_request
54
+ end
55
+
56
+ connection = OutboundConnection.from_uri(request.uri)
57
+
58
+ Helpers.scan(wrapped_request, connection, "request")
59
+
60
+ request.on(:response) do |response|
61
+ Scanners::SSRFScanner.track_redirects(
62
+ request: wrapped_request,
63
+ response: Helpers.wrap_response(response)
64
+ )
65
+ end
66
+
67
+ original_call.call
68
+ ensure
69
+ context["ssrf.request"] = prev_request if context
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ Aikido::Zen::Sinks::HTTPX.load_sinks!
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aikido::Zen
4
+ module Sinks
5
+ module Kernel
6
+ SINK = Sinks.add("Kernel", scanners: [Scanners::ShellInjectionScanner])
7
+
8
+ module Helpers
9
+ def self.scan(command, operation)
10
+ SINK.scan(command: command, operation: operation)
11
+ end
12
+ end
13
+
14
+ def self.load_sinks!
15
+ [::Kernel.singleton_class, ::Kernel].each do |klass|
16
+ klass.class_eval do
17
+ extend Sinks::DSL
18
+
19
+ %i[system spawn].each do |method_name|
20
+ sink_before method_name do |*args|
21
+ # Remove the optional environment argument before the command-line.
22
+ args.shift if args.first.is_a?(Hash)
23
+ Helpers.scan(args.first, method_name)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ Aikido::Zen::Sinks::Kernel.load_sinks!
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aikido::Zen
4
+ module Sinks
5
+ module Mysql2
6
+ SINK = Sinks.add("mysql2", scanners: [Scanners::SQLInjectionScanner])
7
+
8
+ module Helpers
9
+ def self.scan(query, operation)
10
+ SINK.scan(query: query, dialect: :mysql, operation: operation)
11
+ end
12
+ end
13
+
14
+ def self.load_sinks!
15
+ if Aikido::Zen.satisfy "mysql2"
16
+ require "mysql2"
17
+
18
+ ::Mysql2::Client.class_eval do
19
+ extend Sinks::DSL
20
+
21
+ sink_before :query do |sql|
22
+ Helpers.scan(sql, "query")
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ Aikido::Zen::Sinks::Mysql2.load_sinks!
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../scanners/ssrf_scanner"
4
+ require_relative "../outbound_connection_monitor"
5
+
6
+ module Aikido::Zen
7
+ module Sinks
8
+ module Net
9
+ module HTTP
10
+ SINK = Sinks.add("net-http", scanners: [
11
+ Scanners::SSRFScanner,
12
+ OutboundConnectionMonitor
13
+ ])
14
+
15
+ module Helpers
16
+ # Maps a Net::HTTP connection to an Aikido OutboundConnection,
17
+ # which our tooling expects.
18
+ #
19
+ # @param http [Net::HTTP]
20
+ # @return [Aikido::Zen::OutboundConnection]
21
+ def self.build_outbound(http)
22
+ OutboundConnection.new(
23
+ host: http.address,
24
+ port: http.port
25
+ )
26
+ end
27
+
28
+ def self.wrap_request(req, session)
29
+ uri = req.uri if req.uri.is_a?(URI)
30
+ uri ||= URI(format("%<scheme>s://%<hostname>s:%<port>s%<path>s", {
31
+ scheme: session.use_ssl? ? "https" : "http",
32
+ hostname: session.address,
33
+ port: session.port,
34
+ path: req.path
35
+ }))
36
+
37
+ Scanners::SSRFScanner::Request.new(
38
+ verb: req.method,
39
+ uri: uri,
40
+ headers: req.to_hash,
41
+ header_normalizer: ->(val) { Array(val).join(", ") }
42
+ )
43
+ end
44
+
45
+ def self.wrap_response(response)
46
+ Scanners::SSRFScanner::Response.new(
47
+ status: response.code.to_i,
48
+ headers: response.to_hash,
49
+ header_normalizer: ->(val) { Array(val).join(", ") }
50
+ )
51
+ end
52
+
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
+ def self.load_sinks!
63
+ # In stdlib but not always required
64
+ require "net/http"
65
+
66
+ ::Net::HTTP.class_eval do
67
+ extend Sinks::DSL
68
+
69
+ sink_around :request do |original_call, req|
70
+ wrapped_request = Helpers.wrap_request(req, self)
71
+
72
+ # Store the request information so the DNS sinks can pick it up.
73
+ context = Aikido::Zen.current_context
74
+ if context
75
+ prev_request = context["ssrf.request"]
76
+ context["ssrf.request"] = wrapped_request
77
+ end
78
+
79
+ connection = Helpers.build_outbound(self)
80
+
81
+ Helpers.scan(wrapped_request, connection, "request")
82
+
83
+ response = original_call.call
84
+
85
+ Scanners::SSRFScanner.track_redirects(
86
+ request: wrapped_request,
87
+ response: Helpers.wrap_response(response)
88
+ )
89
+
90
+ response
91
+ ensure
92
+ context["ssrf.request"] = prev_request if context
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+
101
+ Aikido::Zen::Sinks::Net::HTTP.load_sinks!
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../scanners/ssrf_scanner"
4
+ require_relative "../outbound_connection_monitor"
5
+
6
+ module Aikido::Zen
7
+ module Sinks
8
+ module Patron
9
+ SINK = Sinks.add("patron", scanners: [
10
+ Scanners::SSRFScanner,
11
+ OutboundConnectionMonitor
12
+ ])
13
+
14
+ module Helpers
15
+ def self.wrap_response(request, response)
16
+ # In this case, automatic redirection happened inside libcurl.
17
+ if response.url != request.url && !response.url.to_s.empty?
18
+ Scanners::SSRFScanner::Response.new(
19
+ status: 302, # We can't know what the actual status was, but we just need a 3XX
20
+ headers: response.headers.merge("Location" => response.url)
21
+ )
22
+ else
23
+ Scanners::SSRFScanner::Response.new(
24
+ status: response.status,
25
+ headers: response.headers
26
+ )
27
+ end
28
+ end
29
+
30
+ def self.scan(request, connection, operation)
31
+ SINK.scan(
32
+ request: request,
33
+ connection: connection,
34
+ operation: operation
35
+ )
36
+ end
37
+ end
38
+
39
+ def self.load_sinks!
40
+ if Aikido::Zen.satisfy "patron", ">= 0.6.4"
41
+ require "patron"
42
+
43
+ ::Patron::Session.class_eval do
44
+ extend Sinks::DSL
45
+
46
+ sink_around :handle_request do |original_call, request|
47
+ wrapped_request = Scanners::SSRFScanner::Request.new(
48
+ verb: request.action,
49
+ uri: URI(request.url),
50
+ headers: request.headers
51
+ )
52
+
53
+ # Store the request information so the DNS sinks can pick it up.
54
+ context = Aikido::Zen.current_context
55
+ if context
56
+ prev_request = context["ssrf.request"]
57
+ context["ssrf.request"] = wrapped_request
58
+ end
59
+
60
+ connection = OutboundConnection.from_uri(URI(request.url))
61
+
62
+ Helpers.scan(wrapped_request, connection, "request")
63
+
64
+ response = original_call.call
65
+
66
+ Scanners::SSRFScanner.track_redirects(
67
+ request: wrapped_request,
68
+ response: Helpers.wrap_response(request, response)
69
+ )
70
+
71
+ # When libcurl has follow_location set, it will handle redirections
72
+ # internally, and expose the response.url as the URI that was last
73
+ # requested in the redirect chain.
74
+ #
75
+ # In this case, we can't actually stop the request from happening, but
76
+ # we can scan again (now that we know another request happened), to
77
+ # stop the response from being exposed to the user. This downgrades
78
+ # the SSRF into a blind SSRF, which is better than doing nothing.
79
+ if request.url != response.url && !response.url.to_s.empty?
80
+ last_effective_request = Scanners::SSRFScanner::Request.new(
81
+ verb: request.action,
82
+ uri: URI(response.url),
83
+ headers: request.headers
84
+ )
85
+ context["ssrf.request"] = last_effective_request if context
86
+
87
+ connection = OutboundConnection.from_uri(URI(response.url))
88
+
89
+ Helpers.scan(last_effective_request, connection, "request")
90
+ end
91
+
92
+ response
93
+ ensure
94
+ context["ssrf.request"] = prev_request if context
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+
103
+ Aikido::Zen::Sinks::Patron.load_sinks!
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aikido::Zen
4
+ module Sinks
5
+ module PG
6
+ SINK = Sinks.add("pg", scanners: [Scanners::SQLInjectionScanner])
7
+
8
+ module Helpers
9
+ # For some reason, the ActiveRecord pg adaptor does not wrap exceptions
10
+ # in ActiveRecord::StatementInvalid, leading to inconsistent handling.
11
+ # This guarantees that Aikido::Zen::SQLInjectionErrors are wrapped in
12
+ # an ActiveRecord::StatementInvalid.
13
+ def self.safe(&block)
14
+ # Code coverage is disabled here because this ActiveRecord behavior is
15
+ # exercised in end-to-end tests, which are not covered by SimpleCov.
16
+ # :nocov:
17
+ if !defined?(ActiveRecord::StatementInvalid)
18
+ Sinks::DSL.safe(&block)
19
+ else
20
+ begin
21
+ Sinks::DSL.safe(&block)
22
+ rescue Aikido::Zen::SQLInjectionError => err
23
+ raise ActiveRecord::StatementInvalid, cause: err
24
+ end
25
+ end
26
+ # :nocov:
27
+ end
28
+
29
+ def self.scan(query, operation)
30
+ SINK.scan(
31
+ query: query,
32
+ dialect: :postgresql,
33
+ operation: operation
34
+ )
35
+ end
36
+ end
37
+
38
+ def self.load_sinks!
39
+ if Aikido::Zen.satisfy "pg", ">= 1.0"
40
+ require "pg"
41
+
42
+ ::PG::Connection.class_eval do
43
+ extend Sinks::DSL
44
+
45
+ %i[
46
+ send_query exec sync_exec async_exec
47
+ send_query_params exec_params sync_exec_params async_exec_params
48
+ ].each do |method_name|
49
+ presafe_sink_before method_name do |query|
50
+ Helpers.safe do
51
+ Helpers.scan(query, method_name)
52
+ end
53
+ end
54
+ end
55
+
56
+ %i[
57
+ send_prepare prepare async_prepare sync_prepare
58
+ ].each do |method_name|
59
+ presafe_sink_before method_name do |_, query|
60
+ Helpers.safe do
61
+ Helpers.scan(query, method_name)
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ Aikido::Zen::Sinks::PG.load_sinks!
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../scanners/stored_ssrf_scanner"
4
+ require_relative "../scanners/ssrf_scanner"
5
+
6
+ module Aikido::Zen
7
+ module Sinks
8
+ module Resolv
9
+ SINK = Sinks.add("resolv", scanners: [
10
+ Scanners::StoredSSRFScanner,
11
+ Scanners::SSRFScanner
12
+ ])
13
+
14
+ module Helpers
15
+ def self.scan(name, addresses, operation)
16
+ context = Aikido::Zen.current_context
17
+ if context
18
+ context["dns.lookups"] ||= Scanners::SSRF::DNSLookups.new
19
+ context["dns.lookups"].add(name, addresses)
20
+ end
21
+
22
+ SINK.scan(
23
+ hostname: name,
24
+ addresses: addresses,
25
+ request: context && context["ssrf.request"],
26
+ operation: operation
27
+ )
28
+ end
29
+ end
30
+
31
+ def self.load_sinks!
32
+ # In stdlib but not always required
33
+ require "resolv"
34
+
35
+ ::Resolv.class_eval do
36
+ alias_method :each_address__internal_for_aikido_zen, :each_address
37
+
38
+ def each_address(*args, **kwargs, &blk)
39
+ # each_address is defined "manually" because no sink method pattern
40
+ # is applicable.
41
+
42
+ name, = args
43
+
44
+ addresses = []
45
+ each_address__internal_for_aikido_zen(*args, **kwargs) do |address|
46
+ addresses << address
47
+ blk.call(address)
48
+ end
49
+ ensure
50
+ # Ensure partial results are scanned.
51
+
52
+ Sinks::DSL.safe do
53
+ Helpers.scan(name, addresses, "lookup")
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ Aikido::Zen::Sinks::Resolv.load_sinks!
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "socket"
4
+ require_relative "../scanners/stored_ssrf_scanner"
5
+ require_relative "../scanners/ssrf_scanner"
6
+
7
+ module Aikido::Zen
8
+ module Sinks
9
+ # We intercept IPSocket.open to hook our DNS checks around it, since
10
+ # there's no way to access the internal DNS resolution that happens in C
11
+ # when using the socket primitives.
12
+ module Socket
13
+ SINK = Sinks.add("socket", scanners: [
14
+ Scanners::StoredSSRFScanner,
15
+ Scanners::SSRFScanner
16
+ ])
17
+
18
+ module Helpers
19
+ def self.scan(hostname, socket, operation)
20
+ # We're patching IPSocket.open(..) method.
21
+ # The IPSocket class hierarchy is:
22
+ # IPSocket
23
+ # / \
24
+ # TCPSocket UDPSocket
25
+ # / \
26
+ # TCPServer SOCKSSocket
27
+ #
28
+ # Because we want to scan only HTTP requests, we skip in case the
29
+ # socket is not *exactly* an instance of TCPSocket — it's any
30
+ # of it subclasses
31
+ return unless socket.instance_of?(TCPSocket)
32
+
33
+ # ["AF_INET", 80, "10.0.0.1", "10.0.0.1"]
34
+ address_family, _port, _hostname, numeric_address = socket.peeraddr(:numeric)
35
+
36
+ # We only care about IPv4 (AF_INET) or IPv6 (AF_INET6) sockets
37
+ # This might be overcautious, since this is _IP_Socket, so you
38
+ # would expect it's only used for IP connections?
39
+
40
+ # Code coverage is disabled here because the then clause is a no-op,
41
+ # so there is nothing to cover.
42
+ # :nocov:
43
+ return unless address_family.start_with?("AF_INET")
44
+ # :nocov:
45
+
46
+ context = Aikido::Zen.current_context
47
+ if context
48
+ context["dns.lookups"] ||= Scanners::SSRF::DNSLookups.new
49
+ context["dns.lookups"].add(hostname, numeric_address)
50
+ end
51
+
52
+ SINK.scan(
53
+ hostname: hostname,
54
+ addresses: [numeric_address],
55
+ request: context && context["ssrf.request"],
56
+ operation: operation
57
+ )
58
+ end
59
+ end
60
+
61
+ def self.load_sinks!
62
+ ::IPSocket.singleton_class.class_eval do
63
+ extend Sinks::DSL
64
+
65
+ sink_after :open do |socket, remote_host|
66
+ # Code coverage is disabled here because the tests are contrived and
67
+ # intentionally do not call open.
68
+ # :nocov:
69
+ Helpers.scan(remote_host, socket, "open")
70
+ # :nocov:
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ Aikido::Zen::Sinks::Socket.load_sinks!
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aikido::Zen
4
+ module Sinks
5
+ module SQLite3
6
+ SINK = Sinks.add("sqlite3", scanners: [Scanners::SQLInjectionScanner])
7
+
8
+ module Helpers
9
+ def self.scan(query, operation)
10
+ SINK.scan(
11
+ query: query,
12
+ dialect: :sqlite,
13
+ operation: operation
14
+ )
15
+ end
16
+ end
17
+
18
+ def self.load_sinks!
19
+ if Aikido::Zen.satisfy "sqlite3", ">= 1.0"
20
+ require "sqlite3"
21
+
22
+ ::SQLite3::Database.class_eval do
23
+ extend Sinks::DSL
24
+
25
+ private
26
+
27
+ # SQLite3::Database#exec_batch is an internal native private method.
28
+ sink_before :exec_batch do |sql|
29
+ Helpers.scan(sql, "exec_batch")
30
+ end
31
+ end
32
+
33
+ ::SQLite3::Statement.class_eval do
34
+ extend Sinks::DSL
35
+
36
+ sink_before :initialize do |_db, sql|
37
+ Helpers.scan(sql, "statement.execute")
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ Aikido::Zen::Sinks::SQLite3.load_sinks!