"""
find_vhosts.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
"""
from itertools import izip, repeat
import w3af.core.controllers.output_manager as om
import w3af.core.data.parsers.parser_cache as parser_cache
import w3af.core.data.kb.knowledge_base as kb
import w3af.core.data.constants.severity as severity
from w3af.core.controllers.plugins.infrastructure_plugin import InfrastructurePlugin
from w3af.core.controllers.misc.fuzzy_string_cmp import fuzzy_equal
from w3af.core.controllers.exceptions import BaseFrameworkException
from w3af.core.controllers.threads.threadpool import return_args, one_to_many
from w3af.core.controllers.misc.is_ip_address import is_ip_address
from w3af.core.controllers.misc.is_private_site import is_private_site
from w3af.core.data.url.helpers import is_no_content_response
from w3af.core.data.fuzzer.utils import rand_alnum
from w3af.core.data.bloomfilter.scalable_bloom import ScalableBloomFilter
from w3af.core.data.dc.headers import Headers
from w3af.core.data.kb.vuln import Vuln
from w3af.core.data.kb.info import Info
class find_vhosts(InfrastructurePlugin):
"""
Modify the HTTP Host header and try to find virtual hosts.
:author: Andres Riancho ([email protected])
"""
COMMON_VHOSTS = ['intranet', 'intra', 'extranet', 'extra', 'test',
'test1', 'old', 'new', 'admin', 'adm', 'webmail',
'services', 'console', 'apps', 'mail', 'corporate',
'ws', 'webservice', 'private', 'secure', 'safe',
'hidden', 'public']
def __init__(self):
InfrastructurePlugin.__init__(self)
# Internal variables
self._first_exec = True
self._already_queried_dns = ScalableBloomFilter()
def discover(self, fuzzable_request, debugging_id):
"""
Find virtual hosts.
:param fuzzable_request: A fuzzable_request instance that contains
(among other things) the URL to test.
"""
if self._first_exec:
self._first_exec = False
self._check_potential_vhosts(fuzzable_request,
self._get_common_virtual_hosts(fuzzable_request))
# Also test for ""dead links"" that the web developer left in the
# page. For example, if w3af finds a link to:
#
# "http://corp.intranet.com/"
#
# It will try to resolve the DNS name, if it fails, it will try
# to request that page from the server
self._check_potential_vhosts(fuzzable_request,
self._get_dead_domains(fuzzable_request))
def _get_dead_domains(self, fuzzable_request):
"""
Find every link on a HTML document verify if the domain can be resolved
:return: Yield domains that can not be resolved or resolve to a private
IP address
"""
original_response = self._uri_opener.GET(fuzzable_request.get_uri(), cache=True)
try:
dp = parser_cache.dpc.get_document_parser_for(original_response)
except BaseFrameworkException:
# Failed to find a suitable parser for the document
return
#
# Note:
#
# - With parsed_references I'm 100% that it's really something in the
# HTML that the developer intended to add.
#
# - The re_references are the result of regular expressions, which in
# some cases are just false positives.
#
# In this case, and because I'm only going to use the domain name of the
# URL I'm going to trust the re_references also.
#
parsed_references, re_references = dp.get_references()
parsed_references.extend(re_references)
for link in parsed_references:
domain = link.get_domain()
if domain in self._already_queried_dns:
continue
self._already_queried_dns.add(domain)
if not is_private_site(domain):
continue
desc = (u'The content of "%s" references a non existent domain: "%s".'
u' This can be a broken link, or an internal domain name.')
desc %= (fuzzable_request.get_url(), domain)
i = Info(u'Internal hostname in HTML link', desc,
original_response.id, self.get_name())
i.set_url(fuzzable_request.get_url())
kb.kb.append(self, 'find_vhosts', i)
om.out.information(i.get_desc())
yield domain
def _check_potential_vhosts(self, fuzzable_request, vhosts):
"""
Send the HTTP requests to check for potential findings
:param fuzzable_request: The fuzzable request as received by the plugin
:param vhosts: A generator yielding potential vhosts to check
:return: None, vulnerabilities (if any) are written to the KB
"""
# Get some responses to compare later
base_url = fuzzable_request.get_url().base_url()
original_response = self._uri_opener.GET(base_url, cache=True)
orig_resp_body = original_response.get_body()
non_existent_responses = self._get_non_exist(fuzzable_request)
for vhost, vhost_response in self._send_in_threads(base_url, vhosts):
if not self._response_is_different(vhost_response, orig_resp_body, non_existent_responses):
continue
domain = fuzzable_request.get_url().get_domain()
desc = (u'Found a new virtual host at the target web server, the'
u' virtual host name is: "%s". To access this site'
u' you might need to change your DNS resolution settings'
u' in order to point "%s" to the IP address of "%s".')
desc %= (vhost, vhost, domain)
ids = [vhost_response.id, original_response.id]
ids.extend([r.id for r in non_existent_responses])
v = Vuln.from_fr('Virtual host identified', desc, severity.LOW,
ids, self.get_name(), fuzzable_request)
kb.kb.append(self, 'find_vhosts', v)
om.out.information(v.get_desc())
def _response_is_different(self, vhost_response, orig_resp_body, non_existent_responses):
"""
Note that we use 0.35 in fuzzy_equal because we want the responses to be
*really different*.
:param vhost_response: The HTTP response body for the virtual host
:param orig_resp_body: The original HTTP response body
:param non_existent_responses: One or more HTTP responses for virtual hosts
that do not exist in the remote server
:return: True if vhost_response is different from orig_resp_body and non_existent_responses
"""
if is_no_content_response(vhost_response):
return False
if fuzzy_equal(vhost_response.get_body(), orig_resp_body, 0.35):
return False
for ner in non_existent_responses:
if fuzzy_equal(vhost_response.get_body(), ner.get_body(), 0.35):
return False
return True
def _send_in_threads(self, base_url, vhosts):
base_url_repeater = repeat(base_url)
args_iterator = izip(base_url_repeater, vhosts)
http_get = return_args(one_to_many(self._http_get_vhost))
pool_results = self.worker_pool.imap_unordered(http_get, args_iterator)
for ((base_url, vhost),), vhost_response in pool_results:
yield vhost, vhost_response
def _http_get_vhost(self, base_url, vhost):
"""
Performs an HTTP GET to a URL using a specific vhost.
:return: HTTPResponse object.
"""
headers = Headers([('Host', vhost)])
return self._uri_opener.GET(base_url, cache=False, headers=headers)
def _get_non_exist(self, fuzzable_request):
"""
:param fuzzable_request: The original fuzzable request
:return: One or more HTTP responses which are returned by the application
when the Host header contains a domain that does not exist
in the remote server
"""
base_url = fuzzable_request.get_url().base_url()
# One for the TLD
non_existent_domain = 'iDoNotExistPleaseGoAwayNowOrDie%s.com' % rand_alnum(4)
# One for subdomain
args = (rand_alnum(4), base_url.get_domain())
non_existent_subdomain = 'iDoNotExistPleaseGoAwayNowOrDie%s.%s' % args
result = []
for ne_domain in (non_existent_domain, non_existent_subdomain):
try:
http_response = self._http_get_vhost(base_url, ne_domain)
except Exception, e:
msg = 'Failed to generate invalid domain fingerprint: %s'
om.out.debug(msg % e)
else:
result.append(http_response)
return result
def _get_common_virtual_hosts(self, fuzzable_request):
"""
Get a list of common virtual hosts based on the target domain
:param fuzzable_request: The fuzzable request as received by the plugin
:return: A list of possible domain names that could be hosted in the
same web server that "domain".
"""
base_url = fuzzable_request.get_url().base_url()
domain = base_url.get_domain()
root_domain = base_url.get_root_domain()
for subdomain in self.COMMON_VHOSTS:
# intranet
yield subdomain
# It doesn't make any sense to create subdomains based no an
# IP address, they will look like intranet.192.168.1.2 , and
# are invalid domains
if is_ip_address(domain):
continue
# intranet.www.target.com
yield subdomain + '.' + domain
# intranet.target.com
yield subdomain + '.' + root_domain
# intranet.target
yield subdomain + '.' + root_domain.split('.')[0]
def get_long_desc(self):
"""
:return: A DETAILED description of the plugin functions and features.
"""
return """
This plugin uses the HTTP Host header to find new virtual hosts. For
example, if the intranet page is hosted in the same server that the
public page, and the web server is misconfigured, this plugin will
discover that virtual host.
Please note that this plugin doesn't use any DNS technique to find
these virtual hosts.
"""
Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation
14 Oct 2019 13:46Current
7.1High risk
Vulners AI Score7.1