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