1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
|
from collections import defaultdict, Counter, namedtuple
from classes.sitemap import Sitemap
class Results(object):
def __init__(self, options):
self.width = None
self.printer = None
self.results = []
# the storage for 'string' and 'regex' matched fingerprints
# since these don't need extra processing they are added directly
# to the final scores
self.scores = defaultdict(lambda: defaultdict(lambda: Counter()))
# ^ Category ^ Name ^ Version
# md5 fingerprints are based on content that might not hav been changed
# across different versions of the cms. The score of a match is based on
# the number of 'hits' for that URL. The finale score for a cms version will be:
# 1 / number_of_hits
#
self.md5_matches = defaultdict(lambda: defaultdict(lambda: Counter()))
# ^ Url ^ cms ^ versions
self.platform_observations = defaultdict(lambda: defaultdict(set))
self.sitemap = Sitemap()
self.site_info = {
'ip': [], # changed from a string to a list: see issue #19
'title': '',
'cookies': set(),
'subdomains': set(),
}
def _calc_md5_score(self):
# calculate the final scores for md5 fingerprints, and add
# them to the final scores
for url in self.md5_matches:
for cat_name in self.md5_matches[url]:
category, name = cat_name
# get the number of 'hits' for a specific CMS and URL
number_of_hits = sum([self.md5_matches[url][cat_name][version] for version in self.md5_matches[url][cat_name]])
# update the score for the cms version
for version in self.md5_matches[url][cat_name]:
self.scores[category][name][version] += (1 / number_of_hits)
def add_version(self, category, name, version=None, fingerprint=None, weight=1):
url = ''
match_type = ''
if fingerprint is not None:
match_type = fingerprint['type']
# overwrite weight if defined in fingerprint
if 'weight' in fingerprint:
weight = fingerprint['weight']
# add to the sitemap
if 'url' in fingerprint:
self.sitemap.add(fingerprint['url'])
url = fingerprint['url']
else:
url = ''
# add note if present
if 'note' in fingerprint:
note = fingerprint['note']
self.printer.print_debug_line('- %s: %s' % (note, url), 5)
self.add_interesting(note, url)
if category == 'platform' and not version == '' and not match_type == 'md5':
self.platform_observations[name][version].add(fingerprint['url'])
self.printer.print_debug_line('- Found match: %s - %s %s - %s' % (url, name, version, match_type), 5)
#print(category, name, version, weight)
# if the type of the fingerprint is md5, then the we need
# to keep track of how many cms versions have been detected
# the a given URL, as this determines the weight score of
# fingerprint match
if match_type == 'md5':
self.md5_matches[url][(category, name)][version] += 1
# if there has been no version detection (interesting file discovery)
# skip adding the versions to the scores
elif version == None:
pass
# if the version is blank or true, add '0' to
# set it to the worst match
elif version == '' or version == True:
self.scores[category][name][version] += 0
# else add the weight
else:
self.scores[category][name][version] += weight
def update(self):
self._calc_md5_score()
c = {
'cms': namedtuple('CMS', ['name', 'version']),
'platform': namedtuple('Platform', ['name', 'version']),
'js': namedtuple('JavaScript', ['name', 'version']),
'os': namedtuple('OS', ['name', 'version'])
}
for category in self.scores:
# loop over the entries for the category
for name in sorted(self.scores[category]):
# get the versions and remove the ones that are most unlikely
v = self.scores[category][name]
versions = sorted(v.items(), key=lambda x:x[1], reverse=True)
# if the highest rated version is blank, move it to the end of the list
if versions[0][0] == '':
versions = versions[1:] + [versions[0]]
relevant = sorted(i[0] for i in versions if i[1] == versions[0][1])
for version in relevant:
self.results.append(c[category](name, version))
# check if there are multiple precise version detection of the same platform
platforms = self.platform_observations
for platform in platforms:
versions = platforms[platform]
if len(versions) > 1:
for version in versions:
urls = list(versions[version])
self.add_platform_note(platform + ' ' + version, sorted(urls, key=lambda x: len(x))[0])
def add_vulnerabilities(self, cms, version, num_vuln, link):
Vulnerability = namedtuple('Vulnerability', ['software', 'version', 'num_vuln', 'link'])
self.results.append(Vulnerability(cms, version, num_vuln, link))
def add_tool(self, cms, tool_name, tool_link):
Tool = namedtuple('Tool', ['software', 'tool_name', 'link'])
self.results.append(Tool(cms, tool_name, tool_link))
def add_subdomain(self, subdomain, title, ip):
Subdomain = namedtuple('Subdomain', ['subdomain', 'page_title', 'ip'])
self.results.append(Subdomain(subdomain, title, ip))
def add_interesting(self, note, url):
Interesting = namedtuple('Interesting', ['note', 'url'])
if not Interesting(note, url) in self.results:
self.results.append(Interesting(note, url))
def add_platform_note(self, platform, url):
PlatformNote = namedtuple('PlatformNote', ['platform', 'url'])
self.results.append(PlatformNote(platform, url))
def get_sitemap(self):
return str(self.sitemap)
def get_platform_results(self):
return self.scores['platform']
|