From 51ea59fac1ddf53591bf12faa65063123a328476 Mon Sep 17 00:00:00 2001 From: Yvonne Kim Date: Mon, 15 Oct 2018 22:44:47 -0500 Subject: [PATCH 1/4] Beginning work on private key grep plugin --- w3af/plugins/grep/private_keys.py | 98 +++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 w3af/plugins/grep/private_keys.py diff --git a/w3af/plugins/grep/private_keys.py b/w3af/plugins/grep/private_keys.py new file mode 100644 index 0000000000..7a187d27ec --- /dev/null +++ b/w3af/plugins/grep/private_keys.py @@ -0,0 +1,98 @@ +""" +private_keys.py + +Copyright 2006 Andres Riancho + +This file is part of w3af, http://w3af.org/ . + +w3af is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation version 2 of the License. + +w3af is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with w3af; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +""" +import re + +import w3af.core.data.constants.severity as severity + +from w3af.core.controllers.plugins.grep_plugin import GrepPlugin +from w3af.core.data.kb.vuln import Vuln + + +class private_keys(GrepPlugin): + """ + Grep every page for private keys. + + :author: Yvonne Kim + """ + def __init__(self): + GrepPlugin.__init__(self) + + key_regex = '-----BEGIN RSA PRIVATE KEY-----\n[^-]+\n-----END RSA PRIVATE KEY-----' + + self._key_regex = re.compile(key_regex, re.M) + + + def grep(self, request, response): + """ + Plugin entry point, find the error pages and report them. + + :param request: The HTTP request object. + :param response: The HTTP response object + :return: None + """ + if not response.is_text_or_html(): + return + + if not response.get_code() == 200: + return + + clear_text_body = response.get_clear_text_body() + + if clear_text_body is None: + return + + found_keys = self._find_keys(clear_text_body) + + for key in found_keys: + desc = u'The URL: "%s" discloses the private key: "%s"' + desc %= (response.get_url(), key) + + v = Vuln('private key disclosure', desc, + severity.LOW, response.id, self.get_name()) + + v.set_url(response.get_url()) + v.add_to_highlight(key) + + self.kb_append_uniq(self, 'private_keys', v, 'URL') + + def _find_keys(self, body): + """ + :return: A list of matching private keys + """ + res = [] + + match_list = self._key_regex.findall(body) + + for match in match_list: + res.append(match) + + return res + + def get_long_desc(self): + """ + :return: A DETAILED description of the plugin functions and features. + """ + + return """ + This plugin scans responses for private keys. + + """ From 47e4e02fd628f31a815406c6f09ab4e59893b8a9 Mon Sep 17 00:00:00 2001 From: Yvonne Kim Date: Fri, 19 Oct 2018 17:55:18 -0500 Subject: [PATCH 2/4] Add regexes and public/private classifying --- w3af/plugins/grep/keys.py | 124 ++++++++++++++++++++++++++++++ w3af/plugins/grep/private_keys.py | 98 ----------------------- 2 files changed, 124 insertions(+), 98 deletions(-) create mode 100644 w3af/plugins/grep/keys.py delete mode 100644 w3af/plugins/grep/private_keys.py diff --git a/w3af/plugins/grep/keys.py b/w3af/plugins/grep/keys.py new file mode 100644 index 0000000000..9e8497dd84 --- /dev/null +++ b/w3af/plugins/grep/keys.py @@ -0,0 +1,124 @@ +""" +keys.py + +Copyright 2006 Andres Riancho + +This file is part of w3af, http://w3af.org/ . + +w3af is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation version 2 of the License. + +w3af is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with w3af; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +""" +import re + +import w3af.core.data.constants.severity as severity + +from w3af.core.controllers.plugins.grep_plugin import GrepPlugin +from w3af.core.data.kb.vuln import Vuln + + +class private_keys(GrepPlugin): + """ + Grep every page for private keys. + + :author: Yvonne Kim + """ + def __init__(self): + GrepPlugin.__init__(self) + + self.PUBLIC = 'public' + self.PRIVATE = 'private' + + key_regex = ( + # RSA (PKCS1) + ('-----BEGIN RSA PRIVATE KEY-----', ('RSA-PRIVATE', PRIVATE)), + ('-----BEGIN RSA PUBLIC KEY-----', ('RSA-PUBLIC', PUBLIC)), + ('ssh-rsa', ('RSA-PUBLIC', PUBLIC)), + + # DSA + ('-----BEGIN DSA PRIVATE KEY-----', ('DSA-PRIVATE', PRIVATE)), + ('-----BEGIN DSA PUBLIC KEY-----', ('DSA-PUBLIC', PUBLIC)), + ('ssh-dss', ('DSA-PUBLIC', PUBLIC)), + + # Elliptic Curve + ('-----BEGIN EC PRIVATE KEY-----', ('EC-PRIVATE', PRIVATE)), + ('-----BEGIN EC PUBLIC KEY-----', ('EC-PUBLIC', PUBLIC)), + ('ecdsa-sha2-nistp256', ('EC-PUBLIC', PUBLIC)) + + # SSH2 + ('---- BEGIN SSH2 PUBLIC KEY ----', ('SSH2-PRIVATE', PRIVATE)), + ('---- BEGIN SSH2 PRIVATE KEY ----', ('SSH2-PUBLIC', PUBLIC)), + + # ed25519 (OpenSSH) + ('-----BEGIN OPENSSH PRIVATE KEY-----', ('ED25519-SSH-PRIVATE', PRIVATE)), + ('-----BEGIN OPENSSH PUBLIC KEY-----', ('ED25519-SSH-PUBLIC', PUBLIC)), + ('ssh-ed25519', ('ED25519-SSH-PUBLIC', PUBLIC)), + + # PKCS8 + ('-----BEGIN PRIVATE KEY-----', ('PKCS8-PRIVATE', PRIVATE)), + ('-----BEGIN PUBLIC KEY-----', ('PKCS8-PUBLIC', PUBLIC)), + ('-----BEGIN ENCRYPTED PRIVATE KEY-----', ('PKCS8-ENCRYPTED-PRIVATE', PRIVATE)), + ('-----BEGIN ENCRYPTED PUBLIC KEY-----', ('PKCS8-ENCRYPTED-PUBLIC', PUBLIC)), + + # XML + ('', ('XML-RSA', PRIVATE)), + ('', ('.NET-XML-RSA', PUBLIC)), + ) + + self._multi_re = MultiRE(key_regex) + + + def grep(self, request, response): + """ + Plugin entry point, find the error pages and report them. + + :param request: The HTTP request object. + :param response: The HTTP response object + :return: None + """ + if not response.is_text_or_html(): + return + + if not response.get_code() == 200: + return + + clear_text_body = response.get_clear_text_body() + + if clear_text_body is None: + return + + for _, _, _, (key, keypair_type) in self._multi_re.query(response.body): + desc = u'The URL: "%s" discloses a key of type: "%s"' + desc %= (response.get_url(), key) + + if keypair_type == self.PUBLIC: + item = Info('Public key disclosure', desc, response.id, self.get_name()) + + elif keypair_type == self.PRIVATE: + item = Vuln('Private key disclosure', desc, severity.HIGH, response.id, self.get_name()) + + item.set_url(response.get_url()) + item.add_to_highlight(key) + + self.kb_append_uniq(self, 'keys', item, 'URL') + + + def get_long_desc(self): + """ + :return: A DETAILED description of the plugin functions and features. + """ + + return """ + This plugin scans responses for keys in a few of the most common formats. Private keys are classified as vulnerabilities while public keys are information. + + """ diff --git a/w3af/plugins/grep/private_keys.py b/w3af/plugins/grep/private_keys.py deleted file mode 100644 index 7a187d27ec..0000000000 --- a/w3af/plugins/grep/private_keys.py +++ /dev/null @@ -1,98 +0,0 @@ -""" -private_keys.py - -Copyright 2006 Andres Riancho - -This file is part of w3af, http://w3af.org/ . - -w3af is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation version 2 of the License. - -w3af is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with w3af; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -""" -import re - -import w3af.core.data.constants.severity as severity - -from w3af.core.controllers.plugins.grep_plugin import GrepPlugin -from w3af.core.data.kb.vuln import Vuln - - -class private_keys(GrepPlugin): - """ - Grep every page for private keys. - - :author: Yvonne Kim - """ - def __init__(self): - GrepPlugin.__init__(self) - - key_regex = '-----BEGIN RSA PRIVATE KEY-----\n[^-]+\n-----END RSA PRIVATE KEY-----' - - self._key_regex = re.compile(key_regex, re.M) - - - def grep(self, request, response): - """ - Plugin entry point, find the error pages and report them. - - :param request: The HTTP request object. - :param response: The HTTP response object - :return: None - """ - if not response.is_text_or_html(): - return - - if not response.get_code() == 200: - return - - clear_text_body = response.get_clear_text_body() - - if clear_text_body is None: - return - - found_keys = self._find_keys(clear_text_body) - - for key in found_keys: - desc = u'The URL: "%s" discloses the private key: "%s"' - desc %= (response.get_url(), key) - - v = Vuln('private key disclosure', desc, - severity.LOW, response.id, self.get_name()) - - v.set_url(response.get_url()) - v.add_to_highlight(key) - - self.kb_append_uniq(self, 'private_keys', v, 'URL') - - def _find_keys(self, body): - """ - :return: A list of matching private keys - """ - res = [] - - match_list = self._key_regex.findall(body) - - for match in match_list: - res.append(match) - - return res - - def get_long_desc(self): - """ - :return: A DETAILED description of the plugin functions and features. - """ - - return """ - This plugin scans responses for private keys. - - """ From d3f73ee04b02aaaab4ed764747e27e7dfd26cbd6 Mon Sep 17 00:00:00 2001 From: Yvonne Kim Date: Sat, 20 Oct 2018 23:50:45 -0500 Subject: [PATCH 3/4] Add keys plugin unit tests --- w3af/plugins/grep/keys.py | 38 ++++----- w3af/plugins/tests/grep/test_keys.py | 116 +++++++++++++++++++++++++++ 2 files changed, 136 insertions(+), 18 deletions(-) create mode 100644 w3af/plugins/tests/grep/test_keys.py diff --git a/w3af/plugins/grep/keys.py b/w3af/plugins/grep/keys.py index 9e8497dd84..d20538a159 100644 --- a/w3af/plugins/grep/keys.py +++ b/w3af/plugins/grep/keys.py @@ -19,17 +19,17 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA """ -import re import w3af.core.data.constants.severity as severity - +from w3af.core.data.quick_match.multi_in import MultiIn from w3af.core.controllers.plugins.grep_plugin import GrepPlugin from w3af.core.data.kb.vuln import Vuln +from w3af.core.data.kb.info import Info -class private_keys(GrepPlugin): +class keys(GrepPlugin): """ - Grep every page for private keys. + Grep every page for public and private keys. :author: Yvonne Kim """ @@ -39,7 +39,10 @@ def __init__(self): self.PUBLIC = 'public' self.PRIVATE = 'private' - key_regex = ( + PUBLIC = 'public' + PRIVATE = 'private' + + KEY_FORMATS = ( # RSA (PKCS1) ('-----BEGIN RSA PRIVATE KEY-----', ('RSA-PRIVATE', PRIVATE)), ('-----BEGIN RSA PUBLIC KEY-----', ('RSA-PUBLIC', PUBLIC)), @@ -53,7 +56,7 @@ def __init__(self): # Elliptic Curve ('-----BEGIN EC PRIVATE KEY-----', ('EC-PRIVATE', PRIVATE)), ('-----BEGIN EC PUBLIC KEY-----', ('EC-PUBLIC', PUBLIC)), - ('ecdsa-sha2-nistp256', ('EC-PUBLIC', PUBLIC)) + ('ecdsa-sha2-nistp256', ('EC-PUBLIC', PUBLIC)), # SSH2 ('---- BEGIN SSH2 PUBLIC KEY ----', ('SSH2-PRIVATE', PRIVATE)), @@ -72,10 +75,10 @@ def __init__(self): # XML ('', ('XML-RSA', PRIVATE)), - ('', ('.NET-XML-RSA', PUBLIC)), + ('', ('.NET-XML-RSA', PUBLIC)) ) - self._multi_re = MultiRE(key_regex) + self._multi_in = MultiIn(KEY_FORMATS) def grep(self, request, response): @@ -92,25 +95,22 @@ def grep(self, request, response): if not response.get_code() == 200: return - clear_text_body = response.get_clear_text_body() - - if clear_text_body is None: - return - - for _, _, _, (key, keypair_type) in self._multi_re.query(response.body): + for _, (key, keypair_type) in self._multi_in.query(response.body): desc = u'The URL: "%s" discloses a key of type: "%s"' desc %= (response.get_url(), key) if keypair_type == self.PUBLIC: - item = Info('Public key disclosure', desc, response.id, self.get_name()) + item = Info( + 'Public key disclosure', desc, response.id, self.get_name()) elif keypair_type == self.PRIVATE: - item = Vuln('Private key disclosure', desc, severity.HIGH, response.id, self.get_name()) + item = Vuln( + 'Private key disclosure', desc, severity.HIGH, response.id, self.get_name()) item.set_url(response.get_url()) item.add_to_highlight(key) - self.kb_append_uniq(self, 'keys', item, 'URL') + self.kb_append(self, 'keys', item) def get_long_desc(self): @@ -119,6 +119,8 @@ def get_long_desc(self): """ return """ - This plugin scans responses for keys in a few of the most common formats. Private keys are classified as vulnerabilities while public keys are information. + This plugin scans responses for keys in a few of the most common formats. + Private keys are classified as vulnerabilities while public keys are stored + as information. """ diff --git a/w3af/plugins/tests/grep/test_keys.py b/w3af/plugins/tests/grep/test_keys.py new file mode 100644 index 0000000000..8f73046692 --- /dev/null +++ b/w3af/plugins/tests/grep/test_keys.py @@ -0,0 +1,116 @@ +""" +test_keys.py + +Copyright 2011 Andres Riancho + +This file is part of w3af, http://w3af.org/ . + +w3af is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation version 2 of the License. + +w3af is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with w3af; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +""" +import os +import unittest + +import w3af.core.data.kb.knowledge_base as kb + +from w3af.plugins.grep.keys import keys +from w3af.core.data.dc.headers import Headers +from w3af.core.data.url.HTTPResponse import HTTPResponse +from w3af.core.data.request.fuzzable_request import FuzzableRequest +from w3af.core.data.parsers.doc.url import URL +from w3af.core.data.kb.vuln import Vuln +from w3af.core.data.kb.info import Info +from w3af.plugins.tests.helper import PluginTest + + +class TestKeys(PluginTest): + + def setUp(self): + self.plugin = keys() + kb.kb.clear('keys', 'keys') + + def tearDown(self): + self.plugin.end() + + def test_private_key(self): + body = '-----BEGIN PRIVATE KEY-----' + url = URL('http://www.w3af.com/') + headers = Headers([('content-type', 'text/html')]) + response = HTTPResponse(200, body, headers, url, url, _id=1) + request = FuzzableRequest(url, method='GET') + self.plugin.grep(request, response) + + data = kb.kb.get('keys', 'keys') + self.assertEquals(len(data), 1) + self.assertEquals(type(data[0]), Vuln) + + def test_public_key(self): + body = '-----BEGIN PUBLIC KEY-----' + url = URL('http://www.w3af.com/') + headers = Headers([('content-type', 'text/html')]) + response = HTTPResponse(200, body, headers, url, url, _id=1) + request = FuzzableRequest(url, method='GET') + self.plugin.grep(request, response) + + data = kb.kb.get('keys', 'keys') + self.assertEquals(len(data), 1) + self.assertEquals(type(data[0]), Info) + + def test_xml_key(self): + body = '' + url = URL('http://www.w3af.com/') + headers = Headers([('content-type', 'text/html')]) + response = HTTPResponse(200, body, headers, url, url, _id=1) + request = FuzzableRequest(url, method='GET') + self.plugin.grep(request, response) + + data = kb.kb.get('keys', 'keys') + self.assertEquals(len(data), 1) + + def test_public_ecdsa_key(self): + body = 'ecdsa-sha2-nistp256' + url = URL('http://www.w3af.com/') + headers = Headers([('content-type', 'text/html')]) + response = HTTPResponse(200, body, headers, url, url, _id=1) + request = FuzzableRequest(url, method='GET') + self.plugin.grep(request, response) + + data = kb.kb.get('keys', 'keys') + self.assertEquals(len(data), 1) + self.assertEquals(type(data[0]), Info) + + def test_multi_match(self): + body = """ + -----BEGIN OPENSSH PRIVATE KEY----- ssh-ed25519 + ------------------------------test + """ + url = URL('http://www.w3af.com/') + headers = Headers([('content-type', 'text/html')]) + response = HTTPResponse(200, body, headers, url, url, _id=1) + request = FuzzableRequest(url, method='GET') + self.plugin.grep(request, response) + + data = kb.kb.get('keys', 'keys') + self.assertEquals(len(data), 3) + + def test_no_match(self): + body = '-----BEGIN-----ssh----- BEGIN PRIVATE PUBLIC KEY' + url = URL('http://www.w3af.com/') + headers = Headers([('content-type', 'text/html')]) + response = HTTPResponse(200, body, headers, url, url, _id=1) + request = FuzzableRequest(url, method='GET') + self.plugin.grep(request, response) + + data = kb.kb.get('keys', 'keys') + self.assertEquals(len(data), 0) From 679e94ad29b91051118300a5e5179cd669dff561 Mon Sep 17 00:00:00 2001 From: Yvonne Kim Date: Tue, 23 Oct 2018 19:01:35 -0500 Subject: [PATCH 4/4] Add options in OPTION_TYPES for passing tests, update open_api crawl plugin --- w3af/plugins/crawl/open_api.py | 4 ++-- w3af/plugins/tests/test_basic.py | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/w3af/plugins/crawl/open_api.py b/w3af/plugins/crawl/open_api.py index 85748789e7..4140d27131 100644 --- a/w3af/plugins/crawl/open_api.py +++ b/w3af/plugins/crawl/open_api.py @@ -392,8 +392,8 @@ def get_options(self): ol.add(o) d = 'Path to Open API specification' - h = ('By default, the plugin looks for the API specification on the target,', - ' but sometimes applications do not provide an API specification.', + h = ('By default, the plugin looks for the API specification on the target,' + ' but sometimes applications do not provide an API specification.' ' Set this parameter to specify a local path to the API specification.' ' The file must have .json or .yaml extension.') o = opt_factory('custom_spec_location', self._custom_spec_location, d, INPUT_FILE, help=h) diff --git a/w3af/plugins/tests/test_basic.py b/w3af/plugins/tests/test_basic.py index be1db6fd81..fde74873d9 100644 --- a/w3af/plugins/tests/test_basic.py +++ b/w3af/plugins/tests/test_basic.py @@ -38,7 +38,8 @@ from w3af.core.data.options.option_types import ( BOOL, INT, FLOAT, STRING, URL, IPPORT, LIST, - REGEX, COMBO, INPUT_FILE, OUTPUT_FILE, PORT, IP) + REGEX, COMBO, INPUT_FILE, OUTPUT_FILE, PORT, IP, + QUERY_STRING, HEADER) from w3af.plugins.tests.helper import PluginTest, PluginConfig @@ -75,7 +76,7 @@ def test_plugin_options(self): OPTION_TYPES = ( BOOL, INT, FLOAT, STRING, URL, IPPORT, LIST, REGEX, COMBO, - INPUT_FILE, OUTPUT_FILE, PORT, IP) + INPUT_FILE, OUTPUT_FILE, PORT, IP, QUERY_STRING, HEADER) for plugin_type in self.plugins: for plugin in self.plugins[plugin_type]: