threatstack-agent-ruby 0.2.1 → 0.2.2

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