File: results.py

package info (click to toggle)
wig 0.6-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 7,008 kB
  • sloc: python: 1,522; sh: 37; makefile: 5
file content (176 lines) | stat: -rw-r--r-- 5,634 bytes parent folder | download | duplicates (3)
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']