aikido-zen 1.0.0.pre.beta.1-arm64-darwin → 1.0.1.beta.3-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/README.md +67 -83
- data/lib/aikido/zen/api_client.rb +6 -3
- data/lib/aikido/zen/config.rb +11 -2
- data/lib/aikido/zen/context/rails_request.rb +3 -1
- data/lib/aikido/zen/context.rb +4 -0
- data/lib/aikido/zen/internals.rb +48 -13
- data/lib/aikido/zen/libzen-v0.1.39-arm64-darwin.dylib +0 -0
- data/lib/aikido/zen/middleware/request_tracker.rb +6 -4
- data/lib/aikido/zen/rails_engine.rb +5 -9
- data/lib/aikido/zen/request/heuristic_router.rb +6 -0
- data/lib/aikido/zen/request/rails_router.rb +5 -0
- data/lib/aikido/zen/sink.rb +5 -0
- 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 +33 -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.rb +42 -1
- 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/libzen.rake +70 -66
- metadata +17 -13
- data/CHANGELOG.md +0 -25
- data/lib/aikido/zen/libzen-v0.1.37.aarch64.dylib +0 -0
- data/lib/aikido.rb +0 -3
@@ -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 Curl
|
9
|
+
def self.load_sinks!
|
10
|
+
if Aikido::Zen.satisfy "curb", ">= 0.2.3"
|
11
|
+
require "curb"
|
12
|
+
|
13
|
+
::Curl::Easy.prepend(Curl::EasyExtensions)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
9
17
|
SINK = Sinks.add("curb", scanners: [
|
10
|
-
|
11
|
-
|
18
|
+
Scanners::SSRFScanner,
|
19
|
+
OutboundConnectionMonitor
|
12
20
|
])
|
13
21
|
|
14
|
-
module
|
22
|
+
module Helpers
|
15
23
|
def self.wrap_request(curl, url: curl.url)
|
16
|
-
|
24
|
+
Scanners::SSRFScanner::Request.new(
|
17
25
|
verb: nil, # Curb hides this by directly setting an option in C
|
18
26
|
uri: URI(url),
|
19
27
|
headers: curl.headers
|
@@ -33,29 +41,40 @@ module Aikido::Zen
|
|
33
41
|
status = curl.status.to_i
|
34
42
|
end
|
35
43
|
|
36
|
-
|
44
|
+
Scanners::SSRFScanner::Response.new(status: status, headers: headers)
|
37
45
|
end
|
38
46
|
|
39
|
-
def
|
40
|
-
|
47
|
+
def self.scan(request, connection, operation)
|
48
|
+
SINK.scan(
|
49
|
+
request: request,
|
50
|
+
connection: connection,
|
51
|
+
operation: operation
|
52
|
+
)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
module EasyExtensions
|
57
|
+
extend Sinks::DSL
|
58
|
+
|
59
|
+
sink_around :perform do |super_call|
|
60
|
+
wrapped_request = Helpers.wrap_request(self)
|
41
61
|
|
42
62
|
# Store the request information so the DNS sinks can pick it up.
|
43
|
-
|
63
|
+
context = Aikido::Zen.current_context
|
64
|
+
if context
|
44
65
|
prev_request = context["ssrf.request"]
|
45
66
|
context["ssrf.request"] = wrapped_request
|
46
67
|
end
|
47
68
|
|
48
|
-
|
49
|
-
connection: Aikido::Zen::OutboundConnection.from_uri(URI(url)),
|
50
|
-
request: wrapped_request,
|
51
|
-
operation: "request"
|
52
|
-
)
|
69
|
+
connection = OutboundConnection.from_uri(URI(url))
|
53
70
|
|
54
|
-
|
71
|
+
Helpers.scan(wrapped_request, connection, "request")
|
55
72
|
|
56
|
-
|
73
|
+
response = super_call.call
|
74
|
+
|
75
|
+
Scanners::SSRFScanner.track_redirects(
|
57
76
|
request: wrapped_request,
|
58
|
-
response:
|
77
|
+
response: Helpers.wrap_response(self)
|
59
78
|
)
|
60
79
|
|
61
80
|
# When libcurl has follow_location set, it will handle redirections
|
@@ -67,14 +86,21 @@ module Aikido::Zen
|
|
67
86
|
# stop the response from being exposed to the user. This downgrades
|
68
87
|
# the SSRF into a blind SSRF, which is better than doing nothing.
|
69
88
|
if url != last_effective_url
|
70
|
-
last_effective_request =
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
89
|
+
last_effective_request = Helpers.wrap_request(self, url: last_effective_url)
|
90
|
+
|
91
|
+
# Code coverage is disabled here because the else clause is a no-op,
|
92
|
+
# so there is nothing to cover.
|
93
|
+
# :nocov:
|
94
|
+
if context
|
95
|
+
context["ssrf.request"] = last_effective_request
|
96
|
+
else
|
97
|
+
# empty
|
98
|
+
end
|
99
|
+
# :nocov:
|
100
|
+
|
101
|
+
connection = OutboundConnection.from_uri(URI(last_effective_url))
|
102
|
+
|
103
|
+
Helpers.scan(last_effective_request, connection, "request")
|
78
104
|
end
|
79
105
|
|
80
106
|
response
|
@@ -86,4 +112,4 @@ module Aikido::Zen
|
|
86
112
|
end
|
87
113
|
end
|
88
114
|
|
89
|
-
|
115
|
+
Aikido::Zen::Sinks::Curl.load_sinks!
|
@@ -1,20 +1,45 @@
|
|
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 EventMachine
|
9
9
|
module HttpRequest
|
10
|
+
def self.load_sinks!
|
11
|
+
if Aikido::Zen.satisfy "em-http-request", ">= 1.0"
|
12
|
+
require "em-http-request"
|
13
|
+
|
14
|
+
::EventMachine::HttpRequest.use(EventMachine::HttpRequest::Middleware)
|
15
|
+
|
16
|
+
# NOTE: We can't use middleware to intercept requests as we want to ensure any
|
17
|
+
# modifications to the request from user-supplied middleware are already applied
|
18
|
+
# before we scan the request.
|
19
|
+
::EventMachine::HttpClient.prepend(EventMachine::HttpRequest::HttpClientExtensions)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
10
23
|
SINK = Sinks.add("em-http-request", scanners: [
|
11
|
-
|
12
|
-
|
24
|
+
Scanners::SSRFScanner,
|
25
|
+
OutboundConnectionMonitor
|
13
26
|
])
|
14
27
|
|
15
|
-
module
|
16
|
-
def
|
17
|
-
|
28
|
+
module Helpers
|
29
|
+
def self.scan(request, connection, operation)
|
30
|
+
SINK.scan(
|
31
|
+
request: request,
|
32
|
+
connection: connection,
|
33
|
+
operation: operation
|
34
|
+
)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
module HttpClientExtensions
|
39
|
+
extend Sinks::DSL
|
40
|
+
|
41
|
+
sink_before :send_request do
|
42
|
+
wrapped_request = Scanners::SSRFScanner::Request.new(
|
18
43
|
verb: req.method.to_s,
|
19
44
|
uri: URI(req.uri),
|
20
45
|
headers: req.headers
|
@@ -24,16 +49,12 @@ module Aikido::Zen
|
|
24
49
|
context = Aikido::Zen.current_context
|
25
50
|
context["ssrf.request"] = wrapped_request if context
|
26
51
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
port: req.port
|
31
|
-
),
|
32
|
-
request: wrapped_request,
|
33
|
-
operation: "request"
|
52
|
+
connection = OutboundConnection.new(
|
53
|
+
host: req.host,
|
54
|
+
port: req.port
|
34
55
|
)
|
35
56
|
|
36
|
-
|
57
|
+
Helpers.scan(wrapped_request, connection, "request")
|
37
58
|
end
|
38
59
|
end
|
39
60
|
|
@@ -43,13 +64,13 @@ module Aikido::Zen
|
|
43
64
|
context = Aikido::Zen.current_context
|
44
65
|
context["ssrf.request"] = nil if context
|
45
66
|
|
46
|
-
|
47
|
-
request:
|
67
|
+
Scanners::SSRFScanner.track_redirects(
|
68
|
+
request: Scanners::SSRFScanner::Request.new(
|
48
69
|
verb: client.req.method,
|
49
70
|
uri: URI(client.req.uri),
|
50
71
|
headers: client.req.headers
|
51
72
|
),
|
52
|
-
response:
|
73
|
+
response: Scanners::SSRFScanner::Response.new(
|
53
74
|
status: client.response_header.status,
|
54
75
|
headers: client.response_header.to_h
|
55
76
|
)
|
@@ -61,11 +82,4 @@ module Aikido::Zen
|
|
61
82
|
end
|
62
83
|
end
|
63
84
|
|
64
|
-
::EventMachine::HttpRequest
|
65
|
-
.use(Aikido::Zen::Sinks::EventMachine::HttpRequest::Middleware)
|
66
|
-
|
67
|
-
# NOTE: We can't use middleware to intercept requests as we want to ensure any
|
68
|
-
# modifications to the request from user-supplied middleware are already applied
|
69
|
-
# before we scan the request.
|
70
|
-
::EventMachine::HttpClient
|
71
|
-
.prepend(Aikido::Zen::Sinks::EventMachine::HttpRequest::Extensions)
|
85
|
+
Aikido::Zen::Sinks::EventMachine::HttpRequest.load_sinks!
|
@@ -1,31 +1,26 @@
|
|
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 Excon
|
9
|
-
|
10
|
-
Aikido::Zen
|
11
|
-
|
12
|
-
])
|
9
|
+
def self.load_sinks!
|
10
|
+
if Aikido::Zen.satisfy "excon", ">= 0.50.0"
|
11
|
+
require "excon"
|
13
12
|
|
14
|
-
|
15
|
-
|
16
|
-
#
|
17
|
-
# @param connection [Hash<Symbol, Object>] the data set in the connection.
|
18
|
-
# @param request [Hash<Symbol, Object>] the data overrides sent for each
|
19
|
-
# request.
|
20
|
-
#
|
21
|
-
# @return [Aikido::Zen::OutboundConnection]
|
22
|
-
def self.build_outbound(connection, request)
|
23
|
-
Aikido::Zen::OutboundConnection.new(
|
24
|
-
host: request.fetch(:hostname) { connection[:hostname] },
|
25
|
-
port: request.fetch(:port) { connection[:port] }
|
26
|
-
)
|
13
|
+
::Excon::Connection.prepend(ConnectionExtensions)
|
14
|
+
::Excon::Middleware::RedirectFollower.prepend(RedirectFollowerExtensions)
|
27
15
|
end
|
16
|
+
end
|
17
|
+
|
18
|
+
SINK = Sinks.add("excon", scanners: [
|
19
|
+
Scanners::SSRFScanner,
|
20
|
+
OutboundConnectionMonitor
|
21
|
+
])
|
28
22
|
|
23
|
+
module Helpers
|
29
24
|
def self.build_request(connection, request)
|
30
25
|
uri = URI(format("%<scheme>s://%<host>s:%<port>i%<path>s", {
|
31
26
|
scheme: request.fetch(:scheme) { connection[:scheme] },
|
@@ -35,45 +30,61 @@ module Aikido::Zen
|
|
35
30
|
}))
|
36
31
|
uri.query = request.fetch(:query) { connection[:query] }
|
37
32
|
|
38
|
-
|
33
|
+
Scanners::SSRFScanner::Request.new(
|
39
34
|
verb: request.fetch(:method) { connection[:method] },
|
40
35
|
uri: uri,
|
41
36
|
headers: connection[:headers].to_h.merge(request[:headers].to_h)
|
42
37
|
)
|
43
38
|
end
|
44
39
|
|
45
|
-
def request
|
46
|
-
|
40
|
+
def self.scan(request, connection, operation)
|
41
|
+
SINK.scan(
|
42
|
+
request: request,
|
43
|
+
connection: connection,
|
44
|
+
operation: operation
|
45
|
+
)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
module ConnectionExtensions
|
50
|
+
extend Sinks::DSL
|
51
|
+
|
52
|
+
sink_around :request do |super_call, params = {}|
|
53
|
+
request = Helpers.build_request(@data, params)
|
47
54
|
|
48
55
|
# Store the request information so the DNS sinks can pick it up.
|
49
|
-
|
56
|
+
context = Aikido::Zen.current_context
|
57
|
+
if context
|
50
58
|
prev_request = context["ssrf.request"]
|
51
59
|
context["ssrf.request"] = request
|
52
60
|
end
|
53
61
|
|
54
|
-
|
55
|
-
connection: Aikido::Zen::OutboundConnection.from_uri(request.uri),
|
56
|
-
request: request,
|
57
|
-
operation: "request"
|
58
|
-
)
|
62
|
+
connection = OutboundConnection.from_uri(request.uri)
|
59
63
|
|
60
|
-
|
64
|
+
Helpers.scan(request, connection, "request")
|
61
65
|
|
62
|
-
|
66
|
+
response = super_call.call
|
67
|
+
|
68
|
+
Scanners::SSRFScanner.track_redirects(
|
63
69
|
request: request,
|
64
|
-
response:
|
70
|
+
response: Scanners::SSRFScanner::Response.new(
|
65
71
|
status: response.status,
|
66
72
|
headers: response.headers.to_h
|
67
73
|
)
|
68
74
|
)
|
69
75
|
|
70
76
|
response
|
71
|
-
rescue ::
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
+
rescue Sinks::DSL::PresafeError => err
|
78
|
+
outer_cause = err.cause
|
79
|
+
case outer_cause
|
80
|
+
when ::Excon::Error::Socket
|
81
|
+
inner_cause = outer_cause.cause
|
82
|
+
# Excon wraps errors inside the lower level layer. This only happens
|
83
|
+
# to our scanning exceptions when a request is using RedirectFollower,
|
84
|
+
# so we unwrap them when it happens so host apps can handle errors
|
85
|
+
# consistently.
|
86
|
+
raise inner_cause if inner_cause.is_a?(Aikido::Zen::UnderAttackError)
|
87
|
+
end
|
77
88
|
raise
|
78
89
|
ensure
|
79
90
|
context["ssrf.request"] = prev_request if context
|
@@ -81,23 +92,30 @@ module Aikido::Zen
|
|
81
92
|
end
|
82
93
|
|
83
94
|
module RedirectFollowerExtensions
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
95
|
+
extend Sinks::DSL
|
96
|
+
|
97
|
+
sink_before :response_call do |datum|
|
98
|
+
response = datum[:response]
|
99
|
+
|
100
|
+
# Code coverage is disabled here because the else clause is a no-op,
|
101
|
+
# so there is nothing to cover.
|
102
|
+
# :nocov:
|
103
|
+
if !response.nil?
|
104
|
+
Scanners::SSRFScanner.track_redirects(
|
105
|
+
request: Helpers.build_request(datum, {}),
|
106
|
+
response: Scanners::SSRFScanner::Response.new(
|
89
107
|
status: response[:status],
|
90
108
|
headers: response[:headers]
|
91
109
|
)
|
92
110
|
)
|
111
|
+
else
|
112
|
+
# empty
|
93
113
|
end
|
94
|
-
|
95
|
-
super
|
114
|
+
# :nocov:
|
96
115
|
end
|
97
116
|
end
|
98
117
|
end
|
99
118
|
end
|
100
119
|
end
|
101
120
|
|
102
|
-
|
103
|
-
::Excon::Middleware::RedirectFollower.prepend(Aikido::Zen::Sinks::Excon::RedirectFollowerExtensions)
|
121
|
+
Aikido::Zen::Sinks::Excon.load_sinks!
|
@@ -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 Aikido::Zen.satisfy "http", ">= 1.0"
|
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!
|