paraxial 0.8.0 → 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/paraxial/checker.rb +87 -15
- data/lib/paraxial/free_tier.rb +34 -0
- data/lib/paraxial/helpers.rb +20 -0
- data/lib/paraxial/initializers/marshal_patch.rb +1 -1
- data/lib/paraxial/initializers/startup.rb +2 -0
- data/lib/paraxial/version.rb +1 -1
- data/lib/paraxial.rb +84 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: be68560342effbc4099f126d85e25acf0400e0b27dc636f4173192a9a634d3da
|
4
|
+
data.tar.gz: 489450bae67581a1738754843a8ce3b6128ad098aa88228d1823bf41ce0ebada
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: edd5307bcc69fcff116b2873d5571b035808f76bf508447403da5a1064d7faea2d230ceb12f0e6318469815b18d23254a6b4adf914b6c0bca009ac807f35d8fd
|
7
|
+
data.tar.gz: f473320f53f772245dad48f68edeb47271ebe22e921b73b6cfe74506b6e5ccc8925ebf6621f2a4a46f0b6f9c2282c9580f498572f85dc6214fe21359d59dcc52
|
data/lib/paraxial/checker.rb
CHANGED
@@ -3,24 +3,61 @@ module Paraxial
|
|
3
3
|
module Checker
|
4
4
|
@allows = { 'v4' => Patricia.new, 'v6' => Patricia.new(:AF_INET6) }
|
5
5
|
@bans = { 'v4' => Patricia.new, 'v6' => Patricia.new(:AF_INET6) }
|
6
|
-
|
6
|
+
@buffer = Queue.new
|
7
|
+
@mutex = Mutex.new
|
8
|
+
@headers = { 'Content-Type': 'application/json' }
|
7
9
|
|
8
10
|
if Paraxial::Helpers.get_api_key
|
9
11
|
@thread = Thread.new do
|
10
12
|
loop do
|
11
13
|
get_abr
|
12
14
|
sleep(10)
|
15
|
+
flush_buffer
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.req_to_buff(req_hash)
|
21
|
+
@buffer << req_hash
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.flush_buffer
|
25
|
+
@mutex.synchronize do
|
26
|
+
requests = []
|
27
|
+
until @buffer.empty?
|
28
|
+
requests << @buffer.pop(true) rescue nil
|
29
|
+
end
|
30
|
+
|
31
|
+
send_async_request(requests) unless requests.empty?
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.send_async_request(requests)
|
36
|
+
body =
|
37
|
+
{
|
38
|
+
http_requests: requests,
|
39
|
+
private_api_key: Paraxial::Helpers.get_api_key
|
40
|
+
}
|
41
|
+
|
42
|
+
# Do not send HTTP events when free tier is set to true
|
43
|
+
ft = Paraxial::FreeTier.is_free_tier
|
44
|
+
if ft
|
45
|
+
puts "[Paraxial] HTTP ingest not supported on free tier"
|
46
|
+
else
|
47
|
+
Thread.new do
|
48
|
+
uri = URI.parse(Paraxial::Helpers.get_ingest_url)
|
49
|
+
Net::HTTP.post(uri, body.to_json, @headers)
|
13
50
|
end
|
14
51
|
end
|
15
52
|
end
|
16
53
|
|
54
|
+
|
17
55
|
def self.get_abr
|
18
|
-
uri = URI.parse(Paraxial::Helpers.
|
19
|
-
headers = { 'Content-Type': 'application/json' }
|
56
|
+
uri = URI.parse(Paraxial::Helpers.get_abr_url)
|
20
57
|
|
21
58
|
body = { api_key: Paraxial::Helpers.get_api_key }
|
22
59
|
begin
|
23
|
-
r = Net::HTTP.post(uri, body.to_json, headers)
|
60
|
+
r = Net::HTTP.post(uri, body.to_json, @headers)
|
24
61
|
if r.code == '200'
|
25
62
|
put_abr(JSON.parse(r.body))
|
26
63
|
else
|
@@ -92,7 +129,53 @@ module Paraxial
|
|
92
129
|
end
|
93
130
|
end
|
94
131
|
|
132
|
+
def self.ban_ip_msg(ip, length, msg)
|
133
|
+
local_ban(ip)
|
134
|
+
|
135
|
+
uri = URI.parse(Paraxial::Helpers.get_ruby_ban_url)
|
136
|
+
body =
|
137
|
+
{
|
138
|
+
bad_ip: ip,
|
139
|
+
ban_length: length,
|
140
|
+
msg: msg,
|
141
|
+
api_key: Paraxial::Helpers.get_api_key
|
142
|
+
}
|
143
|
+
r = Net::HTTP.post(uri, body.to_json, @headers)
|
144
|
+
if r.code == '200'
|
145
|
+
:ok
|
146
|
+
else
|
147
|
+
:error
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def self.honeypot_ban(ip, length)
|
152
|
+
local_ban(ip)
|
153
|
+
|
154
|
+
uri = URI.parse(Paraxial::Helpers.get_honeypot_url)
|
155
|
+
|
156
|
+
body = { api_key: Paraxial::Helpers.get_api_key, bad_ip: ip, ban_length: length }
|
157
|
+
r = Net::HTTP.post(uri, body.to_json, @headers)
|
158
|
+
if r.code == '200'
|
159
|
+
:ok
|
160
|
+
else
|
161
|
+
:error
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
95
165
|
def self.ban_ip(ip)
|
166
|
+
local_ban(ip)
|
167
|
+
uri = URI.parse(Paraxial::Helpers.get_ban_url)
|
168
|
+
|
169
|
+
body = { api_key: Paraxial::Helpers.get_api_key, ip_address: ip }
|
170
|
+
r = Net::HTTP.post(uri, body.to_json, @headers)
|
171
|
+
if r.code == '200'
|
172
|
+
:ok
|
173
|
+
else
|
174
|
+
:error
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def self.local_ban(ip)
|
96
179
|
if ip.include?('.')
|
97
180
|
# IPv4
|
98
181
|
current_t = @bans['v4']
|
@@ -104,17 +187,6 @@ module Paraxial
|
|
104
187
|
current_t.add(ip)
|
105
188
|
@bans['v6'] = current_t
|
106
189
|
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
190
|
end
|
119
191
|
|
120
192
|
def self.allow_ip?(ip)
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Paraxial
|
5
|
+
module FreeTier
|
6
|
+
class << self
|
7
|
+
attr_reader :is_free_tier
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@is_free_tier = true
|
11
|
+
check_free_tier_status
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
# Kicks off an async HTTP request
|
17
|
+
def check_free_tier_status
|
18
|
+
Thread.new do
|
19
|
+
uri = URI.parse(Paraxial::Helpers.get_free_tier_url())
|
20
|
+
headers = { 'Content-Type': 'application/json' }
|
21
|
+
body = { api_key: Paraxial::Helpers.get_api_key }
|
22
|
+
r = Net::HTTP.post(uri, body.to_json, headers)
|
23
|
+
result = JSON.parse(r.body)
|
24
|
+
# Assume the API response contains a field `free_tier` (true/false)
|
25
|
+
@is_free_tier = result['free_tier']
|
26
|
+
rescue StandardError => e
|
27
|
+
# Handle any errors (network issues, parsing errors, etc.)
|
28
|
+
puts "Error fetching free tier status: #{e.message}"
|
29
|
+
@is_free_tier = true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/paraxial/helpers.rb
CHANGED
@@ -12,6 +12,26 @@ module Paraxial
|
|
12
12
|
get_paraxial_url + '/api/exploit'
|
13
13
|
end
|
14
14
|
|
15
|
+
def self.get_free_tier_url
|
16
|
+
get_paraxial_url + '/api/free_tier'
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.get_abr_url
|
20
|
+
get_paraxial_url + '/api/abr'
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.get_ingest_url
|
24
|
+
get_paraxial_url + '/api/ingest'
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.get_honeypot_url
|
28
|
+
get_paraxial_url + '/api/honeypot_ban_x'
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.get_ruby_ban_url
|
32
|
+
get_paraxial_url + '/api/ruby_ban_x'
|
33
|
+
end
|
34
|
+
|
15
35
|
def self.get_api_key
|
16
36
|
@paraxial_api_key ||= ENV['PARAXIAL_API_KEY']
|
17
37
|
end
|
@@ -6,7 +6,7 @@ unless Rails.env.test? || File.basename($0) == 'rake' || defined?(Rails::Generat
|
|
6
6
|
def load(source, proc = nil)
|
7
7
|
exg = Paraxial.configuration&.exploit_guard || nil
|
8
8
|
if [:monitor, :block].include?(exg)
|
9
|
-
if source.is_a?(String) && source.match?(/ActionView|Net::BufferedIO|ERB
|
9
|
+
if source.is_a?(String) && source.match?(/ActionView|Net::BufferedIO|ERB/)
|
10
10
|
puts "[Paraxial] Exploit Guard triggered, malicious input to Marshal.load"
|
11
11
|
puts source
|
12
12
|
|
@@ -3,6 +3,7 @@ require 'paraxial'
|
|
3
3
|
require 'rpatricia'
|
4
4
|
require_relative '../helpers'
|
5
5
|
require_relative '../checker'
|
6
|
+
require_relative '../free_tier'
|
6
7
|
|
7
8
|
Bundler.setup
|
8
9
|
|
@@ -20,6 +21,7 @@ unless Rails.env.test? || File.basename($0) == 'rake' || defined?(Rails::Generat
|
|
20
21
|
puts '[Paraxial] API key detected, agent starting'
|
21
22
|
|
22
23
|
Paraxial.check_exploit_guard
|
24
|
+
Paraxial::FreeTier.initialize
|
23
25
|
|
24
26
|
deps_and_licenses = []
|
25
27
|
Bundler.load.specs.each do |spec|
|
data/lib/paraxial/version.rb
CHANGED
data/lib/paraxial.rb
CHANGED
@@ -44,7 +44,65 @@ module Paraxial
|
|
44
44
|
utc_time.strftime('%Y-%m-%d %H:%M:%S.%6N') + 'Z'
|
45
45
|
end
|
46
46
|
|
47
|
+
def self.record(request, status)
|
48
|
+
return if Paraxial::Helpers.get_api_key.nil?
|
49
|
+
|
50
|
+
req_hash =
|
51
|
+
{
|
52
|
+
ip_address: request.remote_ip,
|
53
|
+
http_method: request.request_method,
|
54
|
+
path: request.path,
|
55
|
+
user_agent: request.user_agent,
|
56
|
+
allowed: !request.env['paraxial.deny'],
|
57
|
+
status_code: status,
|
58
|
+
inserted_at: get_timestamp,
|
59
|
+
cloud_ip: request.env['paraxial.cloud_ip'],
|
60
|
+
host: request.host
|
61
|
+
}
|
62
|
+
Paraxial::Checker.req_to_buff(req_hash)
|
63
|
+
end
|
64
|
+
|
65
|
+
# routes = ['/login', '/users/:id']
|
66
|
+
def self.block_cloud_ip(request, routes)
|
67
|
+
return if Paraxial::Helpers.get_api_key.nil?
|
68
|
+
|
69
|
+
ip = request.remote_ip
|
70
|
+
cloud_provider = get_cloud_provider(ip)
|
71
|
+
|
72
|
+
if cloud_provider
|
73
|
+
request.env['paraxial.cloud_ip'] = cloud_provider
|
74
|
+
else
|
75
|
+
request.env['paraxial.cloud_ip'] = nil
|
76
|
+
end
|
77
|
+
|
78
|
+
route_patterns = routes.map do |route|
|
79
|
+
Regexp.new("^" + route.gsub(/:\w+/, '\d+') + "$")
|
80
|
+
end
|
81
|
+
|
82
|
+
match = route_patterns.any? { |pattern| pattern.match?(request.path) }
|
83
|
+
|
84
|
+
if match and cloud_provider
|
85
|
+
request.env['paraxial.deny'] = true
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.req_allowed?(request)
|
90
|
+
return if Paraxial::Helpers.get_api_key.nil?
|
91
|
+
|
92
|
+
if request.env['paraxial.deny'] == true
|
93
|
+
false
|
94
|
+
elsif Paraxial::Checker.allow_ip?(request.remote_ip) == true
|
95
|
+
request.env['paraxial.deny'] = false
|
96
|
+
true
|
97
|
+
else
|
98
|
+
request.env['paraxial.deny'] = true
|
99
|
+
false
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
47
103
|
def self.cloud_ip?(ip)
|
104
|
+
return if Paraxial::Helpers.get_api_key.nil?
|
105
|
+
|
48
106
|
if ip.include?('.')
|
49
107
|
!!PARAXIAL_IPV4.search_best(ip)
|
50
108
|
else
|
@@ -52,11 +110,37 @@ module Paraxial
|
|
52
110
|
end
|
53
111
|
end
|
54
112
|
|
113
|
+
def self.get_cloud_provider(ip)
|
114
|
+
return if Paraxial::Helpers.get_api_key.nil?
|
115
|
+
|
116
|
+
if ip.include?('.')
|
117
|
+
PARAXIAL_IPV4.search_best(ip)&.data
|
118
|
+
else
|
119
|
+
PARAXIAL_IPV6.search_best(ip)&.data
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
55
123
|
def self.ban_ip(ip)
|
124
|
+
return if Paraxial::Helpers.get_api_key.nil?
|
125
|
+
|
56
126
|
Paraxial::Checker.ban_ip(ip)
|
57
127
|
end
|
58
128
|
|
129
|
+
def self.ban_ip_msg(ip, length, msg)
|
130
|
+
return if Paraxial::Helpers.get_api_key.nil?
|
131
|
+
|
132
|
+
Paraxial::Checker.ban_ip_msg(ip, length, msg)
|
133
|
+
end
|
134
|
+
|
135
|
+
def self.honeypot_ban(ip, length = :week)
|
136
|
+
return if Paraxial::Helpers.get_api_key.nil?
|
137
|
+
|
138
|
+
Paraxial::Checker.honeypot_ban(ip, length)
|
139
|
+
end
|
140
|
+
|
59
141
|
def self.allow_ip?(ip)
|
142
|
+
return if Paraxial::Helpers.get_api_key.nil?
|
143
|
+
|
60
144
|
Paraxial::Checker.allow_ip?(ip)
|
61
145
|
end
|
62
146
|
|
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.
|
4
|
+
version: 0.9.1
|
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-09-
|
11
|
+
date: 2024-09-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|
@@ -97,6 +97,7 @@ files:
|
|
97
97
|
- lib/paraxial/checker.rb
|
98
98
|
- lib/paraxial/cli.rb
|
99
99
|
- lib/paraxial/engine.rb
|
100
|
+
- lib/paraxial/free_tier.rb
|
100
101
|
- lib/paraxial/helpers.rb
|
101
102
|
- lib/paraxial/initializers/marshal_patch.rb
|
102
103
|
- lib/paraxial/initializers/startup.rb
|