threatstack-agent-ruby 0.2.1

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 (37) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +3 -0
  3. data/LICENSE +6 -0
  4. data/ext/libinjection/extconf.rb +4 -0
  5. data/ext/libinjection/libinjection.h +65 -0
  6. data/ext/libinjection/libinjection.i +13 -0
  7. data/ext/libinjection/libinjection_html5.c +850 -0
  8. data/ext/libinjection/libinjection_html5.h +54 -0
  9. data/ext/libinjection/libinjection_sqli.c +2325 -0
  10. data/ext/libinjection/libinjection_sqli.h +298 -0
  11. data/ext/libinjection/libinjection_sqli_data.h +9654 -0
  12. data/ext/libinjection/libinjection_wrap.c +2393 -0
  13. data/ext/libinjection/libinjection_xss.c +532 -0
  14. data/ext/libinjection/libinjection_xss.h +21 -0
  15. data/lib/constants.rb +110 -0
  16. data/lib/control.rb +61 -0
  17. data/lib/events/event_accumulator.rb +36 -0
  18. data/lib/events/models/attack_event.rb +58 -0
  19. data/lib/events/models/base_event.rb +41 -0
  20. data/lib/events/models/dependency_event.rb +93 -0
  21. data/lib/events/models/environment_event.rb +93 -0
  22. data/lib/events/models/instrumentation_event.rb +46 -0
  23. data/lib/exceptions/request_blocked_error.rb +11 -0
  24. data/lib/instrumentation/common.rb +172 -0
  25. data/lib/instrumentation/instrumenter.rb +144 -0
  26. data/lib/instrumentation/kernel.rb +45 -0
  27. data/lib/instrumentation/rails.rb +61 -0
  28. data/lib/jobs/delayed_job.rb +26 -0
  29. data/lib/jobs/event_submitter.rb +101 -0
  30. data/lib/jobs/job_queue.rb +38 -0
  31. data/lib/jobs/recurrent_job.rb +61 -0
  32. data/lib/threatstack-agent-ruby.rb +7 -0
  33. data/lib/utils/aws_utils.rb +46 -0
  34. data/lib/utils/formatter.rb +47 -0
  35. data/lib/utils/logger.rb +43 -0
  36. data/threatstack-agent-ruby.gemspec +35 -0
  37. metadata +221 -0
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './recurrent_job'
4
+
5
+ module Threatstack
6
+ module Jobs
7
+
8
+ class DelayedJob
9
+ def initialize(name_or_logger, initial_delay = 0, args = nil, &block)
10
+ @job = RecurrentJob.new(name_or_logger,1, initial_delay, 1, args, &block)
11
+ end
12
+
13
+ def stop
14
+ @job.stop
15
+ end
16
+
17
+ def exec_count
18
+ @job.exec_count
19
+ end
20
+
21
+ def stopped
22
+ @job.stopped
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'singleton'
4
+ require 'net/https'
5
+ require 'thread'
6
+ require 'uri'
7
+
8
+ require_relative '../events/event_accumulator'
9
+ require_relative '../constants'
10
+ require_relative '../utils/logger'
11
+ require_relative './recurrent_job'
12
+
13
+ module Threatstack
14
+ module Jobs
15
+ # Singleton class that creates a job to submit events asynchronously
16
+ class EventSubmitter
17
+ include Singleton
18
+ include Threatstack::Constants
19
+
20
+ SUBMITTER_NAME = 'EventSubmitter'
21
+
22
+ attr_reader :job
23
+
24
+ def initialize
25
+ @pid = nil
26
+ @mutex = Mutex.new
27
+ @accumulator = Threatstack::Events::EventAccumulator.instance
28
+ end
29
+
30
+ # start recurrent job
31
+ def start
32
+ pid = Process.pid
33
+ if @pid != pid
34
+ @mutex.synchronize do
35
+ if @pid != pid
36
+ @logger = Threatstack::Utils::TSLogger.create SUBMITTER_NAME
37
+ @logger.debug 'Launching an EventSubmitter instance'
38
+ # clear events in case the accumulator was forked and already had queued events
39
+ @accumulator.clear_events
40
+ # submit every JOB_INTERVAL seconds
41
+ @job = RecurrentJob.new(@logger, JOB_INTERVAL, 5) { submit }
42
+ @pid = pid
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ def submit
49
+ @logger.debug 'Starting event submission'
50
+
51
+ # fetch events from the accumulator
52
+ events = fetch_events
53
+ @logger.debug "Found a total of #{events.length} events"
54
+
55
+ if events.empty?
56
+ @logger.debug 'No events to report'
57
+ return
58
+ end
59
+
60
+ # split events array into groups of EVENTS_PER_REQ
61
+ events.each_slice(EVENTS_PER_REQ) do |group|
62
+ # convert payload to JSON
63
+ json_payload = "[#{group.collect(&:to_json_string).join(', ')}]"
64
+ # submit http request
65
+ uri = URI.parse(APPSEC_BASE_URL + APPSEC_EVENTS_URL)
66
+ headers = {
67
+ 'Content-Type' => 'application/json',
68
+ 'bluefyre-agent-id' => AGENT_ID,
69
+ 'bluefyre-agent-instance-id' => AGENT_INSTANCE_ID
70
+ }
71
+ http = Net::HTTP.new(uri.host, uri.port)
72
+ http.use_ssl = true
73
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
74
+ req = Net::HTTP::Post.new(uri.request_uri, headers)
75
+ req.body = json_payload
76
+ @logger.debug "Sending #{group.length} events with payload: #{json_payload}"
77
+ begin
78
+ resp = http.request(req)
79
+ @logger.debug "Sent #{group.length} events, HTTP code: #{resp.code}"
80
+ rescue StandardError => e
81
+ @logger.error "StandardError: #{e.inspect}"
82
+ end
83
+ end
84
+ end
85
+
86
+ def queue_event(event)
87
+ start
88
+ @accumulator.add_event event
89
+ end
90
+
91
+ def fetch_events
92
+ # check total number of events first
93
+ total_events = @accumulator.events.length
94
+ return [] unless total_events > 0
95
+
96
+ # retrieve events from the accumulator in a thread-safe manner
97
+ @accumulator.remove_events total_events
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Threatstack
4
+ module Jobs
5
+
6
+ class JobQueue
7
+ attr_reader :job_queue
8
+ attr_reader :job_thread
9
+ attr_reader :stopped
10
+
11
+ def initialize
12
+ @stopped = false
13
+ @job_queue = Queue.new
14
+ @job_thread = Thread.new do
15
+ # Fetch an item from the worker queue, or wait until one is available
16
+ while (job = @job_queue.pop)
17
+ Thread.stop if @stopped
18
+ job[:action].call(job[:args])
19
+ end
20
+ @stopped = true
21
+ Thread.stop
22
+ end
23
+ end
24
+
25
+ def add_job(args = nil, &block)
26
+ raise 'Invalid proc provided' unless block_given?
27
+
28
+ @job_queue.push(:action => block, :args => args)
29
+ end
30
+
31
+ def stop
32
+ @stopped = true
33
+ # push nil to stop the while loop
34
+ add_job nil
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thread/every'
4
+
5
+ require_relative '../utils/logger'
6
+
7
+ module Threatstack
8
+ module Jobs
9
+
10
+ class RecurrentJob
11
+ attr_reader :exec_count
12
+ attr_reader :stopped
13
+ attr_reader :logger
14
+
15
+ def initialize(name_or_logger, repeat_every_sec = 10, initial_delay_sec = 0, total_times = nil, args = nil, &block)
16
+ raise 'Block not specified' unless block_given?
17
+
18
+ # create logger or use passed one if available
19
+ if name_or_logger.respond_to?(:debug) && name_or_logger.respond_to?(:info) && name_or_logger.respond_to?(:error)
20
+ @logger = name_or_logger
21
+ else
22
+ @logger = Threatstack::Utils::TSLogger.create "#{name_or_logger}Job"
23
+ end
24
+
25
+ @stopped = false
26
+ @exec_count = 0
27
+ @logger.debug "Creating recurrent job with #{initial_delay_sec} sec delay, repeat every #{repeat_every_sec} sec, #{total_times || 'infinite'} time(s)"
28
+ Thread.new do
29
+ # wait before starting
30
+ sleep initial_delay_sec
31
+ # repeat every X secs
32
+ Thread.every(repeat_every_sec) do
33
+ @logger.debug 'Starting job iteration...'
34
+ # stop if stop method called or exec limit reached
35
+ if @stopped || (!total_times.nil? && @exec_count == total_times)
36
+ @logger.debug 'Job stopped'
37
+ @stopped = true
38
+ Thread.stop
39
+ return
40
+ end
41
+ # increment
42
+ @exec_count += 1
43
+ # perform main action
44
+ begin
45
+ @logger.debug 'Calling job action'
46
+ block.call args
47
+ @logger.debug 'Job action called'
48
+ rescue StandardError => e
49
+ @logger.error "StandardError in job action: #{e.inspect}"
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ def stop
56
+ @logger.debug 'Stopping recurrent job'
57
+ @stopped = true
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './constants'
4
+ require_relative './control'
5
+
6
+ # initialize agent
7
+ Threatstack::Control._setup_agent
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ require_relative './logger'
6
+ require_relative '../constants'
7
+
8
+ module Threatstack
9
+ module Utils
10
+ class Aws
11
+ include Singleton
12
+ include Threatstack::Constants
13
+
14
+ def initialize
15
+ @logger = Threatstack::Utils::TSLogger.create 'AwsUtils'
16
+ end
17
+
18
+ def get_aws_metadata
19
+ aws_metadata = {}
20
+ timeout = 5 # secs
21
+
22
+ begin
23
+ # prepare request
24
+ @logger.debug 'Preparing request to fetch AWS metadata'
25
+ aws_uri = URI(AWS_METADATA_URL)
26
+ http = Net::HTTP.new(aws_uri.host, aws_uri.port)
27
+ http.open_timeout = timeout
28
+ http.read_timeout = timeout
29
+ # http.keep_alive_timeout = 2
30
+ req = Net::HTTP::Get.new(aws_uri.path)
31
+ # fire request
32
+ @logger.debug 'Firing request to fetch AWS metadata'
33
+ resp = http.request(req)
34
+ @logger.debug "AWS metadata request HTTP code #{resp.code} and response: #{resp.body}"
35
+ # convert response to hash
36
+ aws_metadata = resp.body ? JSON.parse(resp.body) : aws_metadata
37
+ rescue StandardError => e
38
+ @logger.error "StandardError: #{e.inspect}"
39
+ end
40
+
41
+ # return
42
+ aws_metadata
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Threatstack
4
+ module Utils
5
+ class TSFormatter
6
+ def regular(str)
7
+ "\e[00m#{str}\e[0m"
8
+ end
9
+
10
+ def bright(str)
11
+ "\e[01m#{str}\e[0m"
12
+ end
13
+
14
+ def black(str)
15
+ "\e[30m#{str}\e[0m"
16
+ end
17
+
18
+ def red(str)
19
+ "\e[31m#{str}\e[0m"
20
+ end
21
+
22
+ def green(str)
23
+ "\e[32m#{str}\e[0m"
24
+ end
25
+
26
+ def yellow(str)
27
+ "\e[33m#{str}\e[0m"
28
+ end
29
+
30
+ def blue(str)
31
+ "\e[34m#{str}\e[0m"
32
+ end
33
+
34
+ def magenta(str)
35
+ "\e[35m#{str}\e[0m"
36
+ end
37
+
38
+ def cyan(str)
39
+ "\e[36m#{str}\e[0m"
40
+ end
41
+
42
+ def gray(str)
43
+ "\e[37m#{str}\e[0m"
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
5
+ require_relative '../constants'
6
+ require_relative './formatter'
7
+
8
+ module Threatstack
9
+ module Utils
10
+ module TSLogger
11
+ def self.create(name)
12
+ formatter = TSFormatter.new
13
+ logger = Logger.new STDOUT
14
+ logger.progname = "TS#{name}"
15
+ logger.datetime_format = '%FT%T%:z'
16
+ logger.level = Threatstack::Constants::LOG_LEVEL
17
+ colors = Threatstack::Constants::LOG_COLORS
18
+ random_color = ['blue', 'gray', 'green', 'yellow', 'cyan', 'magenta'].sample
19
+ progname = colors ? formatter.send(random_color, '%16.16s' % logger.progname) : '%16.16s' % logger.progname
20
+ logger.formatter = proc do |severity, datetime, _progname, msg|
21
+ pid = Process.pid
22
+ sev_padded = '%5.5s' % severity
23
+ sev_final = sev_padded
24
+ if colors
25
+ pid = formatter.green pid
26
+ if severity == 'INFO'
27
+ sev_final = formatter.cyan sev_padded
28
+ elsif severity == 'WARN'
29
+ sev_final = formatter.yellow sev_padded
30
+ elsif severity == 'ERROR' || severity == 'FATAL'
31
+ sev_final = formatter.red sev_padded
32
+ else
33
+ sev_final = formatter.regular sev_padded
34
+ end
35
+ end
36
+
37
+ "[#{datetime}] #{pid} - #{sev_final} - #{progname}: #{msg}\n"
38
+ end
39
+ logger
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'threatstack-agent-ruby'
8
+ spec.version = '0.2.1'
9
+ spec.authors = ['Threat Stack Inc']
10
+ spec.email = ['support@threatstack.com']
11
+ spec.summary = 'Ruby version of the ThreatStack agent which helps identify security vulnerabilities at runtime'
12
+ spec.license = 'Threat Stack'
13
+ spec.homepage = 'https://www.threatstack.com'
14
+ spec.extra_rdoc_files = [
15
+ "LICENSE"
16
+ ]
17
+
18
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
19
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^((test|spec|features|)/|Gemfile_release|Rakefile|README.md|.gitlab-ci.yml|.rubocop.yml|Gemfile.lock|.gitignore)}) }
20
+ end
21
+ spec.require_paths = ['lib']
22
+ spec.extensions = ['ext/libinjection/extconf.rb']
23
+
24
+ spec.add_dependency 'network_interface', '0.0.2'
25
+ spec.add_dependency 'Platform', '0.4.2'
26
+ spec.add_dependency 'thread', '0.2.2'
27
+
28
+ spec.add_development_dependency 'rubocop'
29
+ spec.add_development_dependency 'aws-sdk-s3'
30
+ spec.add_development_dependency 'builder'
31
+ spec.add_development_dependency 'rake'
32
+ spec.add_development_dependency 'rspec'
33
+ spec.add_development_dependency 'simplecov'
34
+ spec.add_development_dependency 'webmock'
35
+ end
metadata ADDED
@@ -0,0 +1,221 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: threatstack-agent-ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.1
5
+ platform: ruby
6
+ authors:
7
+ - Threat Stack Inc
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-05-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: network_interface
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 0.0.2
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 0.0.2
27
+ - !ruby/object:Gem::Dependency
28
+ name: Platform
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '='
32
+ - !ruby/object:Gem::Version
33
+ version: 0.4.2
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '='
39
+ - !ruby/object:Gem::Version
40
+ version: 0.4.2
41
+ - !ruby/object:Gem::Dependency
42
+ name: thread
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '='
46
+ - !ruby/object:Gem::Version
47
+ version: 0.2.2
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '='
53
+ - !ruby/object:Gem::Version
54
+ version: 0.2.2
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: aws-sdk-s3
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: builder
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rake
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rspec
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: simplecov
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: webmock
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ description:
154
+ email:
155
+ - support@threatstack.com
156
+ executables: []
157
+ extensions:
158
+ - ext/libinjection/extconf.rb
159
+ extra_rdoc_files:
160
+ - LICENSE
161
+ files:
162
+ - Gemfile
163
+ - LICENSE
164
+ - ext/libinjection/extconf.rb
165
+ - ext/libinjection/libinjection.h
166
+ - ext/libinjection/libinjection.i
167
+ - ext/libinjection/libinjection_html5.c
168
+ - ext/libinjection/libinjection_html5.h
169
+ - ext/libinjection/libinjection_sqli.c
170
+ - ext/libinjection/libinjection_sqli.h
171
+ - ext/libinjection/libinjection_sqli_data.h
172
+ - ext/libinjection/libinjection_wrap.c
173
+ - ext/libinjection/libinjection_xss.c
174
+ - ext/libinjection/libinjection_xss.h
175
+ - lib/constants.rb
176
+ - lib/control.rb
177
+ - lib/events/event_accumulator.rb
178
+ - lib/events/models/attack_event.rb
179
+ - lib/events/models/base_event.rb
180
+ - lib/events/models/dependency_event.rb
181
+ - lib/events/models/environment_event.rb
182
+ - lib/events/models/instrumentation_event.rb
183
+ - lib/exceptions/request_blocked_error.rb
184
+ - lib/instrumentation/common.rb
185
+ - lib/instrumentation/instrumenter.rb
186
+ - lib/instrumentation/kernel.rb
187
+ - lib/instrumentation/rails.rb
188
+ - lib/jobs/delayed_job.rb
189
+ - lib/jobs/event_submitter.rb
190
+ - lib/jobs/job_queue.rb
191
+ - lib/jobs/recurrent_job.rb
192
+ - lib/threatstack-agent-ruby.rb
193
+ - lib/utils/aws_utils.rb
194
+ - lib/utils/formatter.rb
195
+ - lib/utils/logger.rb
196
+ - threatstack-agent-ruby.gemspec
197
+ homepage: https://www.threatstack.com
198
+ licenses:
199
+ - Threat Stack
200
+ metadata: {}
201
+ post_install_message:
202
+ rdoc_options: []
203
+ require_paths:
204
+ - lib
205
+ required_ruby_version: !ruby/object:Gem::Requirement
206
+ requirements:
207
+ - - ">="
208
+ - !ruby/object:Gem::Version
209
+ version: '0'
210
+ required_rubygems_version: !ruby/object:Gem::Requirement
211
+ requirements:
212
+ - - ">="
213
+ - !ruby/object:Gem::Version
214
+ version: '0'
215
+ requirements: []
216
+ rubygems_version: 3.1.2
217
+ signing_key:
218
+ specification_version: 4
219
+ summary: Ruby version of the ThreatStack agent which helps identify security vulnerabilities
220
+ at runtime
221
+ test_files: []