Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

autocomplete_js improvements #18630

Closed
wants to merge 33 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
bf12c08
ignore __pychace__/ and another script file pattern (for tests)
Q-back May 4, 2020
fa7f743
automatically marked slow and deprecated tests, added pytest.ini
Q-back May 8, 2020
3c26cf1
added pytest to tests requirements
Q-back May 8, 2020
50cb5a5
brought back FormInputSubmitStrategy, better alghoritm of discovering…
Q-back May 12, 2020
13a5d0d
fix _handle_authentication_success breaking parent's functionality in…
Q-back May 12, 2020
e04104f
revert enabling FormInputSubmitStrategy from pre-previous commit
Q-back May 12, 2020
389646d
new options in autocomplete_js to manually provide username/submit bu…
Q-back May 13, 2020
52b8f39
don't create new chrome instance when running autocomplete_js.has_act…
Q-back May 13, 2020
2192ca5
fix after rebase
Q-back May 13, 2020
1723b6b
reloading chrome when checking active session may break the session
Q-back May 21, 2020
b81b71d
implemented _login_using_existing_form
Q-back May 21, 2020
d4b6531
fix iterate error in frame manager, don't kill chrome in autocomplete…
Q-back Jun 1, 2020
25a1125
better description for new params in autocomplete_js
Q-back Jun 3, 2020
888eea6
sometimes login button doesn't contain 'log' characters in it's text,…
Q-back Jun 3, 2020
27ede04
add option to click on element before autocompleting form
Q-back Jun 4, 2020
b76cf07
deleted conftest.py from root directory
Q-back Jun 5, 2020
c68a45a
remove unused pytest imports
Q-back Jun 8, 2020
f92a9ae
marked other failing tests
Q-back Jun 8, 2020
04ec9bc
added tests to docs
Q-back Jun 8, 2020
5854763
fix error when user provides CSS selectors with quotes. Slightly bett…
Q-back Jun 9, 2020
0b8b3f0
fix UnicodeDecodeError when parsing openapi spec
Q-back Jun 10, 2020
2e88cc2
fix request error when not required array param in open_api spec
Q-back Jun 10, 2020
8d520bc
fix error when empty list was returned by querySelectorAll to Instrum…
Q-back Jun 10, 2020
8cbbfa9
Merge branch 'fix/tests' into holm-master
Q-back Jun 15, 2020
ad54e10
Merge branch 'upstream-develop' into holm-master
Q-back Jun 18, 2020
fddafa2
Merge remote-tracking branch 'upstream/feature/improve-autocomplete_j…
Q-back Jun 23, 2020
ea9e923
fix typo in pytestmark
Q-back Jun 19, 2020
5e49d49
function-based plugin runner
Q-back Jun 19, 2020
9eb78c7
test runner class-based, prepared css selector test
Q-back Jun 22, 2020
d5a54f7
mocking network in plugin_runner, autocomplete_js reports CSS selecto…
Q-back Jun 24, 2020
82710b5
cleanup plugin testing code
Q-back Jun 24, 2020
470ba5e
Few comments about the code
Q-back Jun 25, 2020
766a16b
create new kb instance every time kb fixture is used
Q-back Jun 25, 2020
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
8 changes: 8 additions & 0 deletions w3af/core/controllers/chrome/devtools/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,11 @@ class ChromeInterfaceException(Exception):

class ChromeInterfaceTimeout(Exception):
pass


class ChromeScriptRuntimeException(Exception):
def __init__(self, message, function_called=None, *args):
if function_called:
message = "function: {}, exception: {}".format(function_called, message)
super(ChromeScriptRuntimeException, self).__init__(message, *args)
pass
2 changes: 1 addition & 1 deletion w3af/core/controllers/chrome/instrumented/frame_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ def _on_frame_navigated(self, message):
# URL all the child frames are removed from Chrome, we should remove
# them from our code too to mirror state
if frame:
for child_frame_id, child_frame in frame.child_frames:
for child_frame_id, child_frame in frame.child_frames.items():
child_frame.detach(self)

frame.set_navigated()
Expand Down
51 changes: 32 additions & 19 deletions w3af/core/controllers/chrome/instrumented/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import json

import w3af.core.controllers.output_manager as om
from w3af.core.controllers.chrome.devtools.exceptions import ChromeScriptRuntimeException

from w3af.core.data.parsers.doc.url import URL
from w3af.core.controllers.chrome.instrumented.instrumented_base import InstrumentedChromeBase
Expand Down Expand Up @@ -297,11 +298,20 @@ def dispatch_js_event(self, selector, event_type):

return True

def get_login_forms(self):
def get_login_forms(self, exact_css_selectors):
"""
:param dict exact_css_selectors: Optional parameter containing css selectors
for part of form like username input or login button.
:return: Yield LoginForm instances
"""
result = self.js_runtime_evaluate('window._DOMAnalyzer.getLoginForms()')
func = (
'window._DOMAnalyzer.getLoginForms("{}", "{}")'
)
func = func.format(
exact_css_selectors.get('username_input', '').replace('"', '\\"'),
exact_css_selectors.get('login_button', '').replace('"', '\\"'),
)
result = self.js_runtime_evaluate(func)

if result is None:
raise EventTimeout('The event execution timed out')
Expand All @@ -316,11 +326,20 @@ def get_login_forms(self):

yield login_form

def get_login_forms_without_form_tags(self):
def get_login_forms_without_form_tags(self, exact_css_selectors):
"""
:param dict exact_css_selectors: Optional parameter containing css selectors
for part of form like username input or login button.
:return: Yield LoginForm instances
"""
result = self.js_runtime_evaluate('window._DOMAnalyzer.getLoginFormsWithoutFormTags()')
func = (
'window._DOMAnalyzer.getLoginFormsWithoutFormTags("{}", "{}")'
)
func = func.format(
exact_css_selectors.get('username_input', '').replace('"', '\\"'),
exact_css_selectors.get('login_button', '').replace('"', '\\"'),
)
result = self.js_runtime_evaluate(func)

if result is None:
raise EventTimeout('The event execution timed out')
Expand Down Expand Up @@ -406,9 +425,9 @@ def focus(self, selector):
if result is None:
return None

node_ids = result.get('result', {}).get('nodeIds', None)
node_ids = result.get('result', {}).get('nodeIds')

if node_ids is None:
if not node_ids:
msg = ('The call to chrome.focus() failed.'
' CSS selector "%s" returned no nodes (did: %s)')
args = (selector, self.debugging_id)
Expand Down Expand Up @@ -589,19 +608,13 @@ def js_runtime_evaluate(self, expression, timeout=5):
timeout=timeout)

# This is a rare case where the DOM is not present
if result is None:
return None

if 'result' not in result:
return None

if 'result' not in result['result']:
return None

if 'value' not in result['result']['result']:
return None

return result['result']['result']['value']
runtime_exception = result.get('result', {}).get('exceptionDetails')
if runtime_exception:
raise ChromeScriptRuntimeException(
runtime_exception,
function_called=expression
)
return result.get('result', {}).get('result', {}).get('value', None)

def get_js_variable_value(self, variable_name):
"""
Expand Down
72 changes: 64 additions & 8 deletions w3af/core/controllers/chrome/js/dom_analyzer.js
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ var _DOMAnalyzer = _DOMAnalyzer || {
if( !_DOMAnalyzer.eventIsValidForTagName( tag_name, type ) ) return false;

let selector = OptimalSelect.getSingleSelector(element);

// node_type is https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType#Node_type_constants
_DOMAnalyzer.event_listeners.push({"tag_name": tag_name,
"node_type": element.nodeType,
Expand Down Expand Up @@ -865,6 +865,48 @@ var _DOMAnalyzer = _DOMAnalyzer || {
return false;
},

/**
* This is naive function which takes parentElement (the login form) and
* tries to find username input field within it.
* @param {Node} parentElement - parent element to scope to document.querySelectorAll()
* @param {String} exactSelector - optional CSS selector. If provided prevents
* using standard selectors
* @returns {NodeList} - result of querySelectorAll()
*/
_getUsernameInput(parentElement, exactSelector = '') {
if (exactSelector) {
return document.querySelectorAll(exactSelector, parentElement);
}
result = document.querySelectorAll("input[type='email']", parentElement);
if (!result.length) {
result = document.querySelectorAll("input[type='text']", parentElement);
}
return result;
},

/**
* This is naive function which takes parentElement (the login form) and tries
* to find submit button within it.
* @param {Node} parentElement - parent element to scope to document.querySelectorAll()
* @param {String} exactSelector - optional CSS selector. If provided prevents
* using standard selectors
* @returns {NodeList} - result of querySelectorAll()
*/
_getSubmitButton(parentElement, exactSelector = '') {
if (exactSelector) {
return document.querySelectorAll(exactSelector, parentElement);
}
result = document.querySelectorAll("input[type='submit']", parentElement);
if (!result.length) {
result = document.querySelectorAll("button[type='submit']", parentElement);
}
// Maybe it's just normal button without type="submit"...
if (!result.length) {
result = document.querySelectorAll('button', parentElement);
}
return result;
},

/**
* Return the CSS selector for the login forms which exist in the DOM.
*
Expand All @@ -874,8 +916,12 @@ var _DOMAnalyzer = _DOMAnalyzer || {
* - <input type=text>, and
* - <input type=password>
*
* @param {String} usernameCssSelector - CSS selector for username input. If
* provided we won't try to find username input automatically.
* @param {String} submitButtonCssSelector - CSS selector for submit button. If
* provided we won't try to find submit button autmatically.
*/
getLoginForms: function () {
getLoginForms: function (usernameCssSelector = '', submitButtonCssSelector = '') {
let login_forms = [];

// First we identify the forms with a password field using a descendant Selector
Expand All @@ -898,15 +944,15 @@ var _DOMAnalyzer = _DOMAnalyzer || {
let form = forms[0];

// Finally we confirm that the form has a type=text input
let text_fields = document.querySelectorAll("input[type='text']", form)
let text_fields = this._getUsernameInput(form, usernameCssSelector);

// Zero text fields is most likely a password-only login form
// Two text fields or more is most likely a registration from
// One text field is 99% of login forms
if (text_fields.length !== 1) continue;

// And if there is a submit button I want that selector too
let submit_fields = document.querySelectorAll("input[type='submit']", form)
let submit_fields = this._getSubmitButton(form, submitButtonCssSelector);
let submit_selector = null;

if (submit_fields.length !== 0) {
Expand Down Expand Up @@ -936,8 +982,12 @@ var _DOMAnalyzer = _DOMAnalyzer || {
* - <input type=text>, and
* - <input type=password>
*
* @param {String} usernameCssSelector - CSS selector for username input. If
* provided we won't try to find username input automatically.
* @param {String} submitButtonCssSelector - CSS selector for submit button. If
* provided we won't try to find submit button autmatically.
*/
getLoginFormsWithoutFormTags: function () {
getLoginFormsWithoutFormTags: function (usernameCssSelector = '', submitButtonCssSelector = '') {
let login_forms = [];

// First we identify the password fields
Expand All @@ -962,7 +1012,7 @@ var _DOMAnalyzer = _DOMAnalyzer || {
// go up one more level, and so one.
//
// Find if this parent has a type=text input
let text_fields = document.querySelectorAll("input[type='text']", parent)
let text_fields = this._getUsernameInput(parent, usernameCssSelector);

// Zero text fields is most likely a password-only login form
// Two text fields or more is most likely a registration from
Expand All @@ -974,7 +1024,7 @@ var _DOMAnalyzer = _DOMAnalyzer || {
}

// And if there is a submit button I want that selector too
let submit_fields = document.querySelectorAll("input[type='submit']", parent)
let submit_fields = this._getSubmitButton(parent, submitButtonCssSelector)
let submit_selector = null;

if (submit_fields.length !== 0) {
Expand All @@ -999,6 +1049,12 @@ var _DOMAnalyzer = _DOMAnalyzer || {
return JSON.stringify(login_forms);
},

clickOnSelector(exactSelector) {
let element = document.querySelector(exactSelector);
element.click();
return 'success'
},

sliceAndSerialize: function (filtered_event_listeners, start, count) {
return JSON.stringify(filtered_event_listeners.slice(start, start + count));
},
Expand Down Expand Up @@ -1142,4 +1198,4 @@ var _DOMAnalyzer = _DOMAnalyzer || {

};

_DOMAnalyzer.initialize();
_DOMAnalyzer.initialize();
14 changes: 11 additions & 3 deletions w3af/core/controllers/chrome/login/find_form/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,24 @@ def __init__(self, chrome, debugging_id):
self.chrome = chrome
self.debugging_id = debugging_id

def find_forms(self):
def find_forms(self, css_selectors=None):
"""
:param dict css_selectors: optional dict of css selectors used to find
elements of form (like username input or login button)
:return: Yield forms as they are found by each strategy
"""
if css_selectors:
msg = 'Form finder uses the CSS selectors: "%s" (did: %s)'
args = (css_selectors, self.debugging_id)
om.out.debug(msg % args)

identified_forms = []

for strategy_klass in self.STRATEGIES:
strategy = strategy_klass(self.chrome, self.debugging_id)
strategy = strategy_klass(self.chrome, self.debugging_id, css_selectors)

try:
strategy.prepare()
for form in strategy.find_forms():
if form in identified_forms:
continue
Expand All @@ -55,6 +63,6 @@ def find_forms(self):
except Exception as e:
msg = 'Form finder strategy %s raised exception: "%s" (did: %s)'
args = (strategy.get_name(),
e,
repr(e),
self.debugging_id)
om.out.debug(msg % args)
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from w3af.core.controllers.chrome.instrumented.exceptions import EventTimeout


class BaseFindFormStrategy:
def __init__(self, chrome, debugging_id, exact_css_selectors=None):
"""
:param InstrumentedChrome chrome:
:param String debugging_id:
:param dict exact_css_selectors: Optional parameter containing css selectors
for part of form like username input or login button.
"""
self.chrome = chrome
self.debugging_id = debugging_id
self.exact_css_selectors = exact_css_selectors or {}

def prepare(self):
"""
:raises EventTimeout:
Hook called before find_forms()
"""
form_activator_selector = self.exact_css_selectors.get('form_activator')
if form_activator_selector:
func = 'window._DOMAnalyzer.clickOnSelector("{}")'.format(
form_activator_selector.replace('"', '\\"')
)
result = self.chrome.js_runtime_evaluate(func)
if result is None:
raise EventTimeout('The event execution timed out')

def find_forms(self):
raise NotImplementedError

@staticmethod
def get_name():
return 'BaseFindFormStrategy'
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,11 @@
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA

"""
from w3af.core.controllers.chrome.login.find_form.strategies.base_find_form_strategy import \
BaseFindFormStrategy


class FormTagStrategy(object):
def __init__(self, chrome, debugging_id):
self.chrome = chrome
self.debugging_id = debugging_id
class FormTagStrategy(BaseFindFormStrategy):

def find_forms(self):
"""
Expand All @@ -37,7 +36,7 @@ def _simple_form_with_username_password_submit(self):
"""
:return: Yield forms that have username, password and submit inputs
"""
for login_form in self.chrome.get_login_forms():
for login_form in self.chrome.get_login_forms(self.exact_css_selectors):
yield login_form

@staticmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,21 @@
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA

"""
from w3af.core.controllers.chrome.login.find_form.strategies.base_find_form_strategy import \
BaseFindFormStrategy


class PasswordAndParentStrategy(object):
def __init__(self, chrome, debugging_id):
self.chrome = chrome
self.debugging_id = debugging_id
class PasswordAndParentStrategy(BaseFindFormStrategy):

def find_forms(self):
"""
The algorithm is implemented in dom_analyzer.js

:return: Yield forms which are identified by the strategy algorithm
"""
for login_form in self.chrome.get_login_forms_without_form_tags():
for login_form in self.chrome.get_login_forms_without_form_tags(self.exact_css_selectors):
yield login_form

def get_name(self):
@staticmethod
def get_name():
return 'PasswordAndParent'
Loading