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

contribution at 14-07-2020 #18645

Open
wants to merge 46 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 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
b517f04
Merge branch 'feature/improve-report-autocomplete_js' into 'holm-master'
Jun 25, 2020
69ab174
SOAP plugin
Jul 14, 2020
6de1323
Merge branch 'feature/soap-plugin' into 'holm-master'
Jul 14, 2020
3dc8fc3
added Zeep to requirements
Q-back Jul 20, 2020
7f9f578
refactored soap plugin to wsdl_parser
Q-back Aug 14, 2020
6a6e5be
fixed DocumentParser process hanging for too long, fixed potential pi…
Q-back Aug 26, 2020
e9b5713
improve NetworkPatcher() context manager, added @patch_network decorator
Q-back Sep 17, 2020
fafed41
skip tests using internet
Q-back Sep 21, 2020
a81845f
refactored wsdl to force zeep using w3af http_client, extended_urllib…
Q-back Sep 30, 2020
2d12caa
fixed test_cache
Q-back Oct 1, 2020
c3e0eec
submit strategy error will be saved to debug logs instead of error lo…
Q-back Oct 5, 2020
1a063e2
ZeepTransport will save requests it performs, added get_fuzzable_requ…
Q-back Oct 5, 2020
48b1545
report possible fuzzable requests from document parsers
Q-back Oct 6, 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
30 changes: 30 additions & 0 deletions w3af/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import pytest

from w3af.core.data.dc.headers import Headers
from w3af.core.data.parsers.doc.url import URL
from w3af.core.data.url.HTTPRequest import HTTPRequest
from w3af.core.data.url.HTTPResponse import HTTPResponse


@pytest.fixture
def http_response():
url = URL('http://example.com/')
headers = Headers([('content-type', 'text/html')])
return HTTPResponse(
200,
'<body></body>',
headers,
url,
url,
)


@pytest.fixture
def http_request():
url = URL('http://example.com/')
headers = Headers([('content-type', 'text/html')])
return HTTPRequest(
url,
headers,
method='GET',
)
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from w3af.core.controllers.chrome.tests.helpers import ExtendedHttpRequestHandler


@pytest.mark.skip('uses internet')
class AngularBasicTest(BaseChromeCrawlerTest):
def test_angular_click(self):
self._unittest_setup(AngularButtonClickRequestHandler)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from w3af.core.controllers.chrome.crawler.tests.base import BaseChromeCrawlerTest


@pytest.mark.skip('uses internet')
class ReactBasicTest(BaseChromeCrawlerTest):
def test_react_hello_world_app(self):
url = 'http://react-hello-world-app.surge.sh/'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@
along with w3af; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
import pytest

from w3af.core.controllers.chrome.crawler.tests.base import BaseChromeCrawlerTest


class ReactBasicTest(BaseChromeCrawlerTest):
@pytest.mark.skip('uses internet')
def test_vue_todo_list(self):
url = 'http://vue-todo-test.surge.sh'
found_uris = self._crawl(url)
Expand Down
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'
Loading