Skip to content

Commit

Permalink
Add visibility to unstable sessions during scans
Browse files Browse the repository at this point in the history
  • Loading branch information
andresriancho committed Nov 13, 2019
1 parent e000397 commit 2f0fd01
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 21 deletions.
28 changes: 18 additions & 10 deletions w3af/core/controllers/plugins/auth_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,12 @@ def _log_debug(self, message):
def _log_error(self, message):
self._log_messages.append(message)

formatted_message = self._format_message(message)
om.out.error(formatted_message)
# Send the message to the output without adding any formatting
om.out.error(message)

# This is here just for me to be able to quickly find all the activity
# of a specific auth plugin by grepping by its name
self._log_debug(message)

def _format_message(self, message):
message_fmt = '[auth.%s] %s (did: %s)'
Expand All @@ -121,20 +125,25 @@ def _handle_authentication_failure(self):
self._failed_login_count += 1

if self._failed_login_count == self.MAX_FAILED_LOGIN_COUNT:
msg = ('Authentication plugin failed %s consecutive times.'
' Disabling authentication plugin.')
args = (self._failed_login_count,)
self._log_debug(msg % args)
msg = ('The authentication plugin failed %s consecutive times to'
' get a valid application session using the user-provided'
' configuration settings. Disabling the `%s` authentication'
' plugin.')
args = (self._failed_login_count, self.get_name())
self._log_error(msg % args)

self._log_info_to_kb()

self._attempt_login = False

def end(self):
if self._failed_login_count:
msg = 'Authentication plugin failed %s times during the last minutes of the scan.'
args = (self._failed_login_count,)
self._log_debug(msg % args)
msg = ('The `%s` authentication plugin failed %i times to get'
' a valid application session using the user-provided'
' configuration settings')
args = (self.get_name(), self._failed_login_count,)

self._log_error(msg % args)

self._log_info_to_kb()

Expand Down Expand Up @@ -167,7 +176,6 @@ def _log_info_to_kb(self):

i.set_uri(self._get_main_authentication_url())

kb.kb.clear('authentication', 'error')
kb.kb.append('authentication', 'error', i)

def get_type(self):
Expand Down
115 changes: 106 additions & 9 deletions w3af/core/controllers/plugins/auth_session_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,50 @@
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
import w3af.core.data.kb.knowledge_base as kb

from w3af.core.controllers.plugins.auth_plugin import AuthPlugin
from w3af.core.data.misc.encoding import smart_str_ignore
from w3af.core.data.kb.info import Info


SESSIONS_FAILED_MSG = '''\
The authentication plugin identified that the user session was lost %i times
(%i%%) during the scan. Scan results might be inaccurate because some HTTP
requests were sent with an invalid application session.
The most common causes and solutions for this issue are:
* The crawler is following the "Logout" link and mistakenly invalidating the session.
To solve this issue please add the URL of the "Logout" link to the scanner or
`web_spider` blacklist.
* The application is closing the scanner's session when it detects uncommon actions.
Scans are detected when multiple HTTP 500 errors appear, by doing pattern matching
if the HTTP requests (WAF) or an elevated number of requests per second is sent.
To reduce the chances of being detected change the user agent, reduce the number
of threads and only enable a subset of audit and crawl plugins.
'''


class AuthSessionPlugin(AuthPlugin):
"""
Subclass AuthPlugin to define common methods which are used by plugins to
login, verify if the session is active and logout.
"""

MAX_INVALID_SESSION_PERCENT = 5.0
MIN_SESSION_COUNT_SAMPLES = 10

def __init__(self):
AuthPlugin.__init__(self)

# The auth consumer calls has_active_session() for the first time without
# first calling login(), that first time will always be a failure
self._invalid_sessions_count = -1.0
self._valid_sessions_count = 0.0
self._session_failed_http_request_ids = []

#
# These need to be overridden in sub-classes using configurable
# parameters to be defined by the user
Expand Down Expand Up @@ -60,22 +92,87 @@ def has_active_session(self, new_debugging_id=True):
except Exception, e:
msg = 'Failed to check if session is active because of exception: %s'
self._log_debug(msg % e)

self._handle_session_active_failure()

return False

self._log_http_response(http_response)
self._log_session_failed_http_response(http_response)

body = http_response.get_body()
logged_in = smart_str_ignore(self.check_string) in smart_str_ignore(body)

msg_yes = 'User "%s" is currently logged into the application'
msg_yes %= (self.username,)
if logged_in:
self._handle_session_active_success()

msg_no = ('User "%s" is NOT logged into the application, the'
' `check_string` was not found in the HTTP response'
' with ID %s.')
msg_no %= (self.username, http_response.id)
msg = 'User "%s" is currently logged into the application'
msg %= (self.username,)
self._log_debug(msg)

msg = msg_yes if logged_in else msg_no
self._log_debug(msg)
else:
self._handle_session_active_failure()

msg = ('User "%s" is NOT logged into the application, the'
' `check_string` was not found in the HTTP response'
' with ID %s.')
msg %= (self.username, http_response.id)
self._log_debug(msg)

return logged_in

def end(self):
super(AuthSessionPlugin, self).end()

if self._should_report_invalid_sessions():
self._report_invalid_sessions()

def _handle_session_active_failure(self):
self._invalid_sessions_count += 1

def _handle_session_active_success(self):
self._valid_sessions_count += 1

def _report_invalid_sessions(self):
args = (self._invalid_sessions_count,
self._get_invalid_session_perc())
desc = SESSIONS_FAILED_MSG % args

self._log_error(desc)

i = Info('Unstable application session',
desc,
self._session_failed_http_request_ids,
self.get_name())

i.set_uri(self._get_main_authentication_url())

kb.kb.append('authentication', 'error', i)

def _get_invalid_session_perc(self):
total_session_checks = self._valid_sessions_count + self._invalid_sessions_count

if total_session_checks == 0:
return 0.0

return (self._invalid_sessions_count / total_session_checks) * 100

def _should_report_invalid_sessions(self):
percent_failed_sessions = self._get_invalid_session_perc()

if not percent_failed_sessions >= self.MAX_INVALID_SESSION_PERCENT:
return False

total_session_checks = self._valid_sessions_count + self._invalid_sessions_count

if total_session_checks < self.MIN_SESSION_COUNT_SAMPLES:
return False

return True

def _log_session_failed_http_response(self, http_response):
response_saved = self._log_http_response(http_response)

if response_saved:
self._session_failed_http_request_ids.append(http_response.id)

return response_saved
2 changes: 1 addition & 1 deletion w3af/plugins/tests/auth/test_autocomplete.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ def test_handle_invalid_credentials(self):
'Login form sent to http://w3af.org/login_post.py in HTTP request ID 22\n'
'Checking if session for user user@mail.com is active\n'
'User "user@mail.com" is NOT logged into the application, the `check_string` was not found in the HTTP response with ID 23.\n'
'Authentication plugin failed 1 times during the last minutes of the scan.'
'The `autocomplete` authentication plugin failed 1 times to get a valid application session using the user-provided configuration settings\nThe `autocomplete` authentication plugin failed 1 times to get a valid application session using the user-provided configuration settings'
)

self.assertEqual(info.get_name(), 'Authentication failure')
Expand Down
2 changes: 1 addition & 1 deletion w3af/plugins/tests/auth/test_detailed.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ def test_failed_login_invalid_password(self):
'Logging into the application with user: user@mail.com\n'
'Checking if session for user user@mail.com is active\n'
'User "user@mail.com" is NOT logged into the application, the `check_string` was not found in the HTTP response with ID 24.\n'
'Authentication plugin failed 1 times during the last minutes of the scan.'
'The `detailed` authentication plugin failed 1 times to get a valid application session using the user-provided configuration settings\nThe `detailed` authentication plugin failed 1 times to get a valid application session using the user-provided configuration settings'
)

self.assertEqual(info.get_name(), 'Authentication failure')
Expand Down

0 comments on commit 2f0fd01

Please sign in to comment.