perimeter_x 1.3.0 → 2.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,37 +1,89 @@
1
1
  require 'perimeterx/utils/px_logger'
2
2
  require 'perimeterx/utils/px_constants'
3
+ require 'perimeterx/internal/validators/hash_schema_validator'
3
4
 
4
5
  module PxModule
5
6
  class Configuration
7
+ @@basic_config = nil
8
+ @@mutex = Mutex.new
6
9
 
7
10
  attr_accessor :configuration
8
- attr_accessor :PX_DEFAULT
9
11
 
10
12
  PX_DEFAULT = {
11
- :app_id => nil,
12
- :cookie_key => nil,
13
- :auth_token => nil,
14
- :module_enabled => true,
15
- :captcha_enabled => true,
16
- :challenge_enabled => true,
17
- :encryption_enabled => true,
18
- :blocking_score => 70,
19
- :sensitive_headers => ["http-cookie", "http-cookies"],
20
- :api_connect_timeout => 1,
21
- :api_timeout => 1,
22
- :max_buffer_len => 10,
23
- :send_page_activities => true,
24
- :send_block_activities => true,
25
- :sdk_name => PxModule::SDK_NAME,
26
- :debug => false,
27
- :module_mode => PxModule::ACTIVE_MODE,
28
- :local_proxy => false,
29
- :sensitive_routes => []
13
+ :app_id => nil,
14
+ :cookie_key => nil,
15
+ :auth_token => nil,
16
+ :module_enabled => true,
17
+ :challenge_enabled => true,
18
+ :encryption_enabled => true,
19
+ :blocking_score => 100,
20
+ :sensitive_headers => ["http-cookie", "http-cookies"],
21
+ :api_timeout_connection => 1,
22
+ :api_timeout => 1,
23
+ :send_page_activities => true,
24
+ :send_block_activities => true,
25
+ :sdk_name => PxModule::SDK_NAME,
26
+ :debug => false,
27
+ :module_mode => PxModule::MONITOR_MODE,
28
+ :sensitive_routes => [],
29
+ :whitelist_routes => [],
30
+ :ip_headers => [],
31
+ :ip_header_function => nil,
32
+ :bypass_monitor_header => nil,
33
+ :risk_cookie_max_iterations => 5000,
34
+ :first_party_enabled => true
30
35
  }
31
36
 
37
+ CONFIG_SCHEMA = {
38
+ :app_id => {types: [String], required: true},
39
+ :cookie_key => {types: [String], required: true},
40
+ :auth_token => {types: [String], required: true},
41
+ :module_enabled => {types: [FalseClass, TrueClass], required: false},
42
+ :challenge_enabled => {types: [FalseClass, TrueClass], required: false},
43
+ :encryption_enabled => {types: [FalseClass, TrueClass], required: false},
44
+ :blocking_score => {types: [Integer], required: false},
45
+ :sensitive_headers => {types: [Array], allowed_element_types: [String], required: false},
46
+ :api_timeout_connection => {types: [Integer, Float], required: false},
47
+ :api_timeout => {types: [Integer, Float], required: false},
48
+ :send_page_activities => {types: [FalseClass, TrueClass], required: false},
49
+ :send_block_activities => {types: [FalseClass, TrueClass], required: false},
50
+ :sdk_name => {types: [String], required: false},
51
+ :debug => {types: [FalseClass, TrueClass], required: false},
52
+ :module_mode => {types: [Integer], required: false},
53
+ :sensitive_routes => {types: [Array], allowed_element_types: [String], required: false},
54
+ :whitelist_routes => {types: [Array], allowed_element_types: [String, Regexp], required: false},
55
+ :ip_headers => {types: [Array], allowed_element_types: [String], required: false},
56
+ :ip_header_function => {types: [Proc], required: false},
57
+ :bypass_monitor_header => {types: [String], required: false},
58
+ :risk_cookie_max_iterations => {types: [Integer], required: false},
59
+ :custom_verification_handler => {types: [Proc], required: false},
60
+ :additional_activity_handler => {types: [Proc], required: false},
61
+ :custom_logo => {types: [String], required: false},
62
+ :css_ref => {types: [String], required: false},
63
+ :js_ref => {types: [String], required: false},
64
+ :custom_uri => {types: [Proc], required: false},
65
+ :first_party_enabled => {types: [FalseClass, TrueClass], required: false}
66
+
67
+ }
68
+
69
+ def self.set_basic_config(basic_config)
70
+ if @@basic_config.nil?
71
+ @@mutex.synchronize {
72
+ @@basic_config = PX_DEFAULT.merge(basic_config)
73
+ }
74
+ end
75
+ end
76
+
32
77
  def initialize(params)
33
- PX_DEFAULT[:perimeterx_server_host] = "https://sapi-#{params[:app_id].downcase}.perimeterx.net"
34
- @configuration = PX_DEFAULT.merge(params)
78
+ if ! @@basic_config.is_a?(Hash)
79
+ raise PxConfigurationException.new('PerimeterX: Please initialize PerimeterX first')
80
+ end
81
+
82
+ # merge request configuration into the basic configuration
83
+ @configuration = @@basic_config.merge(params)
84
+ validate_hash_schema(@configuration, CONFIG_SCHEMA)
85
+
86
+ @configuration[:backend_url] = "https://sapi-#{@configuration[:app_id].downcase}.perimeterx.net"
35
87
  @configuration[:logger] = PxLogger.new(@configuration[:debug])
36
88
  end
37
89
  end
@@ -17,8 +17,11 @@ module PxModule
17
17
  @px_config[:additional_activity_handler].call(activity_type, px_ctx, details)
18
18
  end
19
19
 
20
+ if !px_ctx.context[:px_cookie].empty?
21
+ details[:cookie_origin] = px_ctx.context[:cookie_origin]
22
+ end
23
+
20
24
  details[:module_version] = @px_config[:sdk_name]
21
- details[:cookie_origin] = px_ctx.context[:cookie_origin]
22
25
 
23
26
  px_data = {
24
27
  :type => activity_type,
@@ -49,41 +52,61 @@ module PxModule
49
52
 
50
53
  def send_block_activity(px_ctx)
51
54
  @logger.debug("PerimeterxActivitiesClients[send_block_activity]")
52
- if (!@px_config[:send_page_acitivites])
55
+ if (!@px_config[:send_block_activities])
53
56
  @logger.debug("PerimeterxActivitiesClients[send_block_activity]: sending activites is disabled")
54
57
  return
55
58
  end
56
59
 
57
60
  details = {
58
- :block_uuid => px_ctx.context[:uuid],
61
+ :http_version => px_ctx.context[:http_version],
62
+ :http_method => px_ctx.context[:http_method],
63
+ :client_uuid => px_ctx.context[:uuid],
59
64
  :block_score => px_ctx.context[:score],
60
- :block_reason => px_ctx.context[:blocking_reason]
65
+ :block_reason => px_ctx.context[:blocking_reason],
66
+ :simulated_block => @px_config[:module_mode] == PxModule::MONITOR_MODE
61
67
  }
62
68
 
69
+ if (px_ctx.context.key?(:risk_rtt))
70
+ details[:risk_rtt] = px_ctx.context[:risk_rtt]
71
+ end
72
+
73
+ if (px_ctx.context.key?(:px_orig_cookie))
74
+ details[:px_orig_cookie] = px_ctx.context[:px_orig_cookie]
75
+ end
76
+
63
77
  send_to_perimeterx(PxModule::BLOCK_ACTIVITY, px_ctx, details)
64
78
 
65
79
  end
66
80
 
67
81
  def send_page_requested_activity(px_ctx)
68
82
  @logger.debug("PerimeterxActivitiesClients[send_page_requested_activity]")
69
- if (!@px_config[:send_page_acitivites])
83
+ if (!@px_config[:send_page_activities])
70
84
  return
71
85
  end
72
86
 
73
87
  details = {
74
88
  :http_version => px_ctx.context[:http_version],
75
89
  :http_method => px_ctx.context[:http_method],
76
- :client_uuid => px_ctx.context[:uuid]
90
+ :client_uuid => px_ctx.context[:uuid],
91
+ :pass_reason => px_ctx.context[:pass_reason]
77
92
  }
78
93
 
79
94
  if (px_ctx.context.key?(:decoded_cookie))
80
95
  details[:px_cookie] = px_ctx.context[:decoded_cookie]
81
96
  end
82
97
 
98
+ if (px_ctx.context.key?(:px_orig_cookie))
99
+ details[:px_orig_cookie] = px_ctx.context[:px_orig_cookie]
100
+ end
101
+
83
102
  if (px_ctx.context.key?(:cookie_hmac))
84
103
  details[:px_cookie_hmac] = px_ctx.context[:cookie_hmac]
85
104
  end
86
105
 
106
+ if (px_ctx.context.key?(:risk_rtt))
107
+ details[:risk_rtt] = px_ctx.context[:risk_rtt]
108
+ end
109
+
87
110
  send_to_perimeterx(PxModule::PAGE_REQUESTED_ACTIVITY, px_ctx, details)
88
111
  end
89
112
  end
@@ -0,0 +1,6 @@
1
+ class PxConfigurationException < StandardError
2
+ def initialize(msg)
3
+ super(msg)
4
+ end
5
+ end
6
+
@@ -0,0 +1,124 @@
1
+ require 'perimeterx/internal/perimeter_x_context'
2
+ module PxModule
3
+ class FirstPartyManager
4
+ def initialize(px_config, px_http_client, logger)
5
+ @px_config = px_config
6
+ @app_id = px_config[:app_id]
7
+ @px_http_client = px_http_client
8
+ @logger = logger
9
+ @from = [
10
+ "/#{@app_id[2..-1]}/init.js",
11
+ "/#{@app_id[2..-1]}/captcha",
12
+ "/#{@app_id[2..-1]}/xhr"
13
+ ]
14
+ end
15
+
16
+ def send_first_party_request(req)
17
+ uri = URI.parse(req.original_url)
18
+ url_path = uri.path
19
+
20
+ headers = extract_headers(req)
21
+ headers["x-px-first-party"] = "1"
22
+ headers["x-px-enforcer-true-ip"] = PerimeterXContext.extract_ip(req, @px_config)
23
+
24
+ if url_path.start_with?(@from[0])
25
+ return get_client(req, uri, headers)
26
+ elsif url_path.start_with?(@from[1])
27
+ return get_captcha(req, uri, headers)
28
+ elsif url_path.start_with?(@from[2])
29
+ return send_xhr(req, uri, headers)
30
+ else
31
+ return nil
32
+ end
33
+ end
34
+
35
+ def get_client(req, uri, headers)
36
+ @logger.debug("FirstPartyManager[get_client]")
37
+
38
+ # define host
39
+ headers["host"] = PxModule::CLIENT_HOST
40
+
41
+ # define request url
42
+ url = "#{uri.scheme}://#{PxModule::CLIENT_HOST}/#{@app_id}/main.min.js"
43
+
44
+ # send request
45
+ return @px_http_client.get(url, headers)
46
+ end
47
+
48
+ def get_captcha(req, uri, headers)
49
+ @logger.debug("FirstPartyManager[get_captcha]")
50
+
51
+ # define host
52
+ headers["host"] = PxModule::CAPTCHA_HOST
53
+
54
+ # define request url
55
+ path_and_query = uri.request_uri
56
+ uri_suffix = path_and_query.sub "/#{@app_id[2..-1]}/captcha", ""
57
+ url = "#{uri.scheme}://#{PxModule::CAPTCHA_HOST}#{uri_suffix}"
58
+
59
+ # send request
60
+ return @px_http_client.get(url, headers)
61
+ end
62
+
63
+ def send_xhr(req, uri, headers)
64
+ @logger.debug("FirstPartyManager[send_xhr]")
65
+
66
+ # handle vid cookies
67
+ if !req.cookies.nil?
68
+ if req.cookies.key?("_pxvid")
69
+ vid = PerimeterXContext.force_utf8(req.cookies["_pxvid"])
70
+ if headers.key?('cookie')
71
+ headers['cookie'] += "; pxvid=#{vid}";
72
+ else
73
+ headers['cookie'] = "pxvid=#{vid}";
74
+ end
75
+ end
76
+ end
77
+
78
+ # define host
79
+ headers["host"] = "collector-#{@app_id.downcase}.perimeterx.net"
80
+
81
+ # define request url
82
+ path_and_query = uri.request_uri
83
+ path_suffix = path_and_query.sub "/#{@app_id[2..-1]}/xhr", ""
84
+ url = "#{uri.scheme}://collector-#{@app_id.downcase}.perimeterx.net#{path_suffix}"
85
+
86
+ # send request
87
+ return @px_http_client.post_xhr(url, req.body.string, headers)
88
+ end
89
+
90
+ def extract_headers(req)
91
+ headers = Hash.new
92
+ req.headers.each do |k, v|
93
+ if (k.start_with? 'HTTP_') && (!@px_config[:sensitive_headers].include? k)
94
+ header = k.to_s.gsub('HTTP_', '')
95
+ header = header.gsub('_', '-').downcase
96
+ headers[header] = PerimeterXContext.force_utf8(v)
97
+ end
98
+ end
99
+ return headers
100
+ end
101
+
102
+ # -1 - not first party request
103
+ # 0 - /init.js
104
+ # 1 - /captcha
105
+ # 2 - /xhr
106
+ def get_first_party_request_type(req)
107
+ url_path = URI.parse(req.original_url).path
108
+ @from.each_with_index do |val,index|
109
+ if url_path.start_with?(val)
110
+ return index
111
+ end
112
+ end
113
+ return -1
114
+ end
115
+
116
+ def is_first_party_request(req)
117
+ return get_first_party_request_type(req) != -1
118
+ end
119
+
120
+ def get_response_content_type(req)
121
+ return get_first_party_request_type(req) == 2 ? :json : :js
122
+ end
123
+ end
124
+ end
@@ -108,6 +108,9 @@ module PxModule
108
108
  px_cookie = px_cookie.gsub(' ', '+')
109
109
  salt, iterations, cipher_text = px_cookie.split(':')
110
110
  iterations = iterations.to_i
111
+ if (iterations > @px_config[:risk_cookie_max_iterations] || iterations < 500)
112
+ return
113
+ end
111
114
  salt = Base64.decode64(salt)
112
115
  cipher_text = Base64.decode64(cipher_text)
113
116
  digest = OpenSSL::Digest::SHA256.new
@@ -1,10 +1,33 @@
1
1
  require 'perimeterx/utils/px_logger'
2
+ require 'perimeterx/utils/px_constants'
2
3
 
3
4
  module PxModule
4
5
  class PerimeterXContext
5
6
 
6
7
  attr_accessor :context
7
8
  attr_accessor :px_config
9
+
10
+ # class methods
11
+
12
+ def self.extract_ip(req, px_config)
13
+ # Get IP from header/custom function
14
+ if px_config[:ip_headers].length() > 0
15
+ px_config[:ip_headers].each do |ip_header|
16
+ if req.headers[ip_header]
17
+ return PerimeterXContext.force_utf8(req.headers[ip_header])
18
+ end
19
+ end
20
+ elsif px_config[:ip_header_function] != nil
21
+ return px_config[:ip_header_function].call(req)
22
+ end
23
+ return req.ip
24
+ end
25
+
26
+ def self.force_utf8(str)
27
+ return str.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '')
28
+ end
29
+
30
+ # instance methods
8
31
 
9
32
  def initialize(px_config, req)
10
33
  @logger = px_config[:logger]
@@ -14,27 +37,41 @@ module PxModule
14
37
  @context[:px_cookie] = Hash.new
15
38
  @context[:headers] = Hash.new
16
39
  @context[:cookie_origin] = 'cookie'
40
+ @context[:made_s2s_risk_api_call] = false
41
+ @context[:first_party_enabled] = px_config[:first_party_enabled]
42
+
17
43
  cookies = req.cookies
18
44
 
45
+ @context[:ip] = PerimeterXContext.extract_ip(req, px_config)
46
+
19
47
  # Get token from header
20
48
  if req.headers[PxModule::TOKEN_HEADER]
21
49
  @context[:cookie_origin] = 'header'
22
- token = req.headers[PxModule::TOKEN_HEADER]
23
- if token.include? ':'
24
- exploded_token = token.split(':', 2)
25
- cookie_sym = "v#{exploded_token[0]}".to_sym
26
- @context[:px_cookie][cookie_sym] = exploded_token[1]
50
+ token = PerimeterXContext.force_utf8(req.headers[PxModule::TOKEN_HEADER])
51
+ if token.match(PxModule::MOBILE_TOKEN_V3_REGEX)
52
+ @context[:px_cookie][:v3] = token[2..-1]
53
+ elsif token.match(PxModule::MOBILE_ERROR_REGEX)
54
+ @context[:mobile_error] = token
55
+ if req.headers[PxModule::ORIGINAL_TOKEN_HEADER]
56
+ token = PerimeterXContext.force_utf8(req.headers[PxModule::ORIGINAL_TOKEN_HEADER])
57
+ if token.match(PxModule::MOBILE_TOKEN_V3_REGEX)
58
+ @context[:px_cookie][:v3] = token[2..-1]
59
+ end
60
+ end
27
61
  end
28
62
  elsif !cookies.empty? # Get cookie from jar
29
63
  # Prepare hashed cookies
30
64
  cookies.each do |k, v|
31
65
  case k.to_s
32
66
  when '_px3'
33
- @context[:px_cookie][:v3] = v
67
+ @context[:px_cookie][:v3] = PerimeterXContext.force_utf8(v)
34
68
  when '_px'
35
- @context[:px_cookie][:v1] = v
36
- when '_pxCaptcha'
37
- @context[:px_captcha] = v
69
+ @context[:px_cookie][:v1] = PerimeterXContext.force_utf8(v)
70
+ when '_pxvid'
71
+ if v.is_a?(String) && v.match(PxModule::VID_REGEX)
72
+ @context[:vid_source] = "vid_cookie"
73
+ @context[:vid] = PerimeterXContext.force_utf8(v)
74
+ end
38
75
  end
39
76
  end #end case
40
77
  end #end empty cookies
@@ -43,10 +80,10 @@ module PxModule
43
80
  if (k.start_with? 'HTTP_')
44
81
  header = k.to_s.gsub('HTTP_', '')
45
82
  header = header.gsub('_', '-').downcase
46
- @context[:headers][header.to_sym] = v
83
+ @context[:headers][header.to_sym] = PerimeterXContext.force_utf8(v)
47
84
  end
48
85
  end #end headers foreach
49
-
86
+
50
87
  @context[:hostname]= req.server_name
51
88
  @context[:user_agent] = req.user_agent ? req.user_agent : ''
52
89
  @context[:uri] = px_config[:custom_uri] ? px_config[:custom_uri].call(req) : req.fullpath
@@ -54,14 +91,6 @@ module PxModule
54
91
  @context[:format] = req.format.symbol
55
92
  @context[:score] = 0
56
93
 
57
- if px_config.key?(:custom_user_ip)
58
- @context[:ip] = req.headers[px_config[:custom_user_ip]]
59
- elsif px_config.key?(:px_custom_user_ip_method)
60
- @context[:ip] = px_config[:px_custom_user_ip_method].call(req)
61
- else
62
- @context[:ip] = req.ip
63
- end
64
-
65
94
  if req.server_protocol
66
95
  httpVer = req.server_protocol.split('/')
67
96
  if httpVer.size > 0
@@ -87,6 +116,8 @@ module PxModule
87
116
  return 'block'
88
117
  when 'j'
89
118
  return 'challenge'
119
+ when 'r'
120
+ return 'rate_limit'
90
121
  else
91
122
  return captcha
92
123
  end