threatstack-agent-ruby 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
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: []