paraxial 0.3.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ea5f2156685b5204495019d6a72ad0f433326438abfef4e70e26b39ab62bf58b
4
- data.tar.gz: 4dd855ada83c429df5422b42177af07863041ef94feb14912375df5f8d18e807
3
+ metadata.gz: fa19e78278421371d0e25cd1e387383676df44a7d09ec936520e6e2e9a4fa70f
4
+ data.tar.gz: 807b6b4401164502c8b38ea4b6ff861c667b0aee5fcd124d68a5f98c19ed5e38
5
5
  SHA512:
6
- metadata.gz: 5f458988de37e4a6c9c0a8f8e1489b613925faf48d11dc59c1addbcca49acc740292cddae12bd675c25c8c640a2f45133e5ed6d9232c8319f74fe868c2e2396e
7
- data.tar.gz: 81504558ae7231a7f5f5397f6ca640faf63532c15686d61942d54a306c0d28b865f3e11e8c17973729a1b200c74ad3246f149ca46a53635e91dfa229fe57a998
6
+ metadata.gz: f99fc3adc1dab0e302e45b26b2a245055b2b03eac517e5640b7f18828de1a78d64362a37811be9221b4f5c560feff727324b171d1c9493609b513de43579780a
7
+ data.tar.gz: fbe25d2549601ab95b67934deda8dd1e070d7ced5cb0abf570f85b8650acc9700d35c4274f3460a25d434a6f09c67ad80b85927564da3ba78a1b1dfa9b9ed2d0
@@ -0,0 +1,138 @@
1
+ require 'rpatricia'
2
+ module Paraxial
3
+ module Checker
4
+ @allows = { 'v4' => Patricia.new, 'v6' => Patricia.new(:AF_INET6) }
5
+ @bans = { 'v4' => Patricia.new, 'v6' => Patricia.new(:AF_INET6) }
6
+
7
+
8
+ if Paraxial::Helpers.get_api_key
9
+ @thread = Thread.new do
10
+ loop do
11
+ get_abr
12
+ sleep(10)
13
+ end
14
+ end
15
+ end
16
+
17
+ def self.get_abr
18
+ uri = URI.parse(Paraxial::Helpers.get_paraxial_url + '/api/abr')
19
+ headers = { 'Content-Type': 'application/json' }
20
+
21
+ body = { api_key: Paraxial::Helpers.get_api_key }
22
+ begin
23
+ r = Net::HTTP.post(uri, body.to_json, headers)
24
+ if r.code == '200'
25
+ put_abr(JSON.parse(r.body))
26
+ else
27
+ 'ab_failed'
28
+ end
29
+ rescue StandardError => e
30
+ puts '[Paraxial] HTTP connection to backend failed, check configuration'
31
+ 'ab_failed'
32
+ end
33
+ end
34
+
35
+ def self.put_abr(abr)
36
+ # Expected input: a hash
37
+ # {"allows"=>[{"address"=>[96, 56, 162, 210], "netmask"=>32}],
38
+ # "bans"=>
39
+ # [{"address"=>[8193, 3512, 34211, 0, 0, 35374, 880, 29492], "netmask"=>128},
40
+ # {"address"=>[111, 56, 162, 210], "netmask"=>32}],
41
+ # "rules"=>[]}
42
+ ipv4_a = []
43
+ ipv4_b = []
44
+ ipv6_a = []
45
+ ipv6_b = []
46
+ abr.each do |key, value|
47
+ next if key == 'rules' # skip rules for now
48
+
49
+ value.each do |ip|
50
+ address = ip['address']
51
+ if address.length == 4
52
+ if key == 'allows'
53
+ ipv4_a << address.join('.')
54
+ elsif key == 'bans'
55
+ ipv4_b << address.join('.')
56
+ end
57
+ elsif key == 'allows'
58
+ ipv6_a << address.map { |n| n.to_s(16).rjust(4, '0') }.join(':')
59
+ elsif key == 'bans'
60
+ ipv6_b << address.map { |n| n.to_s(16).rjust(4, '0') }.join(':')
61
+ end
62
+ end
63
+ end
64
+
65
+ ab = { 'allows' => { 'v4' => ipv4_a, 'v6' => ipv6_a }, 'bans' => { 'v4' => ipv4_b, 'v6' => ipv6_b } }
66
+ Paraxial::Checker.put(ab)
67
+ end
68
+
69
+ def self.put(allow_ban)
70
+ allows = allow_ban['allows']
71
+ bans = allow_ban['bans']
72
+ @allows = { 'v4' => create_patricia(allows['v4'], 'v4'), 'v6' => create_patricia(allows['v6'], 'v6') }
73
+ @bans = { 'v4' => create_patricia(bans['v4'], 'v4'), 'v6' => create_patricia(bans['v6'], 'v6') }
74
+ :ok
75
+ end
76
+
77
+ def self.create_patricia(list, type)
78
+ if type == 'v4'
79
+ p = Patricia.new
80
+ list.each do |ip|
81
+ p.add(ip)
82
+ end
83
+ p
84
+ elsif type == 'v6'
85
+ p = Patricia.new(:AF_INET6)
86
+ list.each do |ip|
87
+ p.add(ip)
88
+ end
89
+ p
90
+ else
91
+ raise 'Wrong type in Paraxial::Checker.create_patricia'
92
+ end
93
+ end
94
+
95
+ def self.ban_ip(ip)
96
+ if ip.include?('.')
97
+ # IPv4
98
+ current_t = @bans['v4']
99
+ current_t.add(ip)
100
+ @bans['v4'] = current_t
101
+ else
102
+ # IPv6
103
+ current_t = @bans['v6']
104
+ current_t.add(ip)
105
+ @bans['v6'] = current_t
106
+ end
107
+
108
+ uri = URI.parse(Paraxial::Helpers.get_ban_url)
109
+ headers = { 'Content-Type': 'application/json' }
110
+
111
+ body = { api_key: Paraxial::Helpers.get_api_key, ip_address: ip }
112
+ r = Net::HTTP.post(uri, body.to_json, headers)
113
+ if r.code == '200'
114
+ :ok
115
+ else
116
+ :error
117
+ end
118
+ end
119
+
120
+ def self.allow_ip?(ip)
121
+ if ip.include?('.')
122
+ if !@allows['v4'].search_best(ip).nil? # v4 on allow list
123
+ true
124
+ elsif !@bans['v4'].search_best(ip).nil? # v4 on ban list
125
+ false
126
+ else # v4 on no list
127
+ true
128
+ end
129
+ elsif !@allows['v6'].search_best(ip).nil? # v6 on allow list
130
+ true
131
+ elsif !@bans['v6'].search_best(ip).nil? # v6 on ban list
132
+ false
133
+ else # v6 on no list
134
+ true
135
+ end
136
+ end
137
+ end
138
+ end
data/lib/paraxial/cli.rb CHANGED
@@ -18,15 +18,8 @@ module Paraxial
18
18
 
19
19
  def scan
20
20
  puts '[Paraxial] Scan starting...'
21
- if check_rubocop_configuration
22
- puts '[Paraxial] .rubocop.yml contains the required paraxial configuration.'
23
- else
24
- puts '[Paraxial] .rubocop.yml does not contain the required paraxial configuration.'
25
- puts '[Paraxial] How to configure: TODO_URL'
26
- exit
27
- end
28
21
 
29
- if ENV['PARAXIAL_API_KEY'].nil?
22
+ if Paraxial::Helpers.get_api_key.nil?
30
23
  puts '[Paraxial] Environment variable PARAXIAL_API_KEY not found'
31
24
  else
32
25
  github_app = options[:github_app]
@@ -36,24 +29,35 @@ module Paraxial
36
29
  pr_number = options[:pr_number]
37
30
 
38
31
  cops = 'Paraxial,Security/Eval,Security/IoMethods,Security/JSONLoad,Security/MarshalLoad,Security/Open,Security/YAMLLoad'
39
- rubocop = `rubocop --only #{cops} --format json`
32
+ rubocop = `rubocop --require paraxial --only #{cops} --disable-pending-cops --format json`
40
33
  lockfile = File.read('./Gemfile.lock')
41
34
  api_key = ENV['PARAXIAL_API_KEY']
42
35
  uri = URI.parse(Paraxial::Helpers.get_paraxial_url + '/api/ruby_scan')
43
36
  headers = { 'Content-Type': 'application/json' }
44
37
 
45
- body = { rubocop:, lockfile:, api_key:, timestamp: Paraxial.get_timestamp }
38
+ body = { rubocop: rubocop, lockfile: lockfile, api_key: api_key, timestamp: Paraxial.get_timestamp }
46
39
  response = Net::HTTP.post(uri, body.to_json, headers)
47
- puts "[Paraxial] scan result: #{response.body}"
40
+ m = JSON.parse(response.body)
41
+ findings = m['ok']['findings']
42
+ puts
43
+ puts "[Paraxial] Scan count #{findings.length}"
44
+ puts
45
+ findings.each do |finding|
46
+ puts finding
47
+ puts
48
+ end
49
+ puts "[Paraxial] Scan UUID #{m['ok']['scan_uuid']}"
50
+ puts "[Paraxial] Scan URL #{m['ok']['scan_url']}"
48
51
  github_valid = (!!github_app and !!install_id and !!repo_owner and !!repo_name and !!pr_number)
49
52
 
50
53
  if github_app and github_valid == false
51
54
  puts '[Paraxial] --github_app missing arguments'
52
55
  puts '[Paraxial] Required: --github_app, --install_id, --repo_owner, --repo_name, --pr_number'
53
56
  elsif github_app and github_valid
54
- uuid_regex = /UUID\s+(\S+)/
55
- match = response.body.match(uuid_regex)
56
- uuid = match[1] if match
57
+ # uuid_regex = /UUID\s+(\S+)/
58
+ # match = response.body.match(uuid_regex)
59
+ # uuid = match[1] if match
60
+ uuid = m['ok']['scan_uuid']
57
61
  if uuid
58
62
  final_uuid = uuid.chomp('.')
59
63
  censored_backend_map = {
@@ -85,21 +89,5 @@ module Paraxial
85
89
  end
86
90
  end
87
91
 
88
- private
89
-
90
- def check_rubocop_configuration
91
- rubocop_file = File.join(Dir.pwd, '.rubocop.yml')
92
-
93
- return false unless File.exist?(rubocop_file)
94
-
95
- config = YAML.load_file(rubocop_file)
96
- required_key = 'require'
97
-
98
- if config.is_a?(Hash) && config[required_key].is_a?(Array)
99
- config[required_key].include?('paraxial')
100
- else
101
- false
102
- end
103
- end
104
92
  end
105
93
  end
@@ -3,5 +3,17 @@ module Paraxial
3
3
  def self.get_paraxial_url
4
4
  @paraxial_url ||= ENV['PARAXIAL_URL'] || 'https://app.paraxial.io/'
5
5
  end
6
+
7
+ def self.get_ban_url
8
+ get_paraxial_url + '/api/ban_ip'
9
+ end
10
+
11
+ def self.get_exploit_url
12
+ get_paraxial_url + '/api/exploit'
13
+ end
14
+
15
+ def self.get_api_key
16
+ @paraxial_api_key ||= ENV['PARAXIAL_API_KEY']
17
+ end
6
18
  end
7
19
  end
@@ -0,0 +1,37 @@
1
+ unless Rails.env.test? || File.basename($0) == 'rake' || defined?(Rails::Generators)
2
+ module Marshal
3
+ class << self
4
+ alias_method :original_load, :load
5
+
6
+ def load(source, proc = nil)
7
+ exg = Paraxial.configuration.exploit_guard
8
+ if [:monitor, :block].include?(exg)
9
+ if source.is_a?(String) && source.match?(/ActionView|Net::BufferedIO|ERB|ActiveSupport/)
10
+ puts "[Paraxial] Exploit Guard triggered, malicious input to Marshal.load"
11
+ puts source
12
+
13
+ m = {
14
+ "api_key" => Paraxial::Helpers.get_api_key,
15
+ "mode" => exg,
16
+ "message" => "Marshal.load exploit behavior detected: #{Base64.encode64(source)}"
17
+ }
18
+ headers = { 'Content-Type': 'application/json' }
19
+ uri = URI.parse(Paraxial::Helpers.get_exploit_url)
20
+ Thread.new do
21
+ Net::HTTP.post(uri, m.to_json, headers)
22
+ end
23
+ if exg == :monitor
24
+ original_load(source, proc)
25
+ else
26
+ :block
27
+ end
28
+ else
29
+ original_load(source, proc)
30
+ end
31
+ else
32
+ original_load(source, proc)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -2,65 +2,75 @@ require 'bundler'
2
2
  require 'paraxial'
3
3
  require 'rpatricia'
4
4
  require_relative '../helpers'
5
+ require_relative '../checker'
5
6
 
6
7
  Bundler.setup
7
8
 
8
- Rails.application.config.to_prepare do
9
- puts '[Paraxial] Init start'
10
- api_key = ENV['PARAXIAL_API_KEY']
9
+ unless Rails.env.test? || File.basename($0) == 'rake' || defined?(Rails::Generators)
10
+ Rails.application.config.to_prepare do
11
+ puts '[Paraxial] Agent starting...'
12
+ api_key = Paraxial::Helpers.get_api_key
11
13
 
12
- if api_key.nil?
13
- puts '[Paraxial] Init PARAXIAL_API_KEY key not set, agent not started'
14
- elsif Rails.env.test?
15
- puts '[Paraxial] Init Test environment detected, agent not started'
16
- else
17
- begin
18
- puts '[Paraxial] Init config valid, agent starting'
19
- deps_and_licenses = []
20
- Bundler.load.specs.each do |spec|
21
- # Print the gem name and license
22
- h = { name: spec.name, version: spec.version.to_s, description: Paraxial.trim_dep(spec.description),
23
- license: spec.license || 'None' }
24
- deps_and_licenses << h
25
- end
26
- deps_and_licenses << { name: 'ruby', version: RUBY_VERSION, description: 'The Ruby Programming Language',
27
- license: 'Ruby' }
28
- uri = URI.parse(Paraxial::Helpers.get_paraxial_url + '/api/ruby_app_lic')
29
- headers = { 'Content-Type': 'application/json' }
14
+ if api_key.nil?
15
+ puts '[Paraxial] PARAXIAL_API_KEY key not set, agent not started'
16
+ elsif Rails.env.test?
17
+ puts '[Paraxial] Test environment detected, agent not started'
18
+ else
19
+ begin
20
+ puts '[Paraxial] API key detected, agent starting'
30
21
 
31
- body = { app_lic: deps_and_licenses, api_key:, timestamp: Paraxial.get_timestamp }
32
- cloud_uri = URI.parse(Paraxial::Helpers.get_paraxial_url + '/api/cloud_ip_list')
33
- response = Net::HTTP.get(cloud_uri)
22
+ Paraxial.check_exploit_guard
34
23
 
35
- Thread.new do
36
- Net::HTTP.post(uri, body.to_json, headers)
37
- end
24
+ deps_and_licenses = []
25
+ Bundler.load.specs.each do |spec|
26
+ # Print the gem name and license
27
+ h = { name: spec.name, version: spec.version.to_s, description: Paraxial.trim_dep(spec.description),
28
+ license: spec.license || 'None' }
29
+ deps_and_licenses << h
30
+ end
31
+ deps_and_licenses << { name: 'ruby', version: RUBY_VERSION, description: 'The Ruby Programming Language',
32
+ license: 'Ruby' }
33
+ uri = URI.parse(Paraxial::Helpers.get_paraxial_url + '/api/ruby_app_lic')
34
+ headers = { 'Content-Type': 'application/json' }
35
+
36
+ body = { app_lic: deps_and_licenses, api_key: api_key, timestamp: Paraxial.get_timestamp }
37
+ cloud_uri = URI.parse(Paraxial::Helpers.get_paraxial_url + '/api/cloud_ip_list')
38
+ response = Net::HTTP.get(cloud_uri)
39
+
40
+ Thread.new do
41
+ Net::HTTP.post(uri, body.to_json, headers)
42
+ end
38
43
 
39
- # https://github.com/jkitching/rpatricia
40
- pt_v4 = Patricia.new
41
- pt_v6 = Patricia.new(:AF_INET6)
42
- cloud_list = JSON.parse(response)
43
- cloud_list.each do |k, v|
44
- if k.include?('::')
45
- pt_v6.add(k, v)
46
- else
47
- pt_v4.add(k, v)
44
+ # https://github.com/jkitching/rpatricia
45
+ pt_v4 = Patricia.new
46
+ pt_v6 = Patricia.new(:AF_INET6)
47
+ cloud_list = JSON.parse(response)
48
+ cloud_list.each do |k, v|
49
+ if k.include?('::')
50
+ pt_v6.add(k, v)
51
+ else
52
+ pt_v4.add(k, v)
53
+ end
48
54
  end
55
+ puts '[Paraxial] Cloud IPs set'
56
+ # puts '[Paraxial] pt_v4.num_nodes'
57
+ # puts pt_v4.num_nodes
58
+ # puts 'pt_v6.num_nodes'
59
+ # puts pt_v6.num_nodes
60
+ PARAXIAL_IPV4 = pt_v4
61
+ PARAXIAL_IPV6 = pt_v6
62
+ # ab = Paraxial.get_abr
63
+ # puts "[Paraxial] Allows/Bans set: #{ab}"
64
+ rescue Errno::ECONNREFUSED => _e
65
+ puts '[Paraxial] Init HTTP request failed, check configuration'
66
+ PARAXIAL_IPV4 = Patricia.new unless defined?(PARAXIAL_IPV4)
67
+ PARAXIAL_IPV6 = Patricia.new(:AF_INET6) unless defined?(PARAXIAL_IPV4)
68
+ rescue StandardError => e
69
+ puts e
70
+ puts '[Paraxial] Init error, check configuration'
71
+ PARAXIAL_IPV4 = Patricia.new unless defined?(PARAXIAL_IPV4)
72
+ PARAXIAL_IPV6 = Patricia.new(:AF_INET6) unless defined?(PARAXIAL_IPV4)
49
73
  end
50
- # puts '[Paraxial] pt_v4.num_nodes'
51
- # puts pt_v4.num_nodes
52
- # puts 'pt_v6.num_nodes'
53
- # puts pt_v6.num_nodes
54
- PARAXIAL_IPV4 = pt_v4
55
- PARAXIAL_IPV6 = pt_v6
56
- rescue Errno::ECONNREFUSED => _e
57
- puts '[Paraxial] Init HTTP request failed, check configuration'
58
- PARAXIAL_IPV4 = Patricia.new
59
- PARAXIAL_IPV6 = Patricia.new(:AF_INET6)
60
- rescue StandardError => _e
61
- puts '[Paraxial] Init error, check configuration'
62
- PARAXIAL_IPV4 = Patricia.new
63
- PARAXIAL_IPV6 = Patricia.new(:AF_INET6)
64
74
  end
65
75
  end
66
76
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Paraxial
4
- VERSION = '0.3.0'
4
+ VERSION = '0.5.0'
5
5
  end
data/lib/paraxial.rb CHANGED
@@ -9,11 +9,14 @@ require_relative 'rubocop/cop/paraxial/send'
9
9
  require_relative 'rubocop/cop/paraxial/constantize'
10
10
  require_relative 'rubocop/cop/paraxial/html_safe'
11
11
  require_relative 'rubocop/cop/paraxial/sql'
12
- require_relative "paraxial/version"
12
+ require_relative 'paraxial/version'
13
13
  require_relative 'paraxial/cli'
14
14
 
15
-
16
15
  module Paraxial
16
+ class << self
17
+ attr_accessor :configuration
18
+ end
19
+
17
20
  class Error < StandardError; end
18
21
  # Your code goes here...
19
22
 
@@ -27,7 +30,7 @@ module Paraxial
27
30
 
28
31
  if request_path.end_with?('.php')
29
32
  # Return a 404 response if the request path ends with '.php'
30
- [404, { 'Content-Type' => 'text/plain' }, ["Not Found from Paraxial.io"]]
33
+ [404, { 'Content-Type' => 'text/plain' }, ['Not Found from Paraxial.io']]
31
34
  else
32
35
  # Pass the request to the next middleware or the application
33
36
  @app.call(env)
@@ -37,15 +40,27 @@ module Paraxial
37
40
 
38
41
  def self.get_timestamp
39
42
  utc_time = Time.now.utc
40
- utc_time.strftime("%Y-%m-%d %H:%M:%S.%6N") + "Z"
43
+ utc_time.strftime('%Y-%m-%d %H:%M:%S.%6N') + 'Z'
41
44
  end
42
45
 
43
46
  def self.cloud_ip?(ip)
44
- !!(PARAXIAL_IPV4.search_best(ip) or PARAXIAL_IPV6.search_best(ip))
47
+ if ip.include?('.')
48
+ !!PARAXIAL_IPV4.search_best(ip)
49
+ else
50
+ !!PARAXIAL_IPV6.search_best(ip)
51
+ end
52
+ end
53
+
54
+ def self.ban_ip(ip)
55
+ Paraxial::Checker.ban_ip(ip)
56
+ end
57
+
58
+ def self.allow_ip?(ip)
59
+ Paraxial::Checker.allow_ip?(ip)
45
60
  end
46
61
 
47
62
  def self.trim_dep(input)
48
- if input == nil
63
+ if input.nil?
49
64
  nil
50
65
  else
51
66
  cleaned_string = input.gsub(/\n/, '')
@@ -54,11 +69,40 @@ module Paraxial
54
69
  period_index = cleaned_string.index('.')
55
70
 
56
71
  # If there's a period, truncate the string up to that point
57
- if period_index
58
- cleaned_string = cleaned_string[0..period_index]
59
- end
72
+ cleaned_string = cleaned_string[0..period_index] if period_index
60
73
 
61
74
  cleaned_string
62
75
  end
63
76
  end
77
+
78
+ def self.configure
79
+ self.configuration ||= Configuration.new
80
+ yield(configuration) if block_given?
81
+ end
82
+
83
+ def self.check_exploit_guard
84
+ if configuration.nil?
85
+ puts "[Paraxial] Exploit Guard, no config exists, will not run"
86
+ return
87
+ end
88
+
89
+ case configuration.exploit_guard
90
+ when :monitor
91
+ puts "[Paraxial] Exploit Guard, running in monitor mode"
92
+ when :block
93
+ puts "[Paraxial] Exploit Guard, running in block mode"
94
+ when nil
95
+ puts "[Paraxial] Exploit Guard, not configured, will not run"
96
+ else
97
+ puts "[Paraxial] Exploit Guard, bad value"
98
+ end
99
+ end
100
+
101
+ class Configuration
102
+ attr_accessor :exploit_guard
103
+
104
+ def initialize
105
+ @exploit_guard = nil
106
+ end
107
+ end
64
108
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: paraxial
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Lubas
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-08-13 00:00:00.000000000 Z
11
+ date: 2024-08-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -80,9 +80,11 @@ files:
80
80
  - Rakefile
81
81
  - exe/paraxial
82
82
  - lib/paraxial.rb
83
+ - lib/paraxial/checker.rb
83
84
  - lib/paraxial/cli.rb
84
85
  - lib/paraxial/engine.rb
85
86
  - lib/paraxial/helpers.rb
87
+ - lib/paraxial/initializers/marshal_patch.rb
86
88
  - lib/paraxial/initializers/startup.rb
87
89
  - lib/paraxial/version.rb
88
90
  - lib/rubocop/cop/paraxial/constantize.rb