Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
7a2b17e
Merge remote-tracking branch 'remotes/origin/dev-first-party-mode' in…
alexbpx Nov 24, 2018
9b850e5
Merge remote-tracking branch 'remotes/origin/dev' into dev-refactor-c…
alexbpx Nov 24, 2018
1159bbb
Fxied errors
alexbpx Nov 24, 2018
8e2a9d4
Config refactoring
alexbpx Nov 24, 2018
36e5e6e
Config
alexbpx Nov 25, 2018
b65dc14
merging confligcts
alexbpx Nov 25, 2018
c811d14
Deleted Notes
alexbpx Nov 25, 2018
12e2efb
Feactored static files check
alexbpx Nov 25, 2018
47f4b0e
Refactored literals
alexbpx Nov 25, 2018
694ce36
Block activity
alexbpx Nov 25, 2018
e0f9092
Block action added to block acitivity
alexbpx Nov 25, 2018
a89913a
Removed socket_ip from context
alexbpx Nov 25, 2018
4ca2ee2
property
alexbpx Nov 25, 2018
c69dd9e
Telemetry
alexbpx Nov 25, 2018
0385fe5
Whitelist and sensitive route capabilities
alexbpx Nov 25, 2018
2417a14
Merge branch 'dev-refactor-config' into dev-custom-params-enforcer-tel
alexbpx Nov 25, 2018
07c26f9
Changes
alexbpx Nov 26, 2018
63a43b5
px_blocker tests
alexbpx Nov 26, 2018
b685b68
px_utils tests
alexbpx Nov 26, 2018
2e58395
px_validator tests
alexbpx Nov 26, 2018
63332dd
px_validator tests
alexbpx Nov 26, 2018
4c8f7ae
Fixed a few files after running automation
alexbpx Nov 27, 2018
1897868
Merge remote-tracking branch 'remotes/origin/dev' into dev-custom-par…
alexbpx Nov 27, 2018
8b04726
Fixed unittests
alexbpx Nov 27, 2018
35efd5a
Fxied j challenge response
alexbpx Nov 27, 2018
d89edaf
Added px_proxy tests
alexbpx Nov 27, 2018
c0abed6
Fxied building process of telemetry config
alexbpx Nov 27, 2018
2302fe5
phase 1
Nov 27, 2018
6a694ff
Changed telemtry uri
alexbpx Nov 27, 2018
009e13c
Empty tests
alexbpx Nov 27, 2018
7581c84
Empty tests
alexbpx Nov 27, 2018
8b9922a
Fxied captcha loop
alexbpx Nov 28, 2018
febc41d
Fixed captcha loops
alexbpx Nov 28, 2018
757745e
fixed unittests
alexbpx Nov 28, 2018
713351b
Merge remote-tracking branch 'remotes/origin/dev-custom-params-enforc…
alexbpx Nov 28, 2018
369402a
Mobile header handling
alexbpx Nov 29, 2018
7bc5abe
Fixed tests
alexbpx Nov 29, 2018
1dcbe87
fixed empty mobile header handling
alexbpx Nov 29, 2018
5e45a45
asdad
alexbpx Nov 29, 2018
ee8bf1d
Merge remote-tracking branch 'remotes/origin/dev' into dev-mobile_sdk
alexbpx Nov 30, 2018
353ec48
Merge remote-tracking branch 'remotes/origin/dev' into dev-mobile_sdk
alexbpx Nov 30, 2018
e9d5586
Added px_proxy tests
alexbpx Nov 30, 2018
c5680d3
Fixed code and tests
alexbpx Nov 30, 2018
924f154
Added unittests
alexbpx Nov 30, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions perimeterx/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ def _verify(self, environ, start_response):
uri = ctx.get('uri')
px_proxy = PXProxy(config)
if px_proxy.should_reverse_request(uri):
return px_proxy.handle_reverse_request(self.config, ctx, start_response, environ)
body = environ['wsgi.input'].read(int(environ.get('CONTENT_LENGTH'))) if environ.get('CONTENT_LENGTH') else ''
return px_proxy.handle_reverse_request(self.config, ctx, start_response, body)
if px_utils.is_static_file(ctx):
logger.debug('Filter static file request. uri: ' + uri)
return self.app(environ, start_response)
Expand All @@ -65,11 +66,11 @@ def _verify(self, environ, start_response):
return self.handle_verification(ctx, self.config, environ, start_response)
except:
logger.error("Cought exception, passing request")
self.pass_traffic(ctx)
self.pass_traffic({})
return self.app(environ, start_response)

def handle_verification(self, ctx, config, environ, start_response):
score = ctx.get('risk_score', -1)
score = ctx.get('score', -1)
result = None
headers = None
status = None
Expand Down
24 changes: 20 additions & 4 deletions perimeterx/px_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ def prepare_risk_body(ctx, config):
'ip': ctx.get('ip'),
'headers': format_headers(ctx.get('headers')),
'uri': ctx.get('uri'),
'url': ctx.get('full_url', '')
'url': ctx.get('full_url', ''),
'firstParty': 'true' if config.first_party else 'false'
},
'vid': ctx.get('vid', ''),
'uuid': ctx.get('uuid', ''),
Expand All @@ -82,10 +83,15 @@ def prepare_risk_body(ctx, config):
'http_version': ctx.get('http_version', ''),
'module_version': config.module_version,
'risk_mode': config.module_mode,
'px_cookie_hmac': ctx.get('cookie_hmac', ''),
'request_cookie_names': ctx.get('cookie_names', '')
'request_cookie_names': ctx.get('cookie_names', ''),
'cookie_origin': ctx.get('cookie_origin')
}
}
if ctx.get('cookie_hmac'):
body['additional']['px_cookie_hmac'] = ctx.get('cookie_hmac')


body = add_original_token_data(ctx, body)

if config.enrich_custom_parameters:
risk_custom_params = config.enrich_custom_parameters(custom_params)
Expand All @@ -96,7 +102,7 @@ def prepare_risk_body(ctx, config):

if ctx['s2s_call_reason'] == 'cookie_decryption_failed':
logger.debug('attaching orig_cookie to request')
body['additional']['px_cookie_orig'] = ctx.get('px_orig_cookie')
body['additional']['px_orig_cookie'] = ctx.get('px_orig_cookie')

if ctx['s2s_call_reason'] in ['cookie_expired', 'cookie_validation_failed']:
logger.debug('attaching px_cookie to request')
Expand All @@ -105,6 +111,16 @@ def prepare_risk_body(ctx, config):
logger.debug("PxAPI[send_risk_request] request body: " + str(body))
return body

def add_original_token_data(ctx, body):
if ctx.get('original_uuid'):
body['additional']['original_uuid'] = ctx.get('original_uuid')
if ctx.get('original_token_error'):
body['additional']['original_token_error'] = ctx.get('original_token_error')
if ctx.get('original_token'):
body['additional']['original_token'] = ctx.get('original_token')
if ctx.get('decoded_original_token'):
body['additional']['decoded_original_token'] = ctx.get('decoded_original_token')
return body

def format_headers(headers):
ret_val = []
Expand Down
4 changes: 4 additions & 0 deletions perimeterx/px_constants.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
PREFIX_PX_COOKIE_V1 = '_px'
PREFIX_PX_COOKIE_V3 = '_px3'
PREFIX_PX_TOKEN_V1 = '1'
PREFIX_PX_TOKEN_V3 = '3'
MOBILE_SDK_HEADER = "x-px-authorization"
MOBILE_SDK_ORIGINAL_HEADER= "x-px-original-token"

TRANS_5C = b"".join(chr(x ^ 0x5C) for x in range(256))
TRANS_36 = b"".join(chr(x ^ 0x36) for x in range(256))
Expand Down
58 changes: 42 additions & 16 deletions perimeterx/px_context.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import Cookie
from px_constants import *


def build_context(environ, config):
logger = config.logger
headers = {}
Expand All @@ -12,6 +11,8 @@ def build_context(environ, config):
http_protocol = 'http://'
px_cookies = {}
request_cookie_names = list()
cookie_origin = "cookie"
original_token = None

# Extracting: Headers, user agent, http method, http version
for key in environ.keys():
Expand All @@ -29,22 +30,34 @@ def build_context(environ, config):
http_version = protocol_split[1]
if key == 'CONTENT_TYPE' or key == 'CONTENT_LENGTH':
headers[key.replace('_', '-').lower()] = environ.get(key)
if key == 'HTTP_' + MOBILE_SDK_HEADER.replace('-','_').upper():
headers[MOBILE_SDK_HEADER] = environ.get(key, '')


cookies = Cookie.SimpleCookie(environ.get('HTTP_COOKIE', ''))
cookie_keys = cookies.keys()

for key in cookie_keys:
request_cookie_names.append(key)
if key == PREFIX_PX_COOKIE_V1 or key == PREFIX_PX_COOKIE_V3:
logger.debug('Found cookie prefix:' + key)
px_cookies[key] = cookies.get(key).value
mobile_header = headers.get(MOBILE_SDK_HEADER)
vid = None
if '_pxvid' in cookie_keys:
vid = cookies.get('_pxvid').value
original_token = None
if mobile_header is None:
cookies = Cookie.SimpleCookie(environ.get('HTTP_COOKIE', ''))
cookie_keys = cookies.keys()

for key in cookie_keys:
request_cookie_names.append(key)
if key == PREFIX_PX_COOKIE_V1 or key == PREFIX_PX_COOKIE_V3:
logger.debug('Found cookie prefix:' + key)
px_cookies[key] = cookies.get(key).value
vid = None
if '_pxvid' in cookie_keys:
vid = cookies.get('_pxvid').value
else:
vid = ''
else:
vid = ''
user_agent = environ.get('HTTP_USER_AGENT', '')
cookie_origin = "header"
original_token = headers.get(MOBILE_SDK_ORIGINAL_HEADER)
logger.debug('Mobile SDK token detected')
cookie_name, cookie = get_token_object(config, mobile_header)
px_cookies[cookie_name] = cookie

user_agent = headers.get('user-agent','')
uri = environ.get('PATH_INFO') or ''
full_url = http_protocol + (headers.get('host') or environ.get('SERVER_NAME') or '') + uri
hostname = headers.get('host')
Expand All @@ -62,15 +75,28 @@ def build_context(environ, config):
'cookie_names': request_cookie_names,
'risk_rtt': 0,
'ip': extract_ip(config, environ),
'vid': vid,
'vid': vid if vid else None,
'query_params': environ['QUERY_STRING'],
'sensitive_route': sensitive_route,
'whitelist_route': whitelist_route,
's2s_call_reason': 'none',
'cookie_origin': 'cookie'
'cookie_origin':cookie_origin,
'original_token': original_token,
'is_mobile': cookie_origin == "header"
}

return ctx

def get_token_object(config, token):
result = {}
logger = config.logger
sliced_token = token.split(":", 1)
if len(sliced_token) > 1:
key = sliced_token.pop(0)
if key == PREFIX_PX_TOKEN_V1 or key == PREFIX_PX_TOKEN_V3:
logger.debug('Found token prefix:' + key)
return key, sliced_token[0]
return PREFIX_PX_TOKEN_V3, token

def extract_ip(config, environ):
ip = environ.get('HTTP_X_FORWARDED_FOR') if environ.get('HTTP_X_FORWARDED_FOR') else environ.get('REMOTE_ADDR')
Expand Down
32 changes: 17 additions & 15 deletions perimeterx/px_cookie.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,32 @@
import struct


class PxCookie:
class PxCookie(object):

def __init__(self, config):
self._config = config
self._logger = config.logger


def build_px_cookie(self, ctx):
def build_px_cookie(self, px_cookies, user_agent):
self._logger.debug("PxCookie[build_px_cookie]")
px_cookies = ctx['px_cookies'].keys()
# Check that its not empty
if not px_cookies:
return None

px_cookies.sort(reverse=True)
prefix = px_cookies[0]
if prefix == PREFIX_PX_COOKIE_V1:
self._logger.debug("PxCookie[build_px_cookie] using cookie v1")
px_cookie_keys = px_cookies.keys()
px_cookie_keys.sort(reverse=True)
prefix = px_cookie_keys[0]
if prefix == PREFIX_PX_TOKEN_V1 or prefix == PREFIX_PX_COOKIE_V1:
self._logger.debug("PxCookie[build_px_cookie] using token v1")
from px_cookie_v1 import PxCookieV1
return PxCookieV1(ctx, self._config)

if prefix == PREFIX_PX_COOKIE_V3:
self._logger.debug("PxCookie[build_px_cookie] using cookie v3")
return PxCookieV1(self._config, px_cookies[prefix])
if prefix == PREFIX_PX_TOKEN_V3 or prefix == PREFIX_PX_COOKIE_V3:
self._logger.debug("PxCookie[build_px_cookie] using token v3")
from px_cookie_v3 import PxCookieV3
return PxCookieV3(ctx, self._config)
ua = ''
if prefix == PREFIX_PX_COOKIE_V3:
ua = user_agent
return PxCookieV3(self._config, px_cookies[prefix], ua)

def decode_cookie(self):
self._logger.debug("PxCookie[decode_cookie]")
Expand Down Expand Up @@ -149,7 +150,8 @@ def is_cookie_valid(self, str_to_hmac):
return False

def deserialize(self):
self._logger.debug("PxCookie[deserialize]")
logger = self._logger
logger.debug("PxCookie[deserialize]")
if self._config.encryption_enabled:
cookie = self.decrypt_cookie()
else:
Expand All @@ -158,7 +160,7 @@ def deserialize(self):
if not cookie:
return False

self._logger.debug("PxCookie[deserialize] decoded cookie: " + cookie)
logger.debug("Original token deserialized : " + cookie)
self.decoded_cookie = json.loads(cookie)
return self.is_cookie_format_valid()

Expand Down
9 changes: 3 additions & 6 deletions perimeterx/px_cookie_v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@

class PxCookieV1(PxCookie):

def __init__(self, ctx, config):
self._ctx = ctx
def __init__(self, config, raw_cookie):
self._config = config
self._logger = config.logger
self.raw_cookie = ctx['px_cookies'].get(PREFIX_PX_COOKIE_V1, '')
self.raw_cookie = raw_cookie

def get_score(self):
return self.decoded_cookie['s']['b']
Expand All @@ -23,10 +22,8 @@ def is_cookie_format_valid(self):
c = self.decoded_cookie
return 't' in c and 'v' in c and 'u' in c and "s" in c and 'a' in c['s'] and 'h' in c

def is_secured(self):
def is_secured(self, user_agent, ip):
c = self.decoded_cookie
user_agent = self._ctx.get('user_agent', '')
ip = self._ctx.get('ip', '')
base_hmac = str(self.get_timestamp()) + str(c['s']['a']) + str(self.get_score()) + self.get_uuid() + self.get_vid()
hmac_with_ip = base_hmac + ip + user_agent
hmac_without_ip = base_hmac + user_agent
Expand Down
18 changes: 9 additions & 9 deletions perimeterx/px_cookie_v3.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
from px_cookie import PxCookie
from px_constants import *


class PxCookieV3(PxCookie):

def __init__(self, ctx, config):
def __init__(self, config, cookie, user_agent):
self._config = config
self._logger = config.logger
self._ctx = ctx
self.raw_cookie = ''
spliced_cookie = self._ctx['px_cookies'].get(PREFIX_PX_COOKIE_V3, '').split(":", 1)
if len(spliced_cookie) > 1:
self._user_agent = user_agent
spliced_cookie = cookie.split(':')
if len(spliced_cookie) is 4:
self.hmac = spliced_cookie[0]
self.raw_cookie = spliced_cookie[1]
self.raw_cookie = ':'.join(spliced_cookie[1:])
else:
self.raw_cookie = cookie

def get_score(self):
return self.decoded_cookie['s']
Expand All @@ -24,11 +24,11 @@ def get_action(self):
return self.decoded_cookie['a']

def is_cookie_format_valid(self):
c = self.decoded_cookie;
c = self.decoded_cookie
return 't' in c and 'v' in c and 'u' in c and 's' in c and 'a' in c

def is_secured(self):
user_agent = self._ctx.get('user_agent', '')
user_agent = self._user_agent
str_hmac = self.raw_cookie + user_agent
return self.is_cookie_valid(str_hmac)

26 changes: 21 additions & 5 deletions perimeterx/px_cookie_validator.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import traceback
import re
import px_original_token_validator
from px_cookie import PxCookie


Expand All @@ -19,16 +21,30 @@ def verify(ctx, config):
ctx['s2s_call_reason'] = 'no_cookie'
return False

if not config.cookie_key:
logger.debug('No cookie key found, pause cookie evaluation')
ctx['s2s_call_reason'] = 'no_cookie_key'
return False

px_cookie_builder = PxCookie(config)
px_cookie = px_cookie_builder.build_px_cookie(ctx)
px_cookie = px_cookie_builder.build_px_cookie(px_cookies=ctx.get('px_cookies'),
user_agent=ctx.get('user_agent'))
#Mobile SDK traffic
if px_cookie and ctx['is_mobile']:
pattern = re.compile("^\d+$")
if re.match(pattern, px_cookie.raw_cookie):
ctx['s2s_call_reason'] = "mobile_error_" + px_cookie.raw_cookie
if ctx['original_token'] is not None:
px_original_token_validator.verify(ctx, config)
return False

if not px_cookie.deserialize():
logger.error('Cookie decryption failed')
ctx['px_orig_cookie'] = px_cookie.raw_cookie
ctx['s2s_call_reason'] = 'cookie_decryption_failed'
return False

ctx['risk_score'] = px_cookie.get_score()
ctx['score'] = px_cookie.get_score()
ctx['uuid'] = px_cookie.get_uuid()
ctx['vid'] = px_cookie.get_vid()
ctx['decoded_cookie'] = px_cookie.decoded_cookie
Expand All @@ -37,8 +53,8 @@ def verify(ctx, config):

if px_cookie.is_high_score():
ctx['block_reason'] = 'cookie_high_score'
logger.debug('Cookie with high score: ' + str(ctx['risk_score']))
return False
logger.debug('Cookie with high score: ' + str(ctx['score']))
return True

if px_cookie.is_cookie_expired():
ctx['s2s_call_reason'] = 'cookie_expired'
Expand All @@ -55,7 +71,7 @@ def verify(ctx, config):
ctx['s2s_call_reason'] = 'sensitive_route'
return False

logger.debug('Cookie validation passed with good score: ' + str(ctx['risk_score']))
logger.debug('Cookie validation passed with good score: ' + str(ctx['score']))
return True
except Exception, e:
traceback.print_exc()
Expand Down
4 changes: 2 additions & 2 deletions perimeterx/px_httpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ def send(full_url, body, headers, config, method):
try:
start = time.time()
if method == 'GET':
response = requests.get(url='https://' + full_url, headers=headers, timeout=500, stream=True)
response = requests.get(url='https://' + full_url, headers=headers, timeout=config.api_timeout, stream=True)
else:
response = requests.post(url='https://' + full_url, headers=headers, data=body, timeout=config.api_timeout)

Expand All @@ -17,6 +17,6 @@ def send(full_url, body, headers, config, method):

logger.debug('PerimeterX server call took ' + str(time.time() - start) + 'ms')
return response
except requests.exceptions.RequestException as e:
except requests.exceptions as e:
logger.debug('Received RequestException, message: ' + e.message)
return False
Loading