def add_cookie(self, response=None, **kwargs): if response is None: response = self.current_response if not kwargs.has_key('secure'): if ESAPI.security_configuration().get_force_secure_cookies(): kwargs['secure'] = True if not kwargs.has_key('httponly'): if ESAPI.security_configuration().get_force_http_only_cookies(): kwargs['httponly'] = True # Validate the key and value errors = ValidationErrorList() safe_key = ESAPI.validator().get_valid_input("cookie name", kwargs['key'], "HTTPCookieName", 50, False, errors) safe_value = ESAPI.validator().get_valid_input("cookie value", kwargs['value'], "HTTPCookieValue", 5000, False, errors) kwargs['key'] = safe_key kwargs['value'] = safe_value # If no errors, set the cookie if len(errors) == 0: response.set_cookie(**kwargs) return # Error! self.logger.warning( Logger.SECURITY_FAILURE, _("Attempt to add unsafe data to cookie (skip mode). Skipping cookie and continuing.") )
def test_is_valid_dir_path(self): encoder_class = ESAPI.security_configuration().get_class_for_interface('encoder') validator_class = ESAPI.security_configuration().get_class_for_interface('validator') encoder = encoder_class([HTMLEntityCodec()]) instance = validator_class(encoder) if os.name == 'nt': # Windows # Windows paths that don't exist and thus should fail self.assertFalse(instance.is_valid_directory_path("test", "c:\\ridiculous", "c:\\", False)) self.assertFalse(instance.is_valid_directory_path("test", "c:\\jeff", "c:\\", False)) self.assertFalse(instance.is_valid_directory_path("test", "c:\\temp\\..\\etc", "c:\\", False)) # When the parent directory doesn't exist, these should fail self.assertFalse(instance.is_valid_directory_path("test", "c:\\", "c:\\ridiculous", False)) self.assertFalse(instance.is_valid_directory_path("test", "c:\\", None, False)) # Windows paths that should pass self.assertTrue(instance.is_valid_directory_path("test", "C:\\", "C:\\", False)) # Windows root directory self.assertTrue(instance.is_valid_directory_path("test", "C:\\Windows", "C:\\", False)) # Windows always exist directory # Should fail for files self.assertFalse(instance.is_valid_directory_path("test", "C:\\Windows\\System32\\cmd.exe", "C:\\", False)) # Windows command shell # Testing case insensitivity between input and parent_dir self.assertTrue(instance.is_valid_directory_path("test", "C:\\", "c:\\", False)) # Windows root directory self.assertTrue(instance.is_valid_directory_path("test", "c:\\Windows", "C:\\", False)) # Windows always exist directory # Testing the verification of the parent directory self.assertFalse(instance.is_valid_directory_path("test", "c:\\", "C:\\windows", False)) # Windows always exist directory self.assertFalse(instance.is_valid_directory_path("test", "C:\\", "C:\\windows", False)) # Windows always exist directory # Unix specific paths should not pass self.assertFalse(instance.is_valid_directory_path("test", "/tmp", "/", False)) # Unix Temporary directory self.assertFalse(instance.is_valid_directory_path("test", "/bin/sh", "/", False)) # Unix Standard shell self.assertFalse(instance.is_valid_directory_path("test", "/etc/config", "/", False)) # Unix specific paths that should not exist or work self.assertFalse(instance.is_valid_directory_path("test", "/etc/ridiculous", "/", False)) self.assertFalse(instance.is_valid_directory_path("test", "/tmp/../etc", "/", False)) else: # Windows paths should fail self.assertFalse(instance.is_valid_directory_path("test", "c:\\ridiculous", "c:\\", False)) self.assertFalse(instance.is_valid_directory_path("test", "c:\\temp\\..\\etc", "c:\\", False)) # Standard Windows locations should fail self.assertFalse(instance.is_valid_directory_path("test", "c:\\", "c:\\", False)) self.assertFalse(instance.is_valid_directory_path("test", "c:\\Windows\\temp", "c:\\", False)) self.assertFalse(instance.is_valid_directory_path("test", "c:\\Windows\\System32\\cmd.exe", "c:\\", False)) # Unix specific paths should pass # Root self.assertTrue(instance.is_valid_directory_path("test", "/", "/", False)) # /bin self.assertTrue(instance.is_valid_directory_path("test", "/bin", "/", False)) # Unix specific paths that should not exist or work self.assertFalse(instance.is_valid_directory_path("test", "/etc/ridiculous", "/", False)) self.assertFalse(instance.is_valid_directory_path("test", "/tmp/../etc", "/", False))
def __init__(self): Encryptor.__init__(self) self.logger = ESAPI.logger("DefaultEncryptor") # Hashing self.hash_algorithm = ESAPI.security_configuration().get_hash_algorithm() self.hash_iterations = ESAPI.security_configuration().get_hash_iterations() # Encryption self.encrypt_algorithm = ESAPI.security_configuration().get_encryption_algorithm() if self.encrypt_algorithm not in self.VALID_ENCRYPTION_ALGOS: raise EncryptionException( _("Encryption Failure - Unknown algorithm for encryption: %(algorithm)s") % {'algorithm' : self.encrypt_algorithm} ) self.encryption_key_length = ESAPI.security_configuration().get_encryption_key_length() self.master_salt = ESAPI.security_configuration().get_master_salt() # Public key crypto self.signing_algorithm = ESAPI.security_configuration().get_digital_signature_algorithm() if self.signing_algorithm not in self.VALID_SIGNING_ALGOS: raise EncryptionException( _("Failure to encrypt"), _("Encryption Failure - Unknown algorithm for signing: %(algorithm)s") % {'algorithm' : self.signing_algorithm} ) self.signing_key_length = ESAPI.security_configuration().get_digital_signature_key_length() # Key locations self.keys_location = os.path.realpath(ESAPI.security_configuration().get_encryption_keys_location()) + '/' self.keys_symmetric_location = self.keys_location + "symmetric" self.keys_asymmetric_private_location = self.keys_location + "asymmetric-private" self.keys_asymmetric_public_location = self.keys_location + "asymmetric-public"
def add_exception(self, exception): # Log the exception if hasattr(exception, 'get_log_message'): self.logger.warning( Logger.SECURITY_FAILURE, exception.get_log_message(), exception ) else: self.logger.warning( Logger.SECURITY_FAILURE, exception.message, exception ) if isinstance(exception, IntrusionException): return # Add the exception to the current user, which may trigger a # dector user = ESAPI.authenticator().current_user event_name = exception.__class__.__name__ try: self.add_security_event(user, event_name) except IntrusionException, extra: quota = ESAPI.security_configuration().get_quota(event_name) for action in quota.actions: message = (_("User exceeded quota of %(count)s per %(interval)s seconds for event %(event_name)s. Taking actions %(actions)s") % {'count' : quota.count, 'interval' : quota.interval, 'event_name' : event_name, 'actions' : quota.actions,}) self.take_security_action(action, message)
def get_cc_rule(self, encoder): pattern = ESAPI.security_configuration().get_validation_pattern( "CreditCard") ccr = StringValidationRule("ccrule", encoder, pattern) ccr.set_maximum_length(CC_MAX_LENGTH) ccr.set_allow_none(False) return ccr
def add_event(self, event_name, log_message): self.logger.warning( Logger.SECURITY_FAILURE, _("Security event %(name)s received: %(message)s") % { 'name': event_name, 'message': log_message, }) # Add the event to the current user, which may trigger a detector user = ESAPI.authenticator().current_user try: self.add_security_event(user, "event_" + event_name) except IntrusionException, extra: quota = ESAPI.security_configuration().get_quota("event_" + event_name) for action in quota.actions: message = (_( "User exceeded quota of %(count)s per %(interval)s seconds for event %(event_name)s. Taking actions %(actions)s" ) % { 'count': quota.count, 'interval': quota.interval, 'event_name': event_name, 'actions': quota.actions, }) self.take_security_action(action, message)
def add_exception(self, exception): # Log the exception if hasattr(exception, 'get_log_message'): self.logger.warning(Logger.SECURITY_FAILURE, exception.get_log_message(), exception) else: self.logger.warning(Logger.SECURITY_FAILURE, exception.message, exception) if isinstance(exception, IntrusionException): return # Add the exception to the current user, which may trigger a # dector user = ESAPI.authenticator().current_user event_name = exception.__class__.__name__ try: self.add_security_event(user, event_name) except IntrusionException, extra: quota = ESAPI.security_configuration().get_quota(event_name) for action in quota.actions: message = (_( "User exceeded quota of %(count)s per %(interval)s seconds for event %(event_name)s. Taking actions %(actions)s" ) % { 'count': quota.count, 'interval': quota.interval, 'event_name': event_name, 'actions': quota.actions, }) self.take_security_action(action, message)
def login_with_password(self, password): if password is None: self.last_failed_login_time = datetime.now() self.increment_failed_login_count() raise AuthenticationLoginException( _("Login failed"), _("Missing password: %(account_name)s") % {'account_name': self.account_name}) # Don't let disabled users log in if not self.is_enabled(): self.last_failed_login_time = datetime.now() self.increment_failed_login_count() raise AuthenticationLoginException( _("Login failed"), _("Disabled user attempt to login: %(account_name)s") % {'account_name': self.account_name}) # Don't let locked users log in if self.is_locked(): self.last_failed_login_time = datetime.now() self.increment_failed_login_count() raise AuthenticationLoginException( _("Login failed"), _("Locked user attempt to login: %(account_name)s") % {'account_name': self.account_name}) # Don't let expired users log in if self.is_expired(): self.last_failed_login_time = datetime.now() self.increment_failed_login_count() raise AuthenticationLoginException( _("Login failed"), _("Expired user attempt to login: %(account_name)s") % {'account_name': self.account_name}) self.logout() if self.verify_password(password): self._logged_in = True ESAPI.http_utilities().change_session_identifier( ESAPI.current_request()) ESAPI.authenticator().current_user = self self.last_login_time = datetime.now() self.last_host_address = ESAPI.http_utilities( ).get_current_request().remote_host self.logger.trace( Logger.SECURITY_SUCCESS, _("User logged in: %(account_name)s") % {'account_name': self.account_name}) else: self._logged_in = False self.last_failed_login_time = datetime.now() self.increment_failed_login_count() if self.get_failed_login_count() >= ESAPI.security_configuration( ).get_allowed_login_attempts(): self.lock() raise AuthenticationLoginException( _("Login failed"), _("Incorrect password provided for %(account_name)s") % {'account_name': self.account_name})
def __init__(self): self.logger = ESAPI.logger("Executor") self.working_dir = ESAPI.security_configuration().get_working_directory() self.max_running_time = ESAPI.security_configuration().get_max_running_time() if os.name == "nt": self.logger.warning( Logger.SECURITY_SUCCESS, _("Using WindowsCodec for Executor. If this is not running on Windows, this could allow for injection"), ) self.codec = WindowsCodec() else: self.logger.warning( Logger.SECURITY_SUCCESS, _("Using UnixCodec for Executor. If this is not running on Unix, this could allow injection"), ) self.codec = UnixCodec()
def log(self, level, event_type, message, exception=None): """ Log the message after optionally encoding any special characters that might be dangerous when viewed by an HTML based log viewer. Also encode any carriage returns and line feeds to prevent log injection attacks. This logs all the supplied parameters plus the user ID, user's source IP, a logging specific session ID, and the current date/time. It will only log the message if the current logging level is enabled, otherwise it will discard the message. @param level: the severity level of the security event @param event_type: the event_type of the event (SECURITY, FUNCTIONALITY, etc.) @param message: the message @param exception: an exception """ # Before we waste all kinds of time preparing this event for the # log, let check to see if its loggable if not self.pyLogger.isEnabledFor(level): return user = ESAPI.authenticator().current_user # create a random session number for the user to represent the # user's 'session', if it doesn't exist already sid = _("unknown") request = ESAPI.http_utilities().current_request if request is not None: session = request.session if session is not None: sid = session.get('ESAPI_SESSION', None) # if there is no session id for the user yet, create one # and store it in the user's session if sid is None: sid = str(ESAPI.randomizer().get_random_integer(0, 1000000)) session['ESAPI_SESSION'] = sid # ensure there's something to log if message is None: message = "" # ensure no CRLF injection into logs for forging records clean = message.replace('\n', '_').replace('\r', '_') if ESAPI.security_configuration().get_log_encoding_required(): clean = ESAPI.encoder().encode_for_html(message) if message != clean: clean += " (Encoded)" extra = { 'eventType' : str(event_type), 'eventSuccess' : [_("SUCCESS"),_("FAILURE")][event_type.is_success()], 'user' : user.account_name, 'hostname' : user.last_host_address, 'sessionID' : sid, } self.pyLogger.log(level, clean, extra=extra)
def make_file_validator(self): if DefaultValidator.file_validator is not None: return DefaultValidator.file_validator = 'fail' file_codecs = [HTMLEntityCodec(), PercentCodec()] encoder_class = ESAPI.security_configuration().get_class_for_interface('encoder') file_encoder = encoder_class(file_codecs) DefaultValidator.file_validator = DefaultValidator( file_encoder )
def __init__(self): self.logger = ESAPI.logger("Executor") self.working_dir = ESAPI.security_configuration( ).get_working_directory() self.max_running_time = ESAPI.security_configuration( ).get_max_running_time() if os.name == 'nt': self.logger.warning( Logger.SECURITY_SUCCESS, _("Using WindowsCodec for Executor. If this is not running on Windows, this could allow for injection" )) self.codec = WindowsCodec() else: self.logger.warning( Logger.SECURITY_SUCCESS, _("Using UnixCodec for Executor. If this is not running on Unix, this could allow injection" )) self.codec = UnixCodec()
def get_valid_file_content(self, context, input_, max_bytes, allow_none, errors=None): """ This implementation only verifies that the file size is less than or equal to the max_bytes specified in the parameter and less than the max bytes specified in ESAPI.conf.settings. """ try: if self.is_empty(input_): if allow_none: return None raise ValidationException( _("%(context)s: Input required") % {'context': context}, _("Input required: context=%(context)s, input=%(input)s") % { 'context': context, 'input': input_ }, context) esapi_max_bytes = ESAPI.security_configuration( ).get_allowed_file_upload_size() if len(input_) > esapi_max_bytes: raise ValidationException( _("%(context)s: Invalid file content can not exceed %(max_bytes)s bytes" ) % { 'context': context, 'max_bytes': esapi_max_bytes }, _("Exceeded ESAPI max length of %(max_bytes) bytes by %(exceeded)s bytes" ) % { 'max_bytes': esapi_max_bytes, 'exceeded': len(input_) - esapi_max_bytes }, context) if len(input_) > max_bytes: raise ValidationException( _("%(context)s: Invalid file content can not exceed %(max_bytes)s bytes" ) % { 'context': context, 'max_bytes': max_bytes }, _("Exceeded max_bytes of %(max_bytes)s bytes by %(exceeded)s bytes" ) % { 'max_bytes': max_bytes, 'exceeded': len(input_) - max_bytes }, context) return input_ except ValidationException, extra: if errors is not None: errors[context] = extra else: raise
def make_file_validator(self): if DefaultValidator.file_validator is not None: return DefaultValidator.file_validator = 'fail' file_codecs = [HTMLEntityCodec(), PercentCodec()] encoder_class = ESAPI.security_configuration().get_class_for_interface( 'encoder') file_encoder = encoder_class(file_codecs) DefaultValidator.file_validator = DefaultValidator(file_encoder)
def get_valid_input(self, context, input_, type_, max_length, allow_none, error_list=None): rvr = StringValidationRule( type_, self.encoder ) pattern = ESAPI.security_configuration().get_validation_pattern(type_) if pattern is not None: rvr.add_whitelist_pattern(pattern) else: rvr.add_whitelist_pattern(type_) rvr.set_maximum_length(max_length) rvr.set_allow_none(allow_none) return rvr.get_valid(context, input_, error_list)
def login_with_password(self, password): if password is None: self.last_failed_login_time = datetime.now() self.increment_failed_login_count() raise AuthenticationLoginException( _("Login failed"), _("Missing password: %(account_name)s") % {'account_name' : self.account_name}) # Don't let disabled users log in if not self.is_enabled(): self.last_failed_login_time = datetime.now() self.increment_failed_login_count() raise AuthenticationLoginException( _("Login failed"), _("Disabled user attempt to login: %(account_name)s") % {'account_name' : self.account_name}) # Don't let locked users log in if self.is_locked(): self.last_failed_login_time = datetime.now() self.increment_failed_login_count() raise AuthenticationLoginException( _("Login failed"), _("Locked user attempt to login: %(account_name)s") % {'account_name' : self.account_name}) # Don't let expired users log in if self.is_expired(): self.last_failed_login_time = datetime.now() self.increment_failed_login_count() raise AuthenticationLoginException( _("Login failed"), _("Expired user attempt to login: %(account_name)s") % {'account_name' : self.account_name}) self.logout() if self.verify_password( password ): self._logged_in = True ESAPI.http_utilities().change_session_identifier( ESAPI.current_request() ) ESAPI.authenticator().current_user = self self.last_login_time = datetime.now() self.last_host_address = ESAPI.http_utilities().get_current_request().remote_host self.logger.trace(Logger.SECURITY_SUCCESS, _("User logged in: %(account_name)s") % {'account_name' : self.account_name}) else: self._logged_in = False self.last_failed_login_time = datetime.now() self.increment_failed_login_count() if self.get_failed_login_count() >= ESAPI.security_configuration().get_allowed_login_attempts(): self.lock() raise AuthenticationLoginException( _("Login failed"), _("Incorrect password provided for %(account_name)s") % {'account_name' : self.account_name})
def get_valid_filename(self, context, input_, allow_none, error_list=None, allowed_extensions=None): try: if self.is_empty(input_): if allow_none: return None raise ValidationException( _("%(context)s: Input file name required") % {'context': context}, _("Input required: context=%(context)s, input=%(input)s") % { 'context': context, 'input': input_ }, context) # Do basic validation self.get_valid_input(context, input_, "Filename", 255, True) # Verify extensions if not allowed_extensions: allowed_extensions = ESAPI.security_configuration( ).get_allowed_file_extensions() file_ext = os.path.splitext(input_)[1] file_ext = file_ext.lower() if file_ext in allowed_extensions: return input_ else: raise ValidationException( _("%(context)s: Invalid file name does not have valid extension ( %(allowed_extensions)s )" ) % { 'context': context, 'allowed_extensions': allowed_extensions }, _("Invalid file name does not have valid extension ( %(allowed_extensions)s ): context=%(context)s, input=%(input)s" ) % { 'allowed_extensions': allowed_extensions, 'context': context, 'input': input_ }, context) except ValidationException, extra: if error_list is not None: error_list[context] = extra else: raise
def add_cookie(self, response=None, **kwargs): if response is None: response = self.current_response if not kwargs.has_key('secure'): if ESAPI.security_configuration().get_force_secure_cookies(): kwargs['secure'] = True if not kwargs.has_key('httponly'): if ESAPI.security_configuration().get_force_http_only_cookies(): kwargs['httponly'] = True # Validate the key and value errors = ValidationErrorList() safe_key = ESAPI.validator().get_valid_input("cookie name", kwargs['key'], "HTTPCookieName", 50, False, errors) safe_value = ESAPI.validator().get_valid_input("cookie value", kwargs['value'], "HTTPCookieValue", 5000, False, errors) kwargs['key'] = safe_key kwargs['value'] = safe_value # If no errors, set the cookie if len(errors) == 0: response.set_cookie(**kwargs) return # Error! self.logger.warning( Logger.SECURITY_FAILURE, _("Attempt to add unsafe data to cookie (skip mode). Skipping cookie and continuing." ))
def get_valid_input(self, context, input_, type_, max_length, allow_none, error_list=None): rvr = StringValidationRule(type_, self.encoder) pattern = ESAPI.security_configuration().get_validation_pattern(type_) if pattern is not None: rvr.add_whitelist_pattern(pattern) else: rvr.add_whitelist_pattern(type_) rvr.set_maximum_length(max_length) rvr.set_allow_none(allow_none) return rvr.get_valid(context, input_, error_list)
def get_valid_file_content(self, context, input_, max_bytes, allow_none, errors=None ): """ This implementation only verifies that the file size is less than or equal to the max_bytes specified in the parameter and less than the max bytes specified in ESAPI.conf.settings. """ try: if self.is_empty(input_): if allow_none: return None raise ValidationException( _("%(context)s: Input required") % {'context' : context}, _("Input required: context=%(context)s, input=%(input)s") % {'context' : context, 'input' : input_}, context ) esapi_max_bytes = ESAPI.security_configuration().get_allowed_file_upload_size() if len(input_) > esapi_max_bytes: raise ValidationException( _("%(context)s: Invalid file content can not exceed %(max_bytes)s bytes") % {'context' : context, 'max_bytes' : esapi_max_bytes}, _("Exceeded ESAPI max length of %(max_bytes) bytes by %(exceeded)s bytes") % {'max_bytes' : esapi_max_bytes, 'exceeded' : len(input_) - esapi_max_bytes}, context ) if len(input_) > max_bytes: raise ValidationException( _("%(context)s: Invalid file content can not exceed %(max_bytes)s bytes") % {'context' : context, 'max_bytes' : max_bytes}, _("Exceeded max_bytes of %(max_bytes)s bytes by %(exceeded)s bytes") % {'max_bytes' : max_bytes, 'exceeded' : len(input_) - max_bytes}, context ) return input_ except ValidationException, extra: if errors is not None: errors[context] = extra else: raise
def add_security_event(self, user, event_name): """ Adds a security event to the user. These events are used to check that the user has not reached the security thresholds set in the SecurityConfiguration. @param user: the user tha caused the event @param event_name: the name of the event that occurred. """ if user.is_anonymous(): return threshold = ESAPI.security_configuration().get_quota(event_name) if threshold is not None: event = user.event_map.get(event_name) if event is None: event = self.Event(event_name) user.event_map[event_name] = event event.increment(threshold.count, threshold.interval)
def add_security_event(self, user, event_name): """ Adds a security event to the user. These events are used to check that the user has not reached the security thresholds set in the SecurityConfiguration. @param user: the user tha caused the event @param event_name: the name of the event that occurred. """ if user.is_anonymous(): return threshold = ESAPI.security_configuration().get_quota(event_name) if threshold is not None: event = user.event_map.get(event_name) if event is None: event = self.Event(event_name) user.event_map[event_name] = event event.increment(threshold.count, threshold.interval)
def add_event(self, event_name, log_message): self.logger.warning( Logger.SECURITY_FAILURE, _("Security event %(name)s received: %(message)s") % {'name' : event_name, 'message' : log_message,} ) # Add the event to the current user, which may trigger a detector user = ESAPI.authenticator().current_user try: self.add_security_event(user, "event_" + event_name) except IntrusionException, extra: quota = ESAPI.security_configuration().get_quota("event_" + event_name) for action in quota.actions: message = (_("User exceeded quota of %(count)s per %(interval)s seconds for event %(event_name)s. Taking actions %(actions)s") % {'count' : quota.count, 'interval' : quota.interval, 'event_name' : event_name, 'actions' : quota.actions,}) self.take_security_action(action, message)
def __init__(self): Encryptor.__init__(self) self.logger = ESAPI.logger("DefaultEncryptor") # Hashing self.hash_algorithm = ESAPI.security_configuration( ).get_hash_algorithm() self.hash_iterations = ESAPI.security_configuration( ).get_hash_iterations() # Encryption self.encrypt_algorithm = ESAPI.security_configuration( ).get_encryption_algorithm() if self.encrypt_algorithm not in self.VALID_ENCRYPTION_ALGOS: raise EncryptionException( _("Encryption Failure - Unknown algorithm for encryption: %(algorithm)s" ) % {'algorithm': self.encrypt_algorithm}) self.encryption_key_length = ESAPI.security_configuration( ).get_encryption_key_length() self.master_salt = ESAPI.security_configuration().get_master_salt() # Public key crypto self.signing_algorithm = ESAPI.security_configuration( ).get_digital_signature_algorithm() if self.signing_algorithm not in self.VALID_SIGNING_ALGOS: raise EncryptionException( _("Failure to encrypt"), _("Encryption Failure - Unknown algorithm for signing: %(algorithm)s" ) % {'algorithm': self.signing_algorithm}) self.signing_key_length = ESAPI.security_configuration( ).get_digital_signature_key_length() # Key locations self.keys_location = os.path.realpath(ESAPI.security_configuration( ).get_encryption_keys_location()) + '/' self.keys_symmetric_location = self.keys_location + "symmetric" self.keys_asymmetric_private_location = self.keys_location + "asymmetric-private" self.keys_asymmetric_public_location = self.keys_location + "asymmetric-public"
def get_valid_filename( self, context, input_, allow_none, error_list=None, allowed_extensions=None): try: if self.is_empty(input_): if allow_none: return None raise ValidationException( _("%(context)s: Input file name required") % {'context' : context}, _("Input required: context=%(context)s, input=%(input)s") % {'context' : context, 'input' : input_}, context ) # Do basic validation self.get_valid_input(context, input_, "Filename", 255, True) # Verify extensions if not allowed_extensions: allowed_extensions = ESAPI.security_configuration().get_allowed_file_extensions() file_ext = os.path.splitext(input_)[1] file_ext = file_ext.lower() if file_ext in allowed_extensions: return input_ else: raise ValidationException( _("%(context)s: Invalid file name does not have valid extension ( %(allowed_extensions)s )") % {'context' : context, 'allowed_extensions' : allowed_extensions}, _("Invalid file name does not have valid extension ( %(allowed_extensions)s ): context=%(context)s, input=%(input)s") % {'allowed_extensions' : allowed_extensions, 'context' : context, 'input' : input_}, context ) except ValidationException, extra: if error_list is not None: error_list[context] = extra else: raise
class DefaultUser(User): """ The reference implementation of the User interface. This implementation is pickled/shelved into a flat file. """ IDLE_TIMEOUT_LENGTH = ESAPI.security_configuration( ).get_session_idle_timeout_length() ABSOLUTE_TIMEOUT_LENGTH = ESAPI.security_configuration( ).get_session_absolute_timeout_length() MAX_ROLE_LENGTH = 250 def __init__(self, account_name): """ Instantiates a new user. @param account_name: The name of this user's account. """ User.__init__(self) self._account_name = None self._set_account_name(account_name) # Get random numbers until we find an unused account number # WARNING: This could cause in infinite loop if the number of users equals the keyspace of uids. while True: id = ESAPI.randomizer().get_random_integer(1) if id != 0 and not ESAPI.authenticator().exists(account_id=id): self._account_id = id break self.logger = ESAPI.logger("DefaultUser") self._screen_name = None self._csrf_token = self.reset_csrf_token() self._roles = [] self._locked = False self._logged_in = False self._enabled = False self._last_host_address = None self._last_password_change_time = None self._last_login_time = datetime.min self._last_failed_login_time = datetime.min self._expiration_time = datetime.max self._sessions = [] # Security event dictionary, used by the IntrusionDetector self.event_map = {} self._failed_login_count = 0 self._locale = None # Login def login_with_password(self, password): if password is None: self.last_failed_login_time = datetime.now() self.increment_failed_login_count() raise AuthenticationLoginException( _("Login failed"), _("Missing password: %(account_name)s") % {'account_name': self.account_name}) # Don't let disabled users log in if not self.is_enabled(): self.last_failed_login_time = datetime.now() self.increment_failed_login_count() raise AuthenticationLoginException( _("Login failed"), _("Disabled user attempt to login: %(account_name)s") % {'account_name': self.account_name}) # Don't let locked users log in if self.is_locked(): self.last_failed_login_time = datetime.now() self.increment_failed_login_count() raise AuthenticationLoginException( _("Login failed"), _("Locked user attempt to login: %(account_name)s") % {'account_name': self.account_name}) # Don't let expired users log in if self.is_expired(): self.last_failed_login_time = datetime.now() self.increment_failed_login_count() raise AuthenticationLoginException( _("Login failed"), _("Expired user attempt to login: %(account_name)s") % {'account_name': self.account_name}) self.logout() if self.verify_password(password): self._logged_in = True ESAPI.http_utilities().change_session_identifier( ESAPI.current_request()) ESAPI.authenticator().current_user = self self.last_login_time = datetime.now() self.last_host_address = ESAPI.http_utilities( ).get_current_request().remote_host self.logger.trace( Logger.SECURITY_SUCCESS, _("User logged in: %(account_name)s") % {'account_name': self.account_name}) else: self._logged_in = False self.last_failed_login_time = datetime.now() self.increment_failed_login_count() if self.get_failed_login_count() >= ESAPI.security_configuration( ).get_allowed_login_attempts(): self.lock() raise AuthenticationLoginException( _("Login failed"), _("Incorrect password provided for %(account_name)s") % {'account_name': self.account_name}) def logout(self): return ESAPI.authenticator().logout(self) def is_logged_in(self): return self._logged_in # Locale def _get_locale(self): return self._locale def _set_locale(self, locale): self._locale = locale locale = property(_get_locale, _set_locale) # Roles def add_role(self, role): """ If role is a string, it will be lower()'d. """ if isinstance(role, str): role = role.lower() if ESAPI.validator().is_valid_input("addRole", role, "RoleName", DefaultUser.MAX_ROLE_LENGTH, False): if role not in self._roles: self._roles.append(role) self.logger.info( Logger.SECURITY_SUCCESS, _("Role %(role_name)s added to %(account_name)s") % { 'role_name': role, 'account_name': self.account_name }) else: # Role already assigned pass else: raise AuthenticationAccountsException( _("Add role failed"), _("Attempt to add invalid role %(role_name)s to %(account_name)s" ) % { 'role_name': role, 'account_name': self.account_name }) def remove_role(self, role): if isinstance(role, str): role = role.lower() try: self._roles.remove(role) self.logger.trace( Logger.SECURITY_SUCCESS, _("Role %(role_name)s removed from %(account_name)") % { 'role_name': role, 'account_name': self.account_name }) except ValueError: # Raise an exception? pass def is_in_role(self, role): if isinstance(role, str): role = role.lower() return role in self._roles def _get_roles(self): return tuple(self._roles) def _set_roles(self, roles): self._roles = list(roles)[:] roles = property(_get_roles, _set_roles, doc="The roles assigned to a particular user") def add_roles(self, roles): for role in roles: self.add_role(role) # Passwords def verify_password(self, password): return ESAPI.authenticator().verify_password(self, password) def change_password(self, old_password, new_password1, new_password2): ESAPI.authenticator().change_password(self, old_password, new_password1, new_password2) def _get_last_password_change_time(self): return self._last_password_change_time def _set_last_password_change_time(self, time): self._last_password_change_time = time self.logger.info( Logger.SECURITY_SUCCESS, _("Set last password change time to %(time)s for %(account_name)s") % { 'time': time, 'account_name': self.account_name }) last_password_change_time = property( _get_last_password_change_time, _set_last_password_change_time, doc="The time of the last password change for this user.") # Enable/Disable def disable(self): self._enabled = False self.logger.info( Logger.SECURITY_SUCCESS, _("Account disabled: %(account_name)s") % {'account_name': self.account_name}) def enable(self): self._enabled = True self.logger.info( Logger.SECURITY_SUCCESS, _("Account enabled: %(account_name)s") % {'account_name': self.account_name}) def is_enabled(self): return self._enabled # Account id def _get_account_id(self): return self._account_id account_id = property(_get_account_id, doc="The User's account ID") # Account name def _get_account_name(self): return self._account_name def _set_account_name(self, name): old = self.account_name self._account_name = name.lower() if old is not None: if old == "": old = "[nothing]" self.logger.info( Logger.SECURITY_SUCCESS, _("Account name changed from %(old)s to %(new)s") % { 'old': old, 'new': self.account_name }) account_name = property(_get_account_name, _set_account_name, doc="The User's account name") # CSRF tokens def _get_csrf_token(self): return self._csrf_token csrf_token = property(_get_csrf_token, doc="The User's CSRF token") def reset_csrf_token(self): self._csrf_token = ESAPI.randomizer().get_random_string( 8, Encoder.CHAR_ALPHANUMERICS) return self.csrf_token # Expiration def _get_expiration_time(self): return self._expiration_time def _set_expiration_time(self, expiration_time): self._expiration_time = expiration_time self.logger.info( Logger.SECURITY_SUCCESS, _("Account expiration time was set to %(time)s for %(account_name)s" ) % { 'time': expiration_time, 'account_name': self.account_name }) expiration_time = property( _get_expiration_time, _set_expiration_time, doc="The date and time that this User's account will expire") def is_expired(self): return self.expiration_time < datetime.now() # Failed logins def get_failed_login_count(self): return self._failed_login_count def increment_failed_login_count(self): self._failed_login_count += 1 def _get_last_failed_login_time(self): return self._last_failed_login_time def _set_last_failed_login_time(self, time): self._last_failed_login_time = time self.logger.info( Logger.SECURITY_SUCCESS, _("Set last failed login time to %(time)s for %(user)s") % { 'time': time, 'user': self.account_name }) last_failed_login_time = property( _get_last_failed_login_time, _set_last_failed_login_time, doc="The date and time of the last failed login for the user.") # Host address def _get_last_host_address(self): if self._last_host_address is None: return "unknown" return self._last_host_address def _set_last_host_address(self, address): if (self._last_host_address is not None and self._last_host_address != address): raise AuthenticationHostException( _("Host change"), _("User sessions just jumped from %(old)s to %(new)s") % { 'old': self._last_host_address, 'new': address }) self._last_host_address = address last_host_address = property(_get_last_host_address, _set_last_host_address, doc="The last host address used by this user") # Login times def _get_last_login_time(self): return self._last_login_time def _set_last_login_time(self, time): self._last_login_time = time self.logger.info( Logger.SECURITY_SUCCESS, _("Set last successful login time to %(time)s for %(account_name)s" ) % { 'time': time, 'account_name': self.account_name }) last_login_time = property( _get_last_login_time, _set_last_login_time, doc="The date and time the user last successfully logged in.") # Screen names def _get_screen_name(self): return self._screen_name def _set_screen_name(self, new_screen_name): self._screen_name = new_screen_name self.logger.info( Logger.SECURITY_SUCCESS, _("ScreenName changed to %(new)s for %(account_name)s") % { 'new': new_screen_name, 'account_name': self.account_name }) screen_name = property(_get_screen_name, _set_screen_name, doc="The screen name or alias for the User") # Session def add_session(self, session): self._sessions.append(session) def remove_session(self, session): try: self._sessions.remove(session) except: pass def get_sessions(self): return tuple(self._sessions) # Anonymous user def is_anonymous(self): return False # Timeouts def is_session_absolute_timeout(self): session = ESAPI.http_utilities().current_request.session if session is None: return True deadline = session.creation_time + self.ABSOLUTE_TIMEOUT_LENGTH return datetime.now() > deadline def is_session_timeout(self): session = ESAPI.http_utilities().current_request.session if session is None: return True deadline = session.last_accessed_time + self.IDLE_TIMEOUT_LENGTH return datetime.now() > deadline # Locking def lock(self): self._locked = True self.logger.info( Logger.SECURITY_SUCCESS, _("Account locked: %(account_name)s") % {'account_name': self.account_name}) def unlock(self): self._locked = False self._failed_login_count = 0 self.logger.info( Logger.SECURITY_SUCCESS, _("Account unlocked: %(account_name)s") % {'account_name': self.account_name}) def is_locked(self): return self._locked def __getstate__(self): # Copy the object's state from self.__dict__ which contains # all our instance attributes. Always use the dict.copy() # method to avoid modifying the original state. state = self.__dict__.copy() # Remove the unpicklable entries. del state['logger'] return state def __setstate__(self, state): """ Restore unpickleable instance attributes like logger. """ self.__dict__.update(state) self.logger = ESAPI.logger("DefaultUser")
def set_content_type(self, response=None): if response is None: response = self.current_response response.content_type = ESAPI.security_configuration().get_response_content_type()
def load_rules(self, rule_file): """ Loads the access rules by storing them in a dictionary. This method reads the file specified by the rule_file parameter, ignoring any lines that begin with the '#' character as comments. Sections of the access rules file are split by the '|' character. The method loads all paths, replacing '\\\\' characters with '/' for uniformity, then loads the list of comma separated roles. The roles are validated to be sure they are within the length and character set limitations specified in the validate_roles method. Then the permissions are stored for each item in the rules list. If the word 'allow' appears on the line, the specified roles are granted access to the data - otherwise, they will be denied access. Each path may only appear once in the access rules file. Any entry, after the first, containing the same path will be logged and ignored. @param rule_file: the name of the file that contains the access rules @return: a dictionary mapping path -> Rule object """ ret = {} input_file = None try: filename = ESAPI.security_configuration().get_resource_file( rule_file) input_file = open(filename, 'r') line = ESAPI.validator().safe_read_line(input_file, 500) while line != '': line = line.strip() if len(line) > 0 and line[0] != '#': rule = Rule() parts = line.split('|') rule.path = parts[0].strip().replace("\\\\", "/") roles = parts[1].strip().lower().split(',') roles = self.validate_roles(roles) for role in roles: rule.roles.append(role.strip()) action = parts[2].strip().lower() if action == 'allow' or action == 'deny': rule.allow = action == 'allow' else: for act in action.split(','): rule.actions.append(act.strip()) if ret.has_key(rule.path): self.logger.warning( Logger.SECURITY_FAILURE, _("Problem in access control file. Duplicate rule ignored: %(rule)s" ) % {'rule': rule}) else: ret[rule.path] = rule line = ESAPI.validator().safe_read_line(input_file, 500) except Exception, extra: raise self.logger.warning( Logger.SECURITY_FAILURE, _("Problem in access control file: %(file)s") % {'file': rule_file}, extra)
def get_cc_rule(self, encoder): pattern = ESAPI.security_configuration().get_validation_pattern("CreditCard") ccr = StringValidationRule("ccrule", encoder, pattern) ccr.set_maximum_length(CC_MAX_LENGTH) ccr.set_allow_none(False) return ccr
def get_new_arm(self, *args): klass = ESAPI.security_configuration().get_class_for_interface("access_reference_map") return klass(*args)
def get_new_arm(self, *args): klass = ESAPI.security_configuration().get_class_for_interface( "access_reference_map") return klass(*args)
def test_canonicalize(self): codecs = [HTMLEntityCodec(), PercentCodec()] encoder_class = ESAPI.security_configuration().get_class_for_interface('encoder') instance = encoder_class(codecs) # Test None paths self.assertEquals( None, instance.canonicalize(None)) self.assertEquals( None, instance.canonicalize(None, True)) self.assertEquals( None, instance.canonicalize(None, False)) # test exception paths self.assertEquals( "%", instance.canonicalize("%25", True)) self.assertEquals( "%", instance.canonicalize("%25", False)) self.assertEquals( "%", instance.canonicalize("%25")) self.assertEquals( "%F", instance.canonicalize("%25F")) self.assertEquals( "<", instance.canonicalize("%3c")) self.assertEquals( "<", instance.canonicalize("%3C")) self.assertEquals( "%X1", instance.canonicalize("%X1")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "%", instance.canonicalize("%")) self.assertEquals( "%", instance.canonicalize("%")) self.assertEquals( "%b", instance.canonicalize("%b")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) # percent encoding self.assertEquals( "<", instance.canonicalize("%3c")) self.assertEquals( "<", instance.canonicalize("%3C")) # html entity encoding self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("&lT")) self.assertEquals( "<", instance.canonicalize("&Lt")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<", instance.canonicalize("&lT;")) self.assertEquals( "<", instance.canonicalize("≪")) self.assertEquals( "<", instance.canonicalize("<")) self.assertEquals( "<script>alert(\"hello\");</script>", instance.canonicalize("%3Cscript%3Ealert%28%22hello%22%29%3B%3C%2Fscript%3E") ) self.assertEquals( "<script>alert(\"hello\");</script>", instance.canonicalize("%3Cscript>alert%28%22hello"%29%3B%3C%2Fscript%3E", False) ) # javascript escape syntax js = [JavascriptCodec()] instance = encoder_class( js ) self.assertEquals( "\0", instance.canonicalize("\\0")) self.assertEquals( "\b", instance.canonicalize("\\b")) self.assertEquals( "\t", instance.canonicalize("\\t")) self.assertEquals( "\n", instance.canonicalize("\\n")) self.assertEquals( unichr(0x0b), instance.canonicalize("\\v")) self.assertEquals( "\f", instance.canonicalize("\\f")) self.assertEquals( "\r", instance.canonicalize("\\r")) self.assertEquals( "\'", instance.canonicalize("\\'")) self.assertEquals( "\"", instance.canonicalize("\\\"")) self.assertEquals( "\\", instance.canonicalize("\\\\")) self.assertEquals( "<", instance.canonicalize("\\<")) self.assertEquals( "<", instance.canonicalize("\\u003c")) self.assertEquals( "<", instance.canonicalize("\\U003c")) self.assertEquals( "<", instance.canonicalize("\\u003C")) self.assertEquals( "<", instance.canonicalize("\\U003C")) self.assertEquals( "<", instance.canonicalize("\\x3c")) self.assertEquals( "<", instance.canonicalize("\\X3c")) self.assertEquals( "<", instance.canonicalize("\\x3C")) self.assertEquals( "<", instance.canonicalize("\\X3C")) # css escape syntax # be careful because some codecs see \0 as null byte css = [CSSCodec()] instance = encoder_class( css ) self.assertEquals( "<", instance.canonicalize("\\3c")); # add strings to prevent null byte self.assertEquals( "<", instance.canonicalize("\\03c")) self.assertEquals( "<", instance.canonicalize("\\003c")) self.assertEquals( "<", instance.canonicalize("\\0003c")) self.assertEquals( "<", instance.canonicalize("\\00003c")) self.assertEquals( "<", instance.canonicalize("\\3C")) self.assertEquals( "<", instance.canonicalize("\\03C")) self.assertEquals( "<", instance.canonicalize("\\003C")) self.assertEquals( "<", instance.canonicalize("\\0003C")) self.assertEquals( "<", instance.canonicalize("\\00003C"))
def __init__(self, test_name=""): """ @param test_name: the test name """ unittest.TestCase.__init__(self, test_name) self.user_class = ESAPI.security_configuration().get_class_for_interface('user')
def set_content_type(self, response=None): if response is None: response = self.current_response response.content_type = ESAPI.security_configuration( ).get_response_content_type()
class DefaultHTTPUtilities(HTTPUtilities): """ The default implementation of the HTTPUtilities class. Note: get_file_uploads() and send_redirect() are not implemented because they are highly dependent upon the framework. @author: Craig Younkins ([email protected]) """ max_bytes = ESAPI.security_configuration().get_allowed_file_upload_size() def __init__(self): self.logger = ESAPI.logger("HTTPUtilities") self.current_request = None self.current_response = None def add_cookie(self, response=None, **kwargs): if response is None: response = self.current_response if not kwargs.has_key('secure'): if ESAPI.security_configuration().get_force_secure_cookies(): kwargs['secure'] = True if not kwargs.has_key('httponly'): if ESAPI.security_configuration().get_force_http_only_cookies(): kwargs['httponly'] = True # Validate the key and value errors = ValidationErrorList() safe_key = ESAPI.validator().get_valid_input("cookie name", kwargs['key'], "HTTPCookieName", 50, False, errors) safe_value = ESAPI.validator().get_valid_input("cookie value", kwargs['value'], "HTTPCookieValue", 5000, False, errors) kwargs['key'] = safe_key kwargs['value'] = safe_value # If no errors, set the cookie if len(errors) == 0: response.set_cookie(**kwargs) return # Error! self.logger.warning( Logger.SECURITY_FAILURE, _("Attempt to add unsafe data to cookie (skip mode). Skipping cookie and continuing." )) def add_csrf_token(self, href): user = ESAPI.authenticator().current_user if user.is_anonymous(): return href # If there are already parameters, append with an &, otherwise append # with a ? token = self.CSRF_TOKEN_NAME + "=" + user.csrf_token if href.find('?') == -1: # No params yet return href + "?" + token else: # href has params already return href + "&" + token def add_header(self, name, value, response=None): if response is None: response = self.current_response stripped_name = name.strip() stripped_value = value.strip() try: safe_name = ESAPI.validator().get_valid_input( "addHeader", stripped_name, "HTTPHeaderName", 20, False) safe_value = ESAPI.validator().get_valid_input( "addHeader", stripped_value, "HTTPHeaderValue", 500, False) response.headers[safe_name] = safe_value except ValidationException, extra: self.logger.warning(Logger.SECURITY_FAILURE, _("Attempt to add invalid header denied"), extra)
def execute_system_command(self, executable, params, parent_dir, working_dir=None, codec=None, log_params=True): if codec is None: codec = self.codec if working_dir is None: working_dir = self.working_dir try: # Executable must exist if not os.path.exists(executable): raise ExecutorException( _("Execution failure"), _("No such executable: %(executable)s") % {"executable": executable} ) directory, filename = os.path.split(executable) # executable must use canonical path if not ESAPI.validator().is_valid_directory_path("Executor", directory, parent_dir, False): raise ExecutorException( _("Execution failure"), _("Directory did not pass validation: %(dir)s") % {"dir": directory} ) # Must be in approved list approved = ESAPI.security_configuration().get_allowed_executables() if executable not in approved: raise ExecutorException( _("Execution failure"), _( "Attempt to invoke executable that is not listed as an approved executable in configuration: %(executable)s" ) % {"executable": executable}, ) # Escape parameters params = [ESAPI.encoder().encode_for_os(codec, param) for param in params] # Working directory must exist if not os.path.exists(working_dir): raise ExecutorException( _("Execution failure"), _("No such working directory for running executable: %(dir)s") % {"dir": working_dir}, ) args = params args.insert(0, executable) start_time = datetime.now() proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=working_dir) if log_params: self.logger.warning( Logger.SECURITY_SUCCESS, _("Initiating executable %(args)s in %(dir)s") % {"args": str(args), "dir": working_dir}, ) else: self.logger.warning( Logger.SECURITY_SUCCESS, _("Initiating executable %(executable)s in %(dir)s") % {"executable": args[0], "dir": working_dir}, ) while proc.poll() is None and datetime.now() - start_time < self.max_running_time: time.sleep(1) if proc.poll() is None: # Kill the process because it ran too long proc.terminate() time.sleep(1) if proc.poll() is None: proc.kill() raise ExecutorException( _("Execution failure"), _("Process exceeded maximum running time and was killed: %(executable)s") % {"executable": executable}, ) else: # Process terminated in allotted timeframe stdout_and_err = proc.communicate() return stdout_and_err except Exception, extra: raise ExecutorException( _("Execution failure"), _("Exception thrown during execution of system command"), extra )
def log(self, level, event_type, message, exception=None): """ Log the message after optionally encoding any special characters that might be dangerous when viewed by an HTML based log viewer. Also encode any carriage returns and line feeds to prevent log injection attacks. This logs all the supplied parameters plus the user ID, user's source IP, a logging specific session ID, and the current date/time. It will only log the message if the current logging level is enabled, otherwise it will discard the message. @param level: the severity level of the security event @param event_type: the event_type of the event (SECURITY, FUNCTIONALITY, etc.) @param message: the message @param exception: an exception """ # Before we waste all kinds of time preparing this event for the # log, let check to see if its loggable if not self.pyLogger.isEnabledFor(level): return user = ESAPI.authenticator().current_user # create a random session number for the user to represent the # user's 'session', if it doesn't exist already sid = _("unknown") request = ESAPI.http_utilities().current_request if request is not None: session = request.session if session is not None: sid = session.get('ESAPI_SESSION', None) # if there is no session id for the user yet, create one # and store it in the user's session if sid is None: sid = str(ESAPI.randomizer().get_random_integer( 0, 1000000)) session['ESAPI_SESSION'] = sid # ensure there's something to log if message is None: message = "" # ensure no CRLF injection into logs for forging records clean = message.replace('\n', '_').replace('\r', '_') if ESAPI.security_configuration().get_log_encoding_required(): clean = ESAPI.encoder().encode_for_html(message) if message != clean: clean += " (Encoded)" extra = { 'eventType': str(event_type), 'eventSuccess': [_("SUCCESS"), _("FAILURE")][event_type.is_success()], 'user': user.account_name, 'hostname': user.last_host_address, 'sessionID': sid, } self.pyLogger.log(level, clean, extra=extra)
def execute_system_command(self, executable, params, parent_dir, working_dir=None, codec=None, log_params=True): if codec is None: codec = self.codec if working_dir is None: working_dir = self.working_dir try: # Executable must exist if not os.path.exists(executable): raise ExecutorException( _("Execution failure"), _("No such executable: %(executable)s") % {'executable': executable}) directory, filename = os.path.split(executable) # executable must use canonical path if not ESAPI.validator().is_valid_directory_path( "Executor", directory, parent_dir, False): raise ExecutorException( _("Execution failure"), _("Directory did not pass validation: %(dir)s") % {'dir': directory}) # Must be in approved list approved = ESAPI.security_configuration().get_allowed_executables() if executable not in approved: raise ExecutorException( _("Execution failure"), _("Attempt to invoke executable that is not listed as an approved executable in configuration: %(executable)s" ) % {'executable': executable}) # Escape parameters params = [ ESAPI.encoder().encode_for_os(codec, param) for param in params ] # Working directory must exist if not os.path.exists(working_dir): raise ExecutorException( _("Execution failure"), _("No such working directory for running executable: %(dir)s" ) % {'dir': working_dir}) args = params args.insert(0, executable) start_time = datetime.now() proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=working_dir) if log_params: self.logger.warning( Logger.SECURITY_SUCCESS, _("Initiating executable %(args)s in %(dir)s") % { 'args': str(args), 'dir': working_dir }) else: self.logger.warning( Logger.SECURITY_SUCCESS, _("Initiating executable %(executable)s in %(dir)s") % { 'executable': args[0], 'dir': working_dir }) while (proc.poll() is None and datetime.now() - start_time < self.max_running_time): time.sleep(1) if proc.poll() is None: # Kill the process because it ran too long proc.terminate() time.sleep(1) if proc.poll() is None: proc.kill() raise ExecutorException( _("Execution failure"), _("Process exceeded maximum running time and was killed: %(executable)s" ) % {'executable': executable}) else: # Process terminated in allotted timeframe stdout_and_err = proc.communicate() return stdout_and_err except Exception, extra: raise ExecutorException( _("Execution failure"), _("Exception thrown during execution of system command"), extra)
def load_rules(self, rule_file): """ Loads the access rules by storing them in a dictionary. This method reads the file specified by the rule_file parameter, ignoring any lines that begin with the '#' character as comments. Sections of the access rules file are split by the '|' character. The method loads all paths, replacing '\\\\' characters with '/' for uniformity, then loads the list of comma separated roles. The roles are validated to be sure they are within the length and character set limitations specified in the validate_roles method. Then the permissions are stored for each item in the rules list. If the word 'allow' appears on the line, the specified roles are granted access to the data - otherwise, they will be denied access. Each path may only appear once in the access rules file. Any entry, after the first, containing the same path will be logged and ignored. @param rule_file: the name of the file that contains the access rules @return: a dictionary mapping path -> Rule object """ ret = {} input_file = None try: filename = ESAPI.security_configuration().get_resource_file(rule_file) input_file = open(filename, 'r') line = ESAPI.validator().safe_read_line(input_file, 500) while line != '': line = line.strip() if len(line) > 0 and line[0] != '#': rule = Rule() parts = line.split('|') rule.path = parts[0].strip().replace("\\\\", "/") roles = parts[1].strip().lower().split(',') roles = self.validate_roles(roles) for role in roles: rule.roles.append(role.strip()) action = parts[2].strip().lower() if action == 'allow' or action == 'deny': rule.allow = action == 'allow' else: for act in action.split(','): rule.actions.append(act.strip()) if ret.has_key(rule.path): self.logger.warning( Logger.SECURITY_FAILURE, _("Problem in access control file. Duplicate rule ignored: %(rule)s") % {'rule' : rule} ) else: ret[rule.path] = rule line = ESAPI.validator().safe_read_line(input_file, 500) except Exception, extra: raise self.logger.warning( Logger.SECURITY_FAILURE, _("Problem in access control file: %(file)s") % {'file' : rule_file},