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

Feature/improve autocomplete js #18552

Open
wants to merge 14 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
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
26 changes: 22 additions & 4 deletions w3af/core/controllers/chrome/instrumented/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,11 +297,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', ''),
exact_css_selectors.get('login_button', ''),
)
result = self.js_runtime_evaluate(func)

if result is None:
raise EventTimeout('The event execution timed out')
Expand All @@ -316,11 +325,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', ''),
exact_css_selectors.get('login_button', ''),
)
result = self.js_runtime_evaluate(func)

if result is None:
raise EventTimeout('The event execution timed out')
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,54 @@ 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 with innerText: 'Login'...
if (!result.length) {
result = [];
let buttons = document.querySelectorAll('button', parentElement);
for (let button of buttons) {
if (button.innerText.toLocaleLowerCase().includes('log')) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I usually dislike code that is tied to a specific language. log might work for login and logga (which I believe is login in .se) but... for sites developed in other languages (spanish, polish, etc.) this will not work.

Also, sign-in will not be found.

Can we find an alternative?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only thing that comes into my mind is to return all buttons located inside parentNode. Hopefully there will be login button.

result.push(button);
}
}
}
return result;
},

/**
* Return the CSS selector for the login forms which exist in the DOM.
*
Expand All @@ -874,8 +922,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 +950,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 +988,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 +1018,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 +1030,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 Down Expand Up @@ -1142,4 +1198,4 @@ var _DOMAnalyzer = _DOMAnalyzer || {

};

_DOMAnalyzer.initialize();
_DOMAnalyzer.initialize();
11 changes: 9 additions & 2 deletions w3af/core/controllers/chrome/login/find_form/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,21 @@ 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:
for form in strategy.find_forms():
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
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 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'
2 changes: 1 addition & 1 deletion w3af/core/controllers/chrome/login/submit_form/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class FormSubmitter(object):
STRATEGIES = [
PressEnterStrategy,
PressTabEnterStrategy,
#FormInputSubmitStrategy
# FormInputSubmitStrategy
]

def __init__(self, chrome, form, login_form_url, username, password, debugging_id):
Expand Down
12 changes: 12 additions & 0 deletions w3af/core/data/options/option_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,18 @@ def add(self, option):
self._internal_opt_list.append(option)
append = add

def pop(self, option):
"""
DANGEROUS!!
You will probably want to deepcopy the OptionList instance before
modifying it with this method to don't block the user from accessing options
again.
"""
if not isinstance(option, int):
option_names = [item.get_name() for item in self._internal_opt_list]
option = option_names.index(option)
return self._internal_opt_list.pop(option)

def __len__(self):
return len(self._internal_opt_list)

Expand Down
Loading