threatstack-agent-ruby 0.2.1 → 0.2.2

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.
@@ -6,8 +6,8 @@ module Threatstack
6
6
  module Jobs
7
7
 
8
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)
9
+ def initialize(name_or_logger, initial_delay = 0, *args, &block)
10
+ @job = RecurrentJob.new(name_or_logger, 1, initial_delay, 1, *args, &block)
11
11
  end
12
12
 
13
13
  def stop
@@ -46,40 +46,34 @@ module Threatstack
46
46
  end
47
47
 
48
48
  def submit
49
- @logger.debug 'Starting event submission'
50
-
51
49
  # fetch events from the accumulator
52
- events = fetch_events
53
- @logger.debug "Found a total of #{events.length} events"
54
-
50
+ events = @accumulator.remove_events EVENTS_PER_REQ
55
51
  if events.empty?
56
52
  @logger.debug 'No events to report'
57
53
  return
58
54
  end
55
+ @logger.debug "Found a total of #{events.length} events"
59
56
 
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
57
+ # convert payload to JSON
58
+ json_payload = "[#{events.collect(&:to_json_string).join(', ')}]"
59
+ # submit http request
60
+ uri = URI.parse(APPSEC_BASE_URL + APPSEC_EVENTS_URL)
61
+ headers = {
62
+ 'Content-Type' => 'application/json',
63
+ 'bluefyre-agent-id' => AGENT_ID,
64
+ 'bluefyre-agent-instance-id' => AGENT_INSTANCE_ID
65
+ }
66
+ http = Net::HTTP.new(uri.host, uri.port)
67
+ http.use_ssl = true
68
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
69
+ req = Net::HTTP::Post.new(uri.request_uri, headers)
70
+ req.body = json_payload
71
+ @logger.debug "Sending #{events.length} events with payload: #{json_payload}"
72
+ begin
73
+ resp = http.request(req)
74
+ @logger.debug "Sent #{events.length} events, HTTP code: #{resp.code}"
75
+ rescue StandardError => e
76
+ @logger.error "StandardError: #{e.inspect}"
83
77
  end
84
78
  end
85
79
 
@@ -87,15 +81,6 @@ module Threatstack
87
81
  start
88
82
  @accumulator.add_event event
89
83
  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
84
  end
100
85
  end
101
86
  end
@@ -7,22 +7,31 @@ module Threatstack
7
7
  attr_reader :job_queue
8
8
  attr_reader :job_thread
9
9
  attr_reader :stopped
10
+ attr_reader :working
10
11
 
11
12
  def initialize
12
13
  @stopped = false
14
+ @working = false
13
15
  @job_queue = Queue.new
14
16
  @job_thread = Thread.new do
15
17
  # Fetch an item from the worker queue, or wait until one is available
16
18
  while (job = @job_queue.pop)
17
- Thread.stop if @stopped
18
- job[:action].call(job[:args])
19
+ if @stopped
20
+ # clear the queue
21
+ @job_queue.clear
22
+ break
23
+ end
24
+ @working = true
25
+ job[:action].call(*job[:args])
26
+ @working = false if @job_queue.empty?
19
27
  end
28
+ @working = false
20
29
  @stopped = true
21
30
  Thread.stop
22
31
  end
23
32
  end
24
33
 
25
- def add_job(args = nil, &block)
34
+ def add_job(*args, &block)
26
35
  raise 'Invalid proc provided' unless block_given?
27
36
 
28
37
  @job_queue.push(:action => block, :args => args)
@@ -30,8 +39,8 @@ module Threatstack
30
39
 
31
40
  def stop
32
41
  @stopped = true
33
- # push nil to stop the while loop
34
- add_job nil
42
+ # push empty job to stop the while loop
43
+ add_job(nil) {}
35
44
  end
36
45
  end
37
46
  end
@@ -12,7 +12,7 @@ module Threatstack
12
12
  attr_reader :stopped
13
13
  attr_reader :logger
14
14
 
15
- def initialize(name_or_logger, repeat_every_sec = 10, initial_delay_sec = 0, total_times = nil, args = nil, &block)
15
+ def initialize(name_or_logger, repeat_every_sec = 10, initial_delay_sec = 0, total_times = nil, *args, &block)
16
16
  raise 'Block not specified' unless block_given?
17
17
 
18
18
  # create logger or use passed one if available
@@ -43,7 +43,7 @@ module Threatstack
43
43
  # perform main action
44
44
  begin
45
45
  @logger.debug 'Calling job action'
46
- block.call args
46
+ block.call *args
47
47
  @logger.debug 'Job action called'
48
48
  rescue StandardError => e
49
49
  @logger.error "StandardError in job action: #{e.inspect}"
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thread'
4
+
5
+ module Threatstack
6
+ module Utils
7
+ class CappedQueue
8
+
9
+ def initialize(max)
10
+ @queue = []
11
+ @max = max
12
+ @mutex = Mutex.new
13
+ end
14
+
15
+ def length
16
+ @queue.length
17
+ end
18
+
19
+ def push(*args)
20
+ @mutex.synchronize do
21
+ @queue.push(*args)
22
+ @queue.shift(@queue.length - @max) if @queue.length > @max
23
+ end
24
+ end
25
+
26
+ def shift(*args)
27
+ @mutex.synchronize do
28
+ @queue.shift(*args)
29
+ end
30
+ end
31
+
32
+ def clear
33
+ @mutex.synchronize do
34
+ @queue.clear
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
data/lib/utils/logger.rb CHANGED
@@ -8,15 +8,17 @@ require_relative './formatter'
8
8
  module Threatstack
9
9
  module Utils
10
10
  module TSLogger
11
- def self.create(name)
11
+ COLORS = ['blue', 'gray', 'green', 'yellow', 'cyan', 'magenta'].freeze
12
+
13
+ def self.create(name, opts = {})
12
14
  formatter = TSFormatter.new
13
15
  logger = Logger.new STDOUT
14
16
  logger.progname = "TS#{name}"
15
17
  logger.datetime_format = '%FT%T%:z'
16
18
  logger.level = Threatstack::Constants::LOG_LEVEL
17
19
  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
+ log_color = opts[:color] || COLORS.sample
21
+ progname = colors ? formatter.send(log_color, '%16.16s' % logger.progname) : '%16.16s' % logger.progname
20
22
  logger.formatter = proc do |severity, datetime, _progname, msg|
21
23
  pid = Process.pid
22
24
  sev_padded = '%5.5s' % severity
@@ -5,7 +5,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
5
 
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = 'threatstack-agent-ruby'
8
- spec.version = '0.2.1'
8
+ spec.version = '0.2.2'
9
9
  spec.authors = ['Threat Stack Inc']
10
10
  spec.email = ['support@threatstack.com']
11
11
  spec.summary = 'Ruby version of the ThreatStack agent which helps identify security vulnerabilities at runtime'
@@ -13,7 +13,8 @@ Gem::Specification.new do |spec|
13
13
  spec.homepage = 'https://www.threatstack.com'
14
14
  spec.extra_rdoc_files = [
15
15
  "LICENSE"
16
- ]
16
+ ]
17
+ spec.required_ruby_version = '>= 1.8.7'
17
18
 
18
19
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
19
20
  `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)}) }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: threatstack-agent-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Threat Stack Inc
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-05-20 00:00:00.000000000 Z
11
+ date: 2021-03-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: network_interface
@@ -161,11 +161,15 @@ extra_rdoc_files:
161
161
  files:
162
162
  - Gemfile
163
163
  - LICENSE
164
+ - ci/requirements.txt
165
+ - ci/trigger.py
164
166
  - ext/libinjection/extconf.rb
165
167
  - ext/libinjection/libinjection.h
166
168
  - ext/libinjection/libinjection.i
167
169
  - ext/libinjection/libinjection_html5.c
168
170
  - ext/libinjection/libinjection_html5.h
171
+ - ext/libinjection/libinjection_pathtraversal.c
172
+ - ext/libinjection/libinjection_pathtraversal_data.h
169
173
  - ext/libinjection/libinjection_sqli.c
170
174
  - ext/libinjection/libinjection_sqli.h
171
175
  - ext/libinjection/libinjection_sqli_data.h
@@ -182,15 +186,17 @@ files:
182
186
  - lib/events/models/instrumentation_event.rb
183
187
  - lib/exceptions/request_blocked_error.rb
184
188
  - lib/instrumentation/common.rb
189
+ - lib/instrumentation/frameworks/kernel.rb
190
+ - lib/instrumentation/frameworks/rails.rb
191
+ - lib/instrumentation/frameworks/random.rb
185
192
  - lib/instrumentation/instrumenter.rb
186
- - lib/instrumentation/kernel.rb
187
- - lib/instrumentation/rails.rb
188
193
  - lib/jobs/delayed_job.rb
189
194
  - lib/jobs/event_submitter.rb
190
195
  - lib/jobs/job_queue.rb
191
196
  - lib/jobs/recurrent_job.rb
192
197
  - lib/threatstack-agent-ruby.rb
193
198
  - lib/utils/aws_utils.rb
199
+ - lib/utils/capped_queue.rb
194
200
  - lib/utils/formatter.rb
195
201
  - lib/utils/logger.rb
196
202
  - threatstack-agent-ruby.gemspec
@@ -206,14 +212,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
206
212
  requirements:
207
213
  - - ">="
208
214
  - !ruby/object:Gem::Version
209
- version: '0'
215
+ version: 1.8.7
210
216
  required_rubygems_version: !ruby/object:Gem::Requirement
211
217
  requirements:
212
218
  - - ">="
213
219
  - !ruby/object:Gem::Version
214
220
  version: '0'
215
221
  requirements: []
216
- rubygems_version: 3.1.2
222
+ rubyforge_project:
223
+ rubygems_version: 2.7.6
217
224
  signing_key:
218
225
  specification_version: 4
219
226
  summary: Ruby version of the ThreatStack agent which helps identify security vulnerabilities
@@ -1,45 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative './common.rb'
4
- require_relative './instrumenter.rb'
5
- require_relative '../utils/logger'
6
-
7
- module Threatstack
8
- module Instrumentation
9
- module TSKernel
10
- @@logger = Threatstack::Utils::TSLogger.create 'KernelInstrumentation'
11
-
12
- # methods to wrap
13
- METHOD_NAMES = ['exec', 'system', '`'].freeze
14
-
15
- def self.wrap_methods
16
- # executed every time a wrapped method is called
17
- on_method_call = Proc.new do |params|
18
- module_name = params[:target_class].name.downcase
19
- method_name = params[:method_name].downcase
20
- called_by = params[:caller_loc] ? params[:caller_loc].first : nil
21
- file_path = called_by ? called_by.absolute_path : nil
22
- # special case for ` method emulation
23
- if method_name == '`' && !file_path.nil? && file_path =~ /.*\/kernel\/agnostics\.rb$/
24
- called_by = params[:caller_loc][1]
25
- file_path = called_by ? called_by.absolute_path : nil
26
- end
27
- line_num = called_by ? called_by.lineno : nil
28
-
29
- arg = params[:args] ? params[:args].first : nil
30
- args = arg ? [arg] : []
31
-
32
- # create and queue the event
33
- Threatstack::Instrumentation.create_instrumentation_event(module_name, method_name, file_path, line_num, args)
34
- end
35
- @@logger.info "Instrumenting Kernel methods: #{METHOD_NAMES}"
36
- instrumenter = Threatstack::Instrumentation::Instrumenter.instance
37
- METHOD_NAMES.each do |method_name|
38
- instrumenter.wrap_class_method(Kernel, method_name, &on_method_call)
39
- instrumenter.wrap_instance_method(Kernel, method_name, &on_method_call)
40
- end
41
- end
42
-
43
- end
44
- end
45
- end
@@ -1,61 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'json'
4
-
5
- require_relative '../constants'
6
- require_relative '../exceptions/request_blocked_error'
7
- require_relative './common'
8
- require_relative '../utils/logger'
9
-
10
- module Threatstack
11
- module Instrumentation
12
- module TSRails
13
- @@logger = Threatstack::Utils::TSLogger.create 'RailsInstrumentation'
14
-
15
- def self.patch_action_controller
16
- @@logger.info 'Looking for Rails gem'
17
- return unless defined?(::Rails) && defined?(::Rails::VERSION)
18
- return unless defined?(ActionController) && defined?(ActionController::Base)
19
-
20
- @@logger.info "Rails #{Rails::VERSION::MAJOR.to_s} gem found, instrumenting ActionController"
21
- ActionController::Base.class_eval do
22
- include Threatstack::Instrumentation::TSRails::TSActionController
23
- end
24
- @@logger.info 'Rails instrumentation done'
25
- end
26
-
27
- module TSActionController
28
- include Threatstack::Constants
29
- @@logger = Threatstack::Utils::TSLogger.create 'RailsInstrumentation'
30
-
31
- def process_action(*args)
32
- # we need the headers hack below because Rails adds a lot of internal headers
33
- headers = request.headers.each_with_object({}) do |(k, v), obj|
34
- obj[k] = v if k.in?(CGI_VARIABLES) || k =~ /^HTTP_/
35
- end
36
- @@logger.debug("Incoming request: #{{ :headers => headers, :path => request.path_parameters,
37
- :query => Threatstack::Instrumentation.drop_sensitive_fields(request.query_parameters),
38
- :body => Threatstack::Instrumentation.drop_sensitive_fields(request.request_parameters) }}")
39
- backtrace = caller.join("\n")
40
-
41
- # check path/query/body parameters
42
- path_res = Threatstack::Instrumentation.check_parameters(request.path_parameters, 'path', request, headers, backtrace)
43
- query_res = Threatstack::Instrumentation.check_parameters(request.query_parameters, 'query', request, headers, backtrace)
44
- body_res = Threatstack::Instrumentation.check_parameters(request.request_parameters, 'body', request, headers, backtrace)
45
-
46
- @@logger.debug "RequestStats -- Path: #{path_res}, Query: #{query_res}, Body: #{body_res}"
47
- sqli_found = (path_res[:sqli] || query_res[:sqli] || body_res[:sqli])
48
- xss_found = (path_res[:xss] || query_res[:xss] || body_res[:xss])
49
- # raise an exception if any attack payloads were detected and blocking is enabled
50
- if (BLOCK_SQLI && sqli_found) || (BLOCK_XSS && xss_found)
51
- raise Threatstack::Exceptions::RequestBlockedError, REQUEST_BLOCKED
52
- end
53
-
54
- # continue processing the request normally if no issues were found
55
- super
56
- end
57
-
58
- end
59
- end
60
- end
61
- end