New upstream version 2.2.0
Sophie Brun
7 years ago
24 | 24 | * Brice Samulenok |
25 | 25 | * Ulisses Albuquerque |
26 | 26 | * Alejandro Parodi |
27 | * Federico Fernandez | |
28 | * xtr4nge |
8 | 8 | |
9 | 9 | New features in the latest update |
10 | 10 | ===================================== |
11 | ||
12 | November 10, 2016: | |
13 | --- | |
14 | * New library to connect with Faraday Server. | |
15 | * Fixed Fplugin, now it uses the new library to communicate with the Server. | |
16 | * New field for Vulnerabilities: plugin creator and status. | |
17 | * Refactor in Faraday Core and GTK Client. | |
18 | * Bug fixing in Faraday Client and Server. | |
19 | * Added Faraday news notifications in GTK and Web UI. | |
20 | * New plugins: Dirb, Netdiscover, FruityWifi, Sentinel. | |
21 | * Improvements on the WPscan plugin. | |
22 | * Fixed Licenses search. | |
23 | * Refactor Licenses module to be compatible with JS Strict Mode. | |
11 | 24 | |
12 | 25 | September 19, 2016: |
13 | 26 | --- |
20 | 33 | * New plugin: WPscan |
21 | 34 | * Host Sidebar on GTK now adds information more intelligently and will never block the application. |
22 | 35 | * Evidence screenshots in report generation is now bigger. |
36 | * Help menu in GTK with links to interesting links. | |
23 | 37 | * Added Help section to WEB UI. |
24 | ||
25 | 38 | |
26 | 39 | August 12, 2016: |
27 | 40 | --- |
0 | #!/usr/bin/env python | |
0 | #!/usr/bin/env python2.7 | |
1 | 1 | # -*- coding: utf-8 -*- |
2 | 2 | |
3 | 3 | ''' |
4 | 4 | Faraday Penetration Test IDE |
5 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
5 | Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) | |
6 | 6 | See the file 'doc/LICENSE' for the license information |
7 | ''' | |
7 | 8 | |
8 | ''' | |
9 | for host in api.__model_controller.getAllHosts(): | |
10 | print "Del host: " + host.name | |
11 | api.delHost(host.id) | |
9 | from persistence.server import server, models | |
10 | ||
11 | def main(workspace=''): | |
12 | ||
13 | for host in models.get_hosts(workspace): | |
14 | print('Delete Host:' + host.name) | |
15 | models.delete_host(workspace, host.id)⏎ |
0 | #!/usr/bin/env python | |
0 | #!/usr/bin/env python2.7 | |
1 | 1 | # -*- coding: utf-8 -*- |
2 | 2 | |
3 | 3 | ''' |
4 | 4 | Faraday Penetration Test IDE |
5 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
5 | Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) | |
6 | 6 | See the file 'doc/LICENSE' for the license information |
7 | ||
8 | 7 | ''' |
9 | 8 | |
10 | for host in api.__model_controller.getAllHosts(): | |
9 | from persistence.server import server, models | |
11 | 10 | |
12 | for i in host.getAllInterfaces(): | |
13 | for s in i.getAllServices(): | |
14 | if s.getStatus() != "open": | |
15 | print "delService" + s.name + "from int:" + i.name | |
16 | api.delServiceFromInterface(host.id,i.id,s.id) | |
11 | def main(workspace=''): | |
17 | 12 | |
18 | ||
13 | for service in models.get_services(workspace): | |
14 | if service.status != 'open' and service.status != 'opened': | |
15 | print('Deleted service: ' + service.name) | |
16 | models.delete_service(workspace, service.id)⏎ |
0 | #!/usr/bin/env python | |
0 | #!/usr/bin/env python2.7 | |
1 | 1 | # -*- coding: utf-8 -*- |
2 | 2 | |
3 | 3 | ''' |
4 | 4 | Faraday Penetration Test IDE |
5 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
5 | Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) | |
6 | 6 | See the file 'doc/LICENSE' for the license information |
7 | ||
8 | 7 | ''' |
9 | 8 | |
10 | 9 | import re |
11 | regex="ssl\-cert|ssl\-date|Traceroute Information|TCP\/IP Timestamps Supported|OS Identification|Common Platform Enumeration" | |
12 | c=0 | |
13 | for host in api.__model_controller.getAllHosts(): | |
14 | hostnames="" | |
15 | for v in host.getVulns(): | |
16 | if re.match(regex,v.name) is not None: | |
17 | api.delVulnFromHost(v.id,host.id) | |
18 | c+=1 | |
10 | from persistence.server import server, models | |
19 | 11 | |
20 | for i in host.getAllInterfaces(): | |
21 | for s in i.getAllServices(): | |
22 | for v in s.getVulns(): | |
23 | if re.match(regex,v.name) is not None: | |
24 | api.delVulnFromService(v.id,host.id,s.id) | |
25 | c+=1 | |
12 | def main(workspace=''): | |
26 | 13 | |
27 | print "Vulnerabilities deleted %s" % c | |
14 | regex = ( | |
15 | r"ssl\-cert|ssl\-date|Traceroute Information|TCP\/IP Timestamps Supported" | |
16 | r"|OS Identification|Common Platform Enumeration") | |
17 | ||
18 | for vuln in models.get_all_vulns(workspace): | |
19 | if re.findall(regex, vuln.name, ) != []: | |
20 | print("Delete Vuln: " + vuln.name) | |
21 | models.delete_vuln(workspace, vuln.id)⏎ |
2 | 2 | |
3 | 3 | ''' |
4 | 4 | Faraday Penetration Test IDE |
5 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
5 | Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) | |
6 | 6 | See the file 'doc/LICENSE' for the license information |
7 | 7 | ''' |
8 | 8 | |
9 | 9 | import os |
10 | import imp | |
11 | import sys | |
10 | 12 | import argparse |
11 | 13 | |
14 | parent_path = os.path.abspath(os.path.join(__file__, '../..')) | |
15 | sys.path.insert(0, parent_path) | |
16 | ||
12 | 17 | if __name__ == '__main__': |
18 | ||
13 | 19 | description = ( |
14 | 20 | 'Using our plugin you can do different actions in the command line' |
15 | 21 | ' and interact with Faraday. Faraday comes with some presets for bulk' |
18 | 24 | parser = argparse.ArgumentParser(description=description) |
19 | 25 | |
20 | 26 | parser.add_argument( |
21 | '-e', | |
22 | '--execute', | |
23 | help='Execute code received in this parameter.' | |
24 | ' Example:\n./fplugin -e "for h in api.__model_controller.getAllHosts(): print h.name"') | |
27 | '-f', | |
28 | '--file', | |
29 | help='Script file.' | |
30 | ' Example:\n./fplugin -f getAllIps.py') | |
25 | 31 | |
26 | 32 | parser.add_argument( |
27 | '-o', | |
28 | '--output', | |
29 | help='Store output of fplugin in a file.') | |
33 | '-w', | |
34 | '--workspace', | |
35 | help='Use workspace') | |
30 | 36 | |
31 | # file with code to execute | |
32 | 37 | parser.add_argument( |
33 | '-f', | |
34 | '--file', | |
35 | help='File with code to execute.' | |
36 | ' Example:\n./fplugin -f getAllIps.py ') | |
38 | '-u', | |
39 | '--url', | |
40 | help='Faraday Server URL. Example: http://localhost:5984') | |
37 | 41 | |
38 | 42 | args, unknown = parser.parse_known_args() |
39 | if args.output: | |
40 | if os.path.isfile(args.output): | |
41 | with open(args.output) as f: | |
42 | data = f.read() | |
43 | print data | |
43 | ||
44 | if args.file: | |
45 | ||
46 | # Get filename and import this | |
47 | module_fplugin = imp.load_source('module_fplugin', args.file) | |
48 | module_fplugin.models.server.FARADAY_UP = False | |
49 | module_fplugin.models.server.SERVER_URL = args.url | |
50 | ||
51 | try: | |
52 | call_main = getattr(module_fplugin, 'main') | |
53 | call_main(workspace = args.workspace) | |
54 | except AttributeError: | |
55 | print 'Error: main() function missing in script?'⏎ |
0 | #!/usr/bin/env python | |
0 | #!/usr/bin/env python2.7 | |
1 | 1 | # -*- coding: utf-8 -*- |
2 | 2 | |
3 | 3 | ''' |
4 | 4 | Faraday Penetration Test IDE |
5 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
5 | Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) | |
6 | 6 | See the file 'doc/LICENSE' for the license information |
7 | ||
8 | 7 | ''' |
9 | 8 | |
10 | for host in api.__model_controller.getAllHosts(): | |
11 | for c in host.getCreds(): | |
12 | print host.name+"|0|"+c.username+ "|"+c.password | |
9 | from persistence.server import server, models | |
13 | 10 | |
14 | for i in host.getAllInterfaces(): | |
15 | for s in i.getAllServices(): | |
16 | for c in s.getCreds(): | |
17 | print host.name+"|"+str(s.getPorts()) + "|"+c.username+ "|"+c.password | |
18 | ||
11 | def main(workspace=''): | |
12 | for credential in models.get_credentials(workspace): | |
13 | print(credential.username + ' : ' + credential.password)⏎ |
0 | #!/usr/bin/env python | |
1 | # -*- coding: utf-8 -*- | |
2 | ||
3 | ''' | |
4 | Faraday Penetration Test IDE | |
5 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
6 | See the file 'doc/LICENSE' for the license information | |
7 | ||
8 | ''' | |
9 | ||
10 | for host in api.__model_controller.getAllHosts(): | |
11 | for i in host.getAllInterfaces(): | |
12 | for h in i._hostnames: | |
13 | print h |
0 | #!/usr/bin/env python | |
0 | #!/usr/bin/env python2.7 | |
1 | 1 | # -*- coding: utf-8 -*- |
2 | 2 | |
3 | 3 | ''' |
4 | 4 | Faraday Penetration Test IDE |
5 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
5 | Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) | |
6 | 6 | See the file 'doc/LICENSE' for the license information |
7 | ''' | |
7 | 8 | |
8 | ''' | |
9 | for h in api.__model_controller.getAllHosts(): print h.name | |
9 | from persistence.server import server, models | |
10 | ||
11 | def main(workspace=''): | |
12 | for host in models.get_hosts(workspace): | |
13 | print(host.name)⏎ |
0 | #!/usr/bin/env python | |
0 | #!/usr/bin/env python2.7 | |
1 | 1 | # -*- coding: utf-8 -*- |
2 | 2 | |
3 | 3 | ''' |
4 | 4 | Faraday Penetration Test IDE |
5 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
5 | Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) | |
6 | 6 | See the file 'doc/LICENSE' for the license information |
7 | ||
8 | 7 | ''' |
9 | 8 | |
10 | for host in api.__model_controller.getAllHosts(): | |
11 | if len(host.getAllInterfaces()) > 1: | |
12 | print host.name | |
9 | from persistence.server import server, models | |
10 | ||
11 | def main(workspace=''): | |
12 | for interface in models.get_interfaces(workspace): | |
13 | print(interface.ipv4['address'])⏎ |
0 | #!/usr/bin/env python | |
1 | # -*- coding: utf-8 -*- | |
2 | ||
3 | ''' | |
4 | Faraday Penetration Test IDE | |
5 | Copyright (C) 2015 Infobyte LLC (http://www.infobytesec.com/) | |
6 | See the file 'doc/LICENSE' for the license information | |
7 | ||
8 | ''' | |
9 | ||
10 | # Get All IPs from targets without services | |
11 | for host in api.__model_controller.getAllHosts(): | |
12 | ||
13 | for i in host.getAllInterfaces(): | |
14 | if not i.getAllServices(): | |
15 | print host.name | |
16 |
0 | #!/usr/bin/env python | |
0 | #!/usr/bin/env python2.7 | |
1 | 1 | # -*- coding: utf-8 -*- |
2 | 2 | |
3 | 3 | ''' |
4 | 4 | Faraday Penetration Test IDE |
5 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
5 | Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) | |
6 | 6 | See the file 'doc/LICENSE' for the license information |
7 | ''' | |
7 | 8 | |
8 | ''' | |
9 | for h in api.__model_controller.getAllHosts(): print h.name+"|"+h.getOS() | |
9 | from persistence.server import server, models | |
10 | ||
11 | def main(workspace=''): | |
12 | for host in models.get_hosts(workspace): | |
13 | print(host.os)⏎ |
0 | #!/usr/bin/env python | |
0 | #!/usr/bin/env python2.7 | |
1 | 1 | # -*- coding: utf-8 -*- |
2 | 2 | |
3 | 3 | ''' |
4 | 4 | Faraday Penetration Test IDE |
5 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
5 | Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) | |
6 | 6 | See the file 'doc/LICENSE' for the license information |
7 | ''' | |
7 | 8 | |
8 | ''' | |
9 | webs={} | |
10 | for host in api.__model_controller.getAllHosts(): | |
9 | from persistence.server import server, models | |
11 | 10 | |
12 | for i in host.getAllInterfaces(): | |
13 | for s in i.getAllServices(): | |
14 | for p in s.getPorts(): | |
15 | if str(p) == '23': | |
16 | webs[host.name]=1 | |
17 | ||
18 | for k,v in webs.iteritems(): | |
19 | print k | |
11 | def main(workspace=''): | |
12 | ||
13 | for service in models.get_services(workspace): | |
14 | if 23 in service.ports: | |
15 | print(service.name) |
0 | #!/usr/bin/env python | |
0 | #!/usr/bin/env python2.7 | |
1 | 1 | # -*- coding: utf-8 -*- |
2 | 2 | |
3 | 3 | ''' |
4 | 4 | Faraday Penetration Test IDE |
5 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
5 | Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) | |
6 | 6 | See the file 'doc/LICENSE' for the license information |
7 | ''' | |
7 | 8 | |
8 | ''' | |
9 | webs={} | |
10 | for host in api.__model_controller.getAllHosts(): | |
9 | from persistence.server import server, models | |
11 | 10 | |
12 | for i in host.getAllInterfaces(): | |
13 | for s in i.getAllServices(): | |
14 | for p in s.getPorts(): | |
15 | if str(p) == '5900': | |
16 | webs[host.name]=1 | |
17 | ||
18 | for k,v in webs.iteritems(): | |
19 | print k | |
11 | def main(workspace=''): | |
12 | ||
13 | for service in models.get_services(workspace): | |
14 | if 5900 in service.ports: | |
15 | print(service.name) |
0 | #!/usr/bin/env python | |
1 | # -*- coding: utf-8 -*- | |
2 | ||
3 | ''' | |
4 | Faraday Penetration Test IDE | |
5 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
6 | See the file 'doc/LICENSE' for the license information | |
7 | ||
8 | ''' | |
9 | ||
10 | import re | |
11 | vulns="" | |
12 | for host in api.__model_controller.getAllHosts(): | |
13 | hostnames="" | |
14 | for i in host.getAllInterfaces(): | |
15 | for h in i._hostnames: | |
16 | hostnames+=","+h | |
17 | ||
18 | for v in host.getVulns(): | |
19 | print host.name+"("+hostnames+")|0|"+v.name.encode("utf-8")+ "|"+re.sub("\n|\r",",",v.desc.encode("utf-8"))+"|"+str(v.severity)+"|"+str(v.id) | |
20 | ||
21 | for i in host.getAllInterfaces(): | |
22 | for s in i.getAllServices(): | |
23 | for v in s.getVulns(): | |
24 | print host.name+"("+hostnames+")|"+str(s.getPorts()) + "|"+v.name.encode("utf-8")+ "|"+re.sub("\n|\r",",",v.desc.encode("utf-8"))+"|"+str(v.severity)+"|"+str(v.id) | |
25 |
0 | #!/usr/bin/env python | |
1 | # -*- coding: utf-8 -*- | |
2 | ||
3 | ''' | |
4 | Faraday Penetration Test IDE | |
5 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
6 | See the file 'doc/LICENSE' for the license information | |
7 | ||
8 | ''' | |
9 | import re | |
10 | webs={} | |
11 | for host in api.__model_controller.getAllHosts(): | |
12 | ||
13 | for i in host.getAllInterfaces(): | |
14 | for s in i.getAllServices(): | |
15 | web=False | |
16 | if re.search("www|web|http|https",s.name): | |
17 | web=True | |
18 | ||
19 | if ['80','443','8080'] in s.getPorts(): | |
20 | web=true | |
21 | ||
22 | for v in s.getVulns(): | |
23 | if v.class_signature=="VulnerabilityWeb": | |
24 | web=True | |
25 | break | |
26 | if web==True: | |
27 | for p in s.getPorts(): | |
28 | if str(p)=="443": | |
29 | webs["https://" + host.name+":"+str(p)+"/"]=1 | |
30 | else: | |
31 | webs["http://" + host.name+":"+str(p)+"/"]=1 | |
32 | ||
33 | for n in s.getNotes(): | |
34 | if n.name =="website": | |
35 | for wn in n.getNotes(): | |
36 | webs["http://" + wn.name+":"+str(p)+"/"]=1 | |
37 | ||
38 | ||
39 | for k,v in webs.iteritems(): | |
40 | print k | |
41 | ||
42 | ||
43 |
0 | #!/usr/bin/env python | |
0 | #!/usr/bin/env python2.7 | |
1 | 1 | # -*- coding: utf-8 -*- |
2 | 2 | |
3 | 3 | ''' |
4 | 4 | Faraday Penetration Test IDE |
5 | 5 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) |
6 | 6 | See the file 'doc/LICENSE' for the license information |
7 | ''' | |
7 | 8 | |
8 | ''' | |
9 | port='8080' | |
10 | webs={} | |
11 | for host in api.__model_controller.getAllHosts(): | |
9 | from persistence.server import server, models | |
12 | 10 | |
13 | for i in host.getAllInterfaces(): | |
14 | for s in i.getAllServices(): | |
15 | for p in s.getPorts(): | |
16 | if str(p) == port: | |
17 | webs[host.name]=1 | |
11 | def main(workspace=''): | |
18 | 12 | |
19 | for k,v in webs.iteritems(): | |
20 | print k | |
13 | ports = [80, 443, 8080] | |
14 | for service in models.get_services(workspace): | |
15 | for port in ports: | |
16 | if port in service.ports: | |
17 | print(service.name) |
0 | #!/usr/bin/env python | |
0 | #!/usr/bin/env python2.7 | |
1 | 1 | # -*- coding: utf-8 -*- |
2 | 2 | ''' |
3 | 3 | Faraday Penetration Test IDE |
4 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
4 | Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) | |
5 | 5 | See the file 'doc/LICENSE' for the license information |
6 | 6 | ''' |
7 | 7 | import os |
1 | 1 | <faraday> |
2 | 2 | |
3 | 3 | <appname>Faraday - Penetration Test IDE</appname> |
4 | <version>2.1.0</version> | |
4 | <version>2.2.0</version> | |
5 | 5 | <debug_status>0</debug_status> |
6 | 6 | <font>-Misc-Fixed-medium-r-normal-*-12-100-100-100-c-70-iso8859-1</font> |
7 | 7 | <home_path>~/</home_path> |
515 | 515 | self.serverIO.active_workspace = event.workspace.name |
516 | 516 | host_count, service_count, vuln_count = self.update_counts() |
517 | 517 | total_host_amount = self.serverIO.get_hosts_number() |
518 | first_host_page = self.serverIO.get_hosts(page='0', page_size='20', sort='vulns', sort_dir='desc') | |
518 | first_host_page = self.serverIO.get_hosts(page='0', page_size='20', | |
519 | sort='vulns', sort_dir='desc') | |
520 | total_host_amount = self.serverIO.get_workspace_numbers()[0] | |
519 | 521 | GObject.idle_add(self.statusbar.set_workspace_label, event.workspace.name) |
520 | GObject.idle_add(self.hosts_sidebar.redo, first_host_page, total_host_amount) | |
522 | GObject.idle_add(self.hosts_sidebar.reset_model_after_workspace_changed, | |
523 | first_host_page, total_host_amount) | |
521 | 524 | GObject.idle_add(self.statusbar.update_ws_info, host_count, |
522 | 525 | service_count, vuln_count) |
523 | 526 | GObject.idle_add(self.statusbar.set_default_conflict_label) |
540 | 543 | GObject.idle_add(self.handle_no_active_workspace) |
541 | 544 | |
542 | 545 | def add_object(): |
543 | GObject.idle_add(self.hosts_sidebar.add_object, event.new_obj) | |
544 | host_count, service_count, vuln_count = self.update_counts() | |
545 | GObject.idle_add(self.statusbar.update_ws_info, host_count, | |
546 | service_count, vuln_count) | |
546 | if event.new_obj: | |
547 | GObject.idle_add(self.hosts_sidebar.add_object, event.new_obj) | |
548 | host_count, service_count, vuln_count = self.update_counts() | |
549 | GObject.idle_add(self.statusbar.update_ws_info, host_count, | |
550 | service_count, vuln_count) | |
547 | 551 | |
548 | 552 | def delete_object(): |
549 | GObject.idle_add(self.hosts_sidebar.remove_object, event.obj_id) | |
550 | host_count, service_count, vuln_count = self.update_counts() | |
551 | GObject.idle_add(self.statusbar.update_ws_info, host_count, | |
552 | service_count, vuln_count) | |
553 | if event.obj_id: | |
554 | GObject.idle_add(self.hosts_sidebar.remove_object, event.obj_id) | |
555 | host_count, service_count, vuln_count = self.update_counts() | |
556 | GObject.idle_add(self.statusbar.update_ws_info, host_count, | |
557 | service_count, vuln_count) | |
553 | 558 | |
554 | 559 | def update_object(): |
555 | GObject.idle_add(self.hosts_sidebar.update_object, event.obj) | |
556 | host_count, service_count, vuln_count = self.update_counts() | |
557 | GObject.idle_add(self.statusbar.update_ws_info, host_count, | |
558 | service_count, vuln_count) | |
560 | if event.obj: | |
561 | GObject.idle_add(self.hosts_sidebar.update_object, event.obj) | |
562 | host_count, service_count, vuln_count = self.update_counts() | |
563 | GObject.idle_add(self.statusbar.update_ws_info, host_count, | |
564 | service_count, vuln_count) | |
559 | 565 | |
560 | 566 | dispatch = {3131: new_log_event, |
561 | 567 | 3141: new_conflict_event, |
615 | 621 | |
616 | 622 | # the dummy values here will be updated as soon as the ws is loaded. |
617 | 623 | self.hosts_sidebar = HostsSidebar(self.show_host_info, self.serverIO.get_hosts, |
618 | self.icons) | |
619 | default_model = self.hosts_sidebar.create_model([]) # dummy empty list | |
620 | self.hosts_sidebar.create_view(default_model) | |
624 | self.serverIO.get_host, self.icons) | |
621 | 625 | self.sidebar = Sidebar(self.ws_sidebar.get_box(), |
622 | 626 | self.hosts_sidebar.get_box()) |
623 | 627 | |
838 | 842 | the user's default browser |
839 | 843 | """ |
840 | 844 | couch_url = CONF.getCouchURI() |
841 | ws_name = self.workspace_manager.getActiveWorkspace().name | |
842 | ws_url = couch_url + "/_ui/#/dashboard/ws/" + ws_name | |
845 | ws_name = self.workspace_manager.getActiveWorkspace() | |
846 | if not ws_name: | |
847 | ws_url = couch_url + "/_ui/" | |
848 | else: | |
849 | ws_url = couch_url + "/_ui/#/dashboard/ws/" + ws_name.name | |
843 | 850 | webbrowser.open(ws_url, new=2) |
844 | 851 | |
845 | 852 | def on_help_dispatch(self, action, param=None): |
0 | import requests | |
0 | 1 | from gi.repository import Gtk |
1 | 2 | from utils.logs import getLogger |
2 | 3 | from functools import wraps |
3 | 4 | from compatibility import CompatibleScrolledWindow as GtkScrolledWindow |
4 | from persistence.server.server import ServerRequestException | |
5 | from persistence.server.server_io_exceptions import ServerRequestException | |
5 | 6 | |
6 | 7 | def safe_io_with_server(response_in_emergency): |
7 | 8 | """A function that takes a response_in_emergency. It will return |
16 | 17 | except ServerRequestException as e: |
17 | 18 | res = response_in_emergency |
18 | 19 | getLogger("Server-GTK IO").warning(e) |
20 | except (requests.exceptions.MissingSchema, requests.exceptions.InvalidSchema): | |
21 | res = response_in_emergency | |
22 | getLogger("Server-GTK IO").error("It looks like the Faraday Server " | |
23 | "URL is not correctly formated. Please change it and " | |
24 | "remember to set it with a valid protocol, like http.\n" | |
25 | "For example: http://faradayserver:port/") | |
19 | 26 | return res |
20 | 27 | return wrapper |
21 | 28 | return safe_decorator |
181 | 181 | creation_ok = self.create_ws_callback(ws_name, ws_desc) |
182 | 182 | if creation_ok: |
183 | 183 | self.sidebar.add_workspace(ws_name) |
184 | self.destroy() | |
184 | self.destroy() | |
185 | 185 | else: |
186 | 186 | errorDialog(self, "Invalid workspace name", |
187 | 187 | "A workspace must be named with " |
611 | 611 | # only the ID and the name are needed, but i still need to 'fill' |
612 | 612 | # the other columns with dummy info |
613 | 613 | |
614 | # display_str = host.getName() + " (" + str(len(host.getVulns())) + ")" | |
615 | display_str = str(host) | |
614 | display_str = host.getName() + " (" + str(len(host.getVulns())) + ")" | |
615 | # display_str = str(host) | |
616 | 616 | owned_status = ("Yes" if host.isOwned() else "No") |
617 | 617 | host_position = model.append(None, [host.getID(), host.getName(), |
618 | 618 | host.getOS(), owned_status, |
655 | 655 | def add_service_to_interface_in_model(service, interface_pos, model): |
656 | 656 | """Append a service to an interface at interface_pos in the given |
657 | 657 | model. Return None. Modifies the model""" |
658 | display_str = str(service) | |
658 | display_str = service.getName() + " (" + str(len(service.getVulns())) + ")" | |
659 | 659 | model.append(interface_pos, [service.getID(), |
660 | 660 | service.getName(), |
661 | 661 | service.getDescription(), |
775 | 775 | raise TypeError |
776 | 776 | return params_string |
777 | 777 | |
778 | # those are 15 strings | |
778 | # those are 16 strings | |
779 | 779 | model = Gtk.ListStore(str, str, str, str, str, str, str, str, |
780 | str, str, str, str, str, str, str) | |
780 | str, str, str, str, str, str, str, str) | |
781 | 781 | |
782 | 782 | vulns = obj.getVulns() |
783 | 783 | for vuln in vulns: |
785 | 785 | if _type == "Vulnerability": |
786 | 786 | # again filling up the model with dumb strings |
787 | 787 | # because gtk |
788 | model.append([_type, vuln.getName(), vuln.getDescription(), | |
789 | vuln.getData(), vuln.getSeverity(), | |
790 | ', '.join(vuln.getRefs()), | |
788 | model.append([_type, vuln.getName(), | |
789 | vuln.getDescription(), | |
790 | vuln.getData(), | |
791 | vuln.getSeverity(), | |
792 | ', '.join([str(v) for v in vuln.getRefs() if v]), | |
793 | vuln.getStatus(), | |
791 | 794 | "", "", "", "", "", "", "", "", ""]) |
792 | 795 | |
793 | 796 | elif _type == "VulnerabilityWeb": |
794 | model.append([_type, vuln.getName(), vuln.getDescription(), | |
795 | vuln.getData(), vuln.getSeverity(), | |
796 | ", ".join(vuln.getRefs()), vuln.getPath(), | |
797 | vuln.getWebsite(), vuln.getRequest(), | |
798 | vuln.getResponse(), vuln.getMethod(), | |
797 | model.append([_type, vuln.getName(), | |
798 | vuln.getDescription(), | |
799 | vuln.getData(), | |
800 | vuln.getSeverity(), | |
801 | ", ".join([str(v) for v in vuln.getRefs() if v]), | |
802 | vuln.getPath(), | |
803 | vuln.getWebsite(), | |
804 | vuln.getRequest(), | |
805 | vuln.getResponse(), | |
806 | vuln.getMethod(), | |
799 | 807 | vuln.getPname(), |
800 | 808 | params_to_string(vuln.getParams()), |
801 | vuln.getQuery(), ""]) | |
809 | vuln.getQuery(), | |
810 | vuln.getStatus(), | |
811 | ""]) | |
802 | 812 | # sort it! |
803 | 813 | sorted_model = Gtk.TreeModelSort(model=model) |
804 | 814 | sorted_model.set_sort_column_id(1, Gtk.SortType.ASCENDING) |
819 | 829 | It is important to notice that the first element of object_info |
820 | 830 | is ignored. This is because of how the models in this class contain |
821 | 831 | information. Thus, there'll be as many of this small boxes as |
822 | len(property_names) minus one, read next paragraph. | |
832 | len(property_names) minus one. | |
823 | 833 | """ |
824 | 834 | |
825 | 835 | for index, prop_name in enumerate(property_names, start=1): |
897 | 907 | |
898 | 908 | elif object_type == "Vulnerability": |
899 | 909 | property_names = ["Name: ", "Description: ", "Data: ", |
900 | "Severity: ", "Refs: "] | |
910 | "Severity: ", "Refs: ", "Status: "] | |
901 | 911 | |
902 | 912 | elif object_type == "VulnerabilityWeb": |
903 | 913 | property_names = ["Name: ", "Description: ", "Data: ", |
904 | 914 | "Severity: ", "Refs: ", "Path: ", |
905 | 915 | "Website: ", "Request: ", "Response: ", |
906 | 916 | "Method: ", "Pname: ", "Params: ", |
907 | "Query: ", "Category: "] | |
917 | "Query: ", "Status: "] | |
908 | 918 | return property_names |
909 | 919 | |
910 | 920 | def clear(self, box): |
1268 | 1278 | obj.getData(), |
1269 | 1279 | obj.getSeverity(), |
1270 | 1280 | obj.getRefs(), |
1271 | obj.getResolution())) | |
1272 | ||
1273 | props = ["Name", "Desc", "Data", "Severity", "Refs", "Resolution"] | |
1281 | obj.getResolution(), | |
1282 | obj.getStatus())) | |
1283 | ||
1284 | props = ["Name", "Desc", "Data", "Severity", "Refs", "Resolution", "Status"] | |
1274 | 1285 | model = self.fill_model_from_props_and_attr(model, attr, props) |
1275 | 1286 | return model |
1276 | 1287 | |
1293 | 1304 | obj.getMethod(), |
1294 | 1305 | obj.getPname(), |
1295 | 1306 | obj.getParams(), |
1296 | obj.getQuery())) | |
1307 | obj.getQuery(), | |
1308 | obj.getStatus())) | |
1297 | 1309 | |
1298 | 1310 | props = ["Name", "Desc", "Data", "Severity", "Refs", "Path", |
1299 | 1311 | "Website", "Request", "Response", "Method", "Pname", |
1300 | "Params", "Query"] | |
1312 | "Params", "Query", "Status"] | |
1301 | 1313 | |
1302 | 1314 | model = self.fill_model_from_props_and_attr(model, attr, props) |
1303 | 1315 | return model |
1481 | 1493 | """ |
1482 | 1494 | selection = self.ws_view.get_selection() |
1483 | 1495 | model, iter_ = selection.get_selected() |
1484 | ws_name = model[iter_][0] | |
1485 | self.change_ws_callback(ws_name) | |
1486 | self.destroy() | |
1496 | if model and iter_: | |
1497 | ws_name = model[iter_][0] | |
1498 | self.change_ws_callback(ws_name) | |
1499 | self.destroy() | |
1487 | 1500 | |
1488 | 1501 | |
1489 | 1502 | class NotificationsDialog(Gtk.Window): |
8 | 8 | import gi |
9 | 9 | import os |
10 | 10 | import math |
11 | import operator | |
12 | import webbrowser | |
11 | 13 | |
12 | 14 | gi.require_version('Gtk', '3.0') |
13 | 15 | |
114 | 116 | the Sidebar notebook. Will list all the host, and when clicking on one, |
115 | 117 | will open a window with more information about it""" |
116 | 118 | |
117 | def __init__(self, open_dialog_callback, get_host_function, icons): | |
119 | def __init__(self, open_dialog_callback, get_several_hosts_function, | |
120 | get_single_host_function, icons): | |
118 | 121 | """Initializes the HostsSidebar. Initialization by itself does |
119 | 122 | almost nothing, the application will inmediatly call create_model |
120 | 123 | with the last workspace and create_view with that model upon startup. |
121 | """ | |
124 | ||
125 | The model looks like this: | |
126 | | HOST_ID | HOST_OS_PIXBUF | OS_STR | DISPLAY_STR | VULN_COUNT| | |
127 | ====================================================================== | |
128 | | a923fd | PixBufIcon(linux)| linux | 192.168.1.2 (5) | 5 | | |
129 | """ | |
130 | ||
122 | 131 | Gtk.Widget.__init__(self) |
123 | 132 | self.open_dialog_callback = open_dialog_callback |
124 | self.get_host_function = get_host_function | |
125 | self.current_model = None | |
133 | self.get_hosts_function = get_several_hosts_function | |
134 | self.get_single_host_function = get_single_host_function | |
135 | self.model = Gtk.ListStore(str, GdkPixbuf.Pixbuf(), str, str, int) | |
136 | self.create_view() | |
126 | 137 | self.progress_label = Gtk.Label("") |
127 | self.host_amount = 0 | |
138 | self.host_amount_total = 0 | |
139 | self.host_amount_in_model = 0 | |
128 | 140 | self.page = 0 |
129 | 141 | self.host_id_to_iter = {} |
130 | 142 | self.linux_icon = icons + "tux.png" |
132 | 144 | self.mac_icon = icons + "Apple.png" |
133 | 145 | self.no_os_icon = icons + "TreeHost.png" |
134 | 146 | |
135 | def __compute_vuln_count(self, host): | |
136 | """Return the total vulnerability count for a given host""" | |
137 | return host.getVulnAmount() | |
138 | ||
139 | def __get_vuln_amount_from_model(self, host_id): | |
140 | """Given a host_id, it will look in the current model for the host_id | |
141 | and return the amount of vulnerabilities IF the host_id corresponds | |
142 | to the model ID. Else it will return None. | |
143 | """ | |
144 | host_iter = self.host_id_to_iter.get(host_id) | |
145 | if host_iter: | |
146 | return self.current_model[host_iter][4] | |
147 | ||
148 | def __add_host_to_model(self, host): | |
149 | """Adds host to the model given as parameter in the initial load | |
150 | of the sidebar.""" | |
151 | vuln_count = self.__compute_vuln_count(host) | |
152 | os_icon, os_str = self.__decide_icon(host.getOS()) | |
153 | display_str = str(host) | |
154 | host_iter = self.current_model.append([host.id, os_icon, os_str, | |
155 | display_str, vuln_count]) | |
156 | self.host_id_to_iter[host.id] = host_iter | |
157 | ||
158 | def __add_host_to_model_after_initial_load(self, host): | |
159 | """Adds a host to the model after the intial load is done | |
160 | (host came through the changes or through a plugin)""" | |
161 | self.host_amount += 1 | |
162 | if self.host_amount % 20 == 0: | |
163 | self.redo([host], self.host_amount, page=self.page+1) | |
164 | else: | |
165 | self.__add_host_to_model(host) | |
166 | ||
167 | def __host_exists_in_current_model(self, host_id): | |
168 | return self.host_id_to_iter.get(host_id) is not None | |
169 | ||
170 | def __get_host_from_host_id(self, host_id): | |
171 | try: | |
172 | return self.get_host_function(couchid=host_id)[0] | |
173 | except IndexError: | |
174 | return None | |
175 | ||
176 | def __add_vuln_to_model(self, vuln): | |
177 | """When a new vulnerability arrives, look up its hosts | |
178 | and update its vuln amount and its representation as a string.""" | |
179 | host_id = self.__find_host_id(vuln) | |
180 | if self.__host_exists_in_current_model(host_id): | |
181 | real_host = self.__get_host_from_host_id(host_id) | |
182 | if real_host is None: return | |
183 | vuln_amount = self.__compute_vuln_count(real_host) | |
184 | self.__update_host_str(host_id, new_vuln_amount=vuln_amount) | |
185 | ||
186 | def __remove_vuln_from_model(self, host_id): | |
187 | """When a new vulnerability id deleted, look up its hosts | |
188 | fand update its vuln amount and its representation as a string.""" | |
189 | if self.__host_exists_in_current_model(host_id): | |
190 | real_host = self.__get_host_from_host_id(host_id) | |
191 | if real_host is None: return | |
192 | vuln_amount = self.__compute_vuln_count(real_host) | |
193 | self.__update_host_str(host_id, new_vuln_amount=vuln_amount) | |
194 | ||
195 | def __update_host_str(self, host_id, new_vuln_amount=None, new_host_name=None): | |
196 | """When a new vulnerability id deleted, look up its hosts | |
197 | and update its vuln amount and its representation as a string.""" | |
198 | host_iter = self.host_id_to_iter[host_id] | |
199 | if not new_host_name: | |
200 | new_host_name = str(self.current_model[host_iter][3].split(" ")[0]) | |
201 | if new_vuln_amount is None: | |
202 | new_vuln_amount = str(self.current_model[host_iter][4]) | |
203 | new_string = "{0} ({1})".format(new_host_name, new_vuln_amount) | |
204 | self.current_model.set_value(host_iter, 3, new_string) | |
205 | self.current_model.set_value(host_iter, 4, int(new_vuln_amount)) | |
206 | ||
207 | def __update_host_in_model(self, host): | |
208 | self.__update_host_str(host.getID(), new_host_name=host.getName()) | |
209 | ||
210 | def __remove_host_from_model(self, host_id): | |
211 | """Deletes a host from the model given as parameter.""" | |
212 | if self.__host_exists_in_current_model(host_id): | |
213 | host_iter = self.host_id_to_iter[host_id] | |
214 | could_be_removed = self.current_model.remove(host_iter) | |
215 | del self.host_id_to_iter[host_id] | |
216 | else: | |
217 | could_be_removed = False | |
218 | return could_be_removed | |
219 | ||
220 | def __find_host_id(self, object_info): | |
221 | object_id = object_info.getID() | |
222 | host_id = object_id.split(".")[0] | |
223 | return host_id | |
147 | @property | |
148 | def number_of_pages(self): | |
149 | return int(math.ceil(float(self.host_amount_total) / 20)) | |
150 | ||
151 | @scrollable(width=160) | |
152 | def scrollable_view(self): | |
153 | return self.view | |
154 | ||
155 | def create_view(self): | |
156 | """Creates a view for the hosts model. | |
157 | It will contain two columns, the first with the OS icon given in | |
158 | the second column of the model. The second column of the view will | |
159 | be the string contained in the fourth column of the model. | |
160 | The first column of the view will be orderer according to the | |
161 | second column of the model, and the second column of the view will | |
162 | be ordered according to its fifth column. | |
163 | Will connect activation of a row with the on_click method | |
164 | """ | |
165 | self.view = Gtk.TreeView(self.model) | |
166 | self.view.set_activate_on_single_click(False) | |
167 | text_renderer = Gtk.CellRendererText() | |
168 | icon_renderer = Gtk.CellRendererPixbuf() | |
169 | column_hosts = Gtk.TreeViewColumn("Hosts", text_renderer, text=3) | |
170 | column_hosts.set_sort_column_id(4) | |
171 | column_hosts.set_sort_indicator(True) | |
172 | column_os = Gtk.TreeViewColumn("", icon_renderer, pixbuf=1) | |
173 | column_os.set_sort_column_id(2) | |
174 | column_os.set_sort_indicator(True) | |
175 | self.view.append_column(column_os) | |
176 | self.view.append_column(column_hosts) | |
177 | self.view.connect("row_activated", self.on_click) | |
178 | self.view.set_enable_search(True) | |
179 | self.view.set_search_column(2) | |
180 | return self.view | |
181 | ||
182 | def reset_model(self, hosts): | |
183 | """Resets the model to a new list of hosts. | |
184 | Use for changing of pages, _not_ for changing of workspaces, | |
185 | there's reset_model_after_workspace_changed for that. | |
186 | """ | |
187 | self.model.clear() | |
188 | self.host_amount_in_model = 0 | |
189 | self.host_id_to_iter = {} | |
190 | self.add_relevant_hosts_to_model(hosts) | |
191 | self.set_move_buttons_sensitivity() | |
192 | ||
193 | def reset_model_after_workspace_changed(self, hosts, total_host_amount): | |
194 | """Reset the model and also sets the page to 0 and the new total | |
195 | host amount will be the length of host.""" | |
196 | self.page = 0 | |
197 | self.host_amount_total = total_host_amount | |
198 | self.reset_model(hosts) | |
199 | self.update_progress_label() | |
224 | 200 | |
225 | 201 | def __decide_icon(self, os): |
226 | 202 | """Return the GdkPixbuf icon according to 'os' paramather string |
227 | 203 | and a str_id to that GdkPixbuf for easy comparison and ordering |
228 | 204 | of the view ('os' paramether string is complicated and has caps). |
229 | 205 | """ |
230 | os = os.lower() | |
206 | os = os.lower() if os else "" | |
231 | 207 | if "linux" in os or "unix" in os: |
232 | 208 | icon = GdkPixbuf.Pixbuf.new_from_file(self.linux_icon) |
233 | 209 | str_id = "linux" |
242 | 218 | str_id = "unknown" |
243 | 219 | return icon, str_id |
244 | 220 | |
245 | def create_model(self, hosts): | |
246 | """Creates a model for a lists of hosts. The model contians the | |
247 | host_id in the first column, the icon as a GdkPixbuf.Pixbuf() | |
248 | in the second column and a display_str with the host_name and the | |
249 | vulnerability count on the third column, like this: | |
250 | | HOST_ID | HOST_OS_PIXBUF | OS_STR | DISPLAY_STR | VULN_COUNT| | |
251 | ====================================================================== | |
252 | | a923fd | PixBufIcon(linux)| linux | 192.168.1.2 (5) | 5 | | |
253 | """ | |
254 | ||
255 | hosts_model = Gtk.ListStore(str, GdkPixbuf.Pixbuf(), str, str, int) | |
256 | self.current_model = hosts_model | |
257 | for host in hosts: | |
258 | self.__add_host_to_model(host) | |
259 | ||
260 | return self.current_model | |
261 | ||
262 | def create_view(self, model): | |
263 | """Creates a view for the hosts model. | |
264 | It will contain two columns, the first with the OS icon given in | |
265 | the second column of the model. The second column of the view will | |
266 | be the string contained in the fourth column of the model. | |
267 | The first column of the view will be orderer according to the | |
268 | second column of the model, and the second column of the view will | |
269 | be ordered according to its fifth column. | |
270 | Will connect activation of a row with the on_click method | |
271 | """ | |
272 | ||
273 | self.view = Gtk.TreeView(model) | |
274 | self.view.set_activate_on_single_click(False) | |
275 | ||
276 | text_renderer = Gtk.CellRendererText() | |
277 | icon_renderer = Gtk.CellRendererPixbuf() | |
278 | ||
279 | column_hosts = Gtk.TreeViewColumn("Hosts", text_renderer, text=3) | |
280 | column_hosts.set_sort_column_id(4) | |
281 | column_hosts.set_sort_indicator(True) | |
282 | ||
283 | column_os = Gtk.TreeViewColumn("", icon_renderer, pixbuf=1) | |
284 | column_os.set_sort_column_id(2) | |
285 | column_os.set_sort_indicator(True) | |
286 | ||
287 | self.view.append_column(column_os) | |
288 | self.view.append_column(column_hosts) | |
289 | ||
290 | self.view.connect("row_activated", self.on_click) | |
291 | ||
292 | self.view.set_enable_search(True) | |
293 | self.view.set_search_column(2) | |
294 | ||
295 | return self.view | |
221 | def _find_host_id(self, object_): | |
222 | """Return the ID of the object's parent host.""" | |
223 | object_id = object_.getID() | |
224 | host_id = object_id.split(".")[0] | |
225 | return host_id | |
226 | ||
227 | def _is_host_in_model_by_host_id(self, host_id): | |
228 | """Return a boolean indicating if host_id is in the model""" | |
229 | return self.host_id_to_iter.get(host_id) is not None | |
230 | ||
231 | def _get_vuln_amount_from_model(self, host_iter): | |
232 | """Return the amount of vulns the model thinks host_iter has. | |
233 | ||
234 | @preconditions: host_iter in self.model | |
235 | """ | |
236 | return self.model[host_iter][4] | |
237 | ||
238 | def _vulns_ids_of_host(self, host): | |
239 | """Return a list of vulnerabilities IDs for the given host. | |
240 | It will return [] if host is None (or any other falsey value). | |
241 | """ | |
242 | return [v.getID() for v in host.getVulns()] if host else [] | |
243 | ||
244 | def _is_vuln_of_host(self, vuln_id, host_id): | |
245 | """Return a boolean indicating whether vuln_id is associated with the | |
246 | host of host_id. Potentially slow, as it makes a request to the server. | |
247 | """ | |
248 | host = self.get_single_host_function(host_id) | |
249 | return vuln_id in self._vulns_ids_of_host(host) | |
250 | ||
251 | def _add_single_host_to_model(self, host): | |
252 | """Add a single host to the model. Return None.""" | |
253 | vuln_count = host.getVulnAmount() | |
254 | os_icon, os_str = self.__decide_icon(host.getOS()) | |
255 | display_str = str(host) | |
256 | host_iter = self.model.append([host.id, os_icon, os_str, display_str, vuln_count]) | |
257 | self.host_id_to_iter[host.id] = host_iter | |
258 | self.host_amount_in_model += 1 | |
259 | ||
260 | def add_relevant_hosts_to_model(self, hosts): | |
261 | """Takes a list of hosts. Add the hosts to the model without going | |
262 | over the maximun size of the model. Return None. | |
263 | """ | |
264 | space_left_in_sidebar = 20 - self.host_amount_in_model | |
265 | relevant_hosts = hosts[0:space_left_in_sidebar] # just ignore those coming after | |
266 | map(self._add_single_host_to_model, relevant_hosts) | |
267 | ||
268 | def _update_single_host_name_in_model(self, host_id, host_iter): | |
269 | """Take a host_id and a host_iter. Changes the string representation | |
270 | of the host in the model. Potentially slow, makes a request to the server. | |
271 | Return None. | |
272 | ||
273 | @precondtions: host_iter must be in self.model | |
274 | """ | |
275 | host = self.get_single_host_function(host_id) | |
276 | new_name = host.getName() | |
277 | vuln_amount = self._get_vuln_amount_from_model(host_iter) | |
278 | new_string = "{0} ({1})".format(new_name, vuln_amount) | |
279 | self.model.set_value(host_iter, 3, new_string) | |
280 | ||
281 | def update_relevant_host_names_in_model(self, hosts): | |
282 | """Takes a list of hosts and updates their string representation | |
283 | in the model. Potentially slow, makes len(hosts) requests to the server. | |
284 | Return None. | |
285 | """ | |
286 | hosts_ids = map(lambda h: h.id, hosts) | |
287 | relevant_hosts = filter(self._is_host_in_model_by_host_id, hosts_ids) | |
288 | host_iters = map(lambda h: self.host_id_to_iter[h], relevant_hosts) | |
289 | map(self._update_single_host_name_in_model, relevant_hosts, host_iters) | |
290 | ||
291 | def _remove_single_host_from_model(self, host_id): | |
292 | """Remove the host of host_id from the model. Return None. | |
293 | ||
294 | @preconditions: host_id must be in self.host_id_to_iter, | |
295 | self.host_id_to_iter[host_id] must be in model | |
296 | """ | |
297 | host_iter = self.host_id_to_iter[host_id] | |
298 | del self.host_id_to_iter[host_id] | |
299 | self.model.remove(host_iter) | |
300 | self.host_amount_total -= 1 | |
301 | self.host_amount_in_model -= 1 | |
302 | ||
303 | def remove_relevant_hosts_from_model(self, host_ids): | |
304 | """Takes a list of host_ids and deletes the one found on the model | |
305 | from there. Return None.""" | |
306 | relevant_host_ids = filter(self._is_host_in_model_by_host_id, host_ids) | |
307 | map(self._remove_single_host_from_model, relevant_host_ids) | |
308 | ||
309 | def _modify_vuln_amount_of_single_host_in_model(self, host_id, new_vuln_amount): | |
310 | """Take a host_id and a new_vuln amount and modify the string representation | |
311 | and the vuln amount of the host of id host_id in the model according | |
312 | to the new_vuln_amount. Return None. | |
313 | ||
314 | @preconditions: host_id must be in self.host_id_to_iter, | |
315 | self.host_id_to_iter[host_id] must in the model. | |
316 | """ | |
317 | host_iter = self.host_id_to_iter[host_id] | |
318 | current_host_name = self.model[host_iter][3].split(" ")[0] | |
319 | new_host_string = "{0} ({1})".format(current_host_name, new_vuln_amount) | |
320 | self.model.set_value(host_iter, 4, new_vuln_amount) | |
321 | self.model.set_value(host_iter, 3, new_host_string) | |
322 | ||
323 | def _modify_vuln_amounts_of_hosts_in_model(self, host_ids, plus_one_or_minus_one): | |
324 | """Takes host_ids (a list of host ids) and a function which should | |
325 | add or take one from an input. Modify the string representation | |
326 | and the vuln_amount of the host_ids found in the model by adding or taking | |
327 | one vulnerability from them, according to the plus_one_or_minus_one | |
328 | function. Return None. | |
329 | """ | |
330 | relevant_host_ids = filter(self._is_host_in_model_by_host_id, host_ids) | |
331 | host_iters = map(lambda h: self.host_id_to_iter[h], relevant_host_ids) | |
332 | vuln_amount_of_those_hosts = map(self._get_vuln_amount_from_model, host_iters) | |
333 | new_vuln_amounts = map(plus_one_or_minus_one, vuln_amount_of_those_hosts) | |
334 | map(self._modify_vuln_amount_of_single_host_in_model, relevant_host_ids, new_vuln_amounts) | |
335 | ||
336 | def add_relevant_vulns_to_model(self, vulns): | |
337 | """Takes vulns, a list of vulnerability object, and adds them to the | |
338 | model by modifying their corresponding hosts in the model. Return None. | |
339 | """ | |
340 | host_ids = map(self._find_host_id, vulns) | |
341 | self._modify_vuln_amounts_of_hosts_in_model(host_ids, lambda x: x+1) | |
342 | ||
343 | def remove_relevant_vulns_from_model(self, vuln_ids): | |
344 | """Takes vulns_ids, a list of vuln ids, and removes them from | |
345 | the model by modifying their corresponding hosts in the model. | |
346 | Return None. | |
347 | """ | |
348 | host_ids = map(lambda v: v.getID().split(".")[0], vulns_ids) | |
349 | self._modify_vuln_amounts_of_hosts_in_model(host_ids, lambda x: x-1) | |
350 | ||
351 | def add_host(self, host): | |
352 | """Adds host to the model. Do not use for hosts added after | |
353 | the initial load of the workspace, use add_host_after_initial_load | |
354 | for that. | |
355 | """ | |
356 | self.add_relevant_hosts_to_model([host]) | |
357 | ||
358 | def remove_host(self, host_id): | |
359 | """Remove host of host_id from the model, if found in it.""" | |
360 | self.remove_relevant_hosts_from_model([host_id]) | |
361 | ||
362 | def update_host_name(self, host): | |
363 | """Update the host name of host in the model, if found in it.""" | |
364 | self.update_relevant_host_names_in_model([host]) | |
365 | ||
366 | def add_vuln(self, vuln): | |
367 | """Adds vuln to the corresponding host, if the host is found in the model.""" | |
368 | self.add_relevant_vulns_to_model([vuln]) | |
369 | ||
370 | def remove_vuln(self, vuln_id): | |
371 | """Removes a vuln from its host, if the host is found in the model.""" | |
372 | self.remove_relevant_vulns_from_model([vuln_id]) | |
373 | ||
374 | def add_host_after_initial_load(self, host): | |
375 | """Adds a host after the initial load of the sidebar. | |
376 | This implies modifiying the total host amount and potentially | |
377 | updating the progress buttons senstivity. | |
378 | """ | |
379 | self.host_amount_total += 1 | |
380 | self.add_host(host) | |
381 | self.set_move_buttons_sensitivity() | |
296 | 382 | |
297 | 383 | def add_object(self, obj): |
384 | """Add and object obj of unkwonw type to the model, if found there""" | |
298 | 385 | object_type = obj.class_signature |
299 | 386 | if object_type == 'Host': |
300 | self.__add_host_to_model_after_initial_load(obj) | |
387 | self.add_host_after_initial_load(host=obj) | |
301 | 388 | if object_type == "Vulnerability" or object_type == "VulnerabilityWeb": |
302 | self.__add_vuln_to_model(obj) | |
389 | self.add_vuln(vuln=obj) | |
303 | 390 | |
304 | 391 | def remove_object(self, obj_id): |
305 | if obj_id.count('.') == 0: | |
306 | self.__remove_host_from_model(obj_id) | |
392 | """Remove an obj of id obj_id from the model, if found there""" | |
393 | potential_host_id = obj_id.split('.')[0] | |
394 | is_host = len(obj_id.split('.')) == 1 | |
395 | if is_host: | |
396 | self.remove_host(host_id=obj_id) | |
397 | # elif not is_host and self._is_vuln_of_host(vuln_id=obj_id, host_id=potential_host_id): | |
398 | # self.remove_vuln(vuln_id=obj_id) | |
307 | 399 | else: |
308 | host_id = obj_id.split(".")[0] | |
309 | self.__remove_vuln_from_model(host_id) | |
400 | # Since we don't know the type of the delete object, | |
401 | # we have to assume it's a vulnerability so the host's | |
402 | # name is updated with the ammount of vulns | |
403 | host = self.get_single_host_function(potential_host_id) | |
404 | if host: | |
405 | self._modify_vuln_amount_of_single_host_in_model(host.getID(), host.getVulnAmount()) | |
310 | 406 | |
311 | 407 | def update_object(self, obj): |
408 | """Update the obj in the model, if found there""" | |
312 | 409 | object_type = obj.class_signature |
313 | 410 | if object_type == 'Host': |
314 | self.__update_host_in_model(obj) | |
315 | ||
316 | def redo(self, hosts, total_host_amount, page=0): | |
317 | """Creates a new model from an updated list of hosts and adapts | |
318 | the view to reflect the changes""" | |
319 | self.page = page | |
320 | self.host_id_to_iter = {} | |
321 | model = self.create_model(hosts) | |
322 | self.redo_view(model) | |
323 | self.host_amount = total_host_amount | |
324 | self.set_move_buttons_sensitivity() | |
325 | self.progress_label.set_label("{0} / {1}".format(self.page+1, self.compute_total_number_of_pages()+1)) | |
326 | ||
327 | def redo_view(self, model): | |
328 | """Updates the view of the object with a new model""" | |
329 | self.view.set_model(model) | |
330 | self.progress_label.set_label("{0} / {1}".format(self.page+1, self.compute_total_number_of_pages()+1)) | |
411 | self.update_host_name(obj) | |
331 | 412 | |
332 | 413 | def on_click(self, tree_view, path, column): |
333 | 414 | """Sends the host_id of the clicked host back to the application""" |
334 | tree_iter = self.current_model.get_iter(path) | |
335 | host_id = self.current_model[tree_iter][0] | |
415 | tree_iter = self.model.get_iter(path) | |
416 | host_id = self.model[tree_iter][0] | |
336 | 417 | self.open_dialog_callback(host_id) |
337 | 418 | |
338 | 419 | def set_move_buttons_sensitivity(self): |
339 | if self.page > 0: | |
340 | self.prev_button.set_sensitive(True) | |
341 | else: | |
342 | self.prev_button.set_sensitive(False) | |
343 | if self.compute_total_number_of_pages() > self.page: | |
344 | self.next_button.set_sensitive(True) | |
345 | else: | |
346 | self.next_button.set_sensitive(False) | |
347 | ||
348 | def compute_total_number_of_pages(self): | |
349 | return int(math.ceil(self.host_amount / 20)) | |
350 | ||
351 | @scrollable(width=160) | |
352 | def scrollable_view(self): | |
353 | return self.view | |
420 | """Update the sensitity of the prev and next buttons according to the | |
421 | page we're on and the total number of pages. | |
422 | """ | |
423 | self.prev_button.set_sensitive(self.page > 0) # its a boolean! | |
424 | ||
425 | # we add one to self.page 'cause they start at zero, but number of pages is | |
426 | # always at least one :) | |
427 | self.next_button.set_sensitive(self.number_of_pages > self.page + 1) | |
354 | 428 | |
355 | 429 | def get_box(self): |
356 | search_entry= self.create_search_entry() | |
430 | """Return the sidebar_box, which contains all the elements of the | |
431 | sidebar. | |
432 | """ | |
433 | search_entry = self.create_search_entry() | |
357 | 434 | scrollable_view = self.scrollable_view() |
358 | 435 | button_box = self.button_box() |
359 | 436 | sidebar_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) |
363 | 440 | return sidebar_box |
364 | 441 | |
365 | 442 | def button_box(self): |
443 | """Return the button_box, which contains the prev and next button | |
444 | as well the progress label. Creates the instance attributes | |
445 | self.prev_button and self.next_button. | |
446 | """ | |
366 | 447 | button_box = Gtk.Box() |
367 | button_box.override_background_color(Gtk.StateType.NORMAL, Gdk.RGBA(.1,.1,.1,.1)) | |
448 | button_box.override_background_color(Gtk.StateType.NORMAL, Gdk.RGBA(.1, .1, .1, .1)) | |
368 | 449 | self.prev_button = Gtk.Button.new_with_label("<<") |
369 | 450 | self.next_button = Gtk.Button.new_with_label(">>") |
370 | 451 | self.prev_button.connect("clicked", self.on_click_move_page, lambda x: x-1) |
374 | 455 | button_box.pack_start(self.next_button, True, True, 0) |
375 | 456 | return button_box |
376 | 457 | |
377 | def on_click_move_page(self, button, add_one_or_take_one_from, *args, **kwargs): | |
378 | self.page = add_one_or_take_one_from(self.page) | |
379 | hosts = self.get_host_function(page=str(self.page), page_size=20, | |
380 | sort='vulns', sort_dir='desc') | |
381 | model = self.create_model(hosts) | |
382 | self.redo_view(model) | |
383 | self.set_move_buttons_sensitivity() | |
458 | def on_click_move_page(self, button, change_page_number_func, *args, **kwargs): | |
459 | """What happens when the user clicks on either self.prev_button | |
460 | or self.next_button. Change self.page according to the change_page_number_func, | |
461 | and resets the model to a new list of hosts requested from the server. | |
462 | """ | |
463 | self.page = change_page_number_func(self.page) | |
464 | hosts = self.get_hosts_function(page=str(self.page), | |
465 | page_size=20, | |
466 | name=self.search_entry.get_text(), | |
467 | sort='vulns', | |
468 | sort_dir='desc') | |
469 | self.reset_model(hosts) | |
470 | self.update_progress_label() | |
471 | ||
472 | def update_progress_label(self): | |
473 | """Updates the progress label with values from self.page and self.number_of_pages.""" | |
474 | self.progress_label.set_label("{0} / {1}".format(self.page+1, self.number_of_pages)) | |
384 | 475 | |
385 | 476 | def create_search_entry(self): |
386 | 477 | """Returns a simple search entry""" |
387 | search_entry = Gtk.Entry() | |
388 | search_entry.set_placeholder_text("Search a host by name...") | |
389 | search_entry.connect("activate", self.on_search_enter_key) | |
390 | search_entry.show() | |
391 | return search_entry | |
478 | self.search_entry = Gtk.Entry() | |
479 | self.search_entry.set_placeholder_text("Search a host by name...") | |
480 | self.search_entry.connect("activate", self.on_search_enter_key) | |
481 | self.search_entry.show() | |
482 | return self.search_entry | |
392 | 483 | |
393 | 484 | def on_search_enter_key(self, entry): |
394 | """When the users preses enter, if the workspace exists, | |
395 | select it. If not, present the window to create a workspace with | |
396 | that name""" | |
397 | search = entry.get_text() | |
398 | if search == "": | |
399 | hosts = self.get_host_function(page=0, page_size=20, sort='vulns', | |
400 | sort_dir='desc') | |
401 | model = self.create_model(hosts) | |
402 | self.redo_view(model) | |
403 | self.set_move_buttons_sensitivity() | |
404 | else: | |
405 | hosts = self.get_host_function(name=search, sort='name', | |
406 | sort_dir='desc') | |
407 | model = self.create_model(hosts) | |
408 | self.redo_view(model) | |
409 | self.prev_button.set_sensitive(False) | |
410 | self.next_button.set_sensitive(False) | |
485 | """Rebuild the model with the search, but self.page stays the same. | |
486 | """ | |
487 | self.on_click_move_page(Gtk.Button(), lambda i: i) | |
411 | 488 | |
412 | 489 | |
413 | 490 | class WorkspaceSidebar(Gtk.Widget): |
544 | 621 | |
545 | 622 | # change the workspace to the newly selected |
546 | 623 | self.change_ws(self.get_selected_ws_name()) |
547 | return True # prevents the click from selecting a workspace | |
548 | # this is handled manually by us on self.change_ws | |
624 | return True # prevents the click from selecting a workspace | |
625 | # this is handled manually by us on self.change_ws | |
549 | 626 | |
550 | 627 | def on_right_click(self, view, event): |
551 | 628 | """On click, check if it was a right click. If it was, |
607 | 684 | """ |
608 | 685 | # NOTE. this function should really be replaced by a dictionary |
609 | 686 | for ws_iter in self.valid_ws_iters: |
610 | if self.workspace_model[ws_iter][0] == ws_name: | |
611 | return ws_iter | |
612 | else: | |
613 | return None | |
687 | if self.workspace_model.iter_is_valid(ws_iter): | |
688 | if self.workspace_model[ws_iter][0] == ws_name: | |
689 | return ws_iter | |
690 | return None | |
614 | 691 | |
615 | 692 | def select_ws_by_name(self, ws_name): |
616 | 693 | """Selects the workspace by name ws_name""" |
662 | 739 | """Returns the ScrolledWindow used to contain the view""" |
663 | 740 | return self.textView |
664 | 741 | |
742 | def news_button(self, url, description): | |
743 | ||
744 | anchor = self.textBuffer.create_child_anchor( | |
745 | self.textBuffer.get_end_iter()) | |
746 | ||
747 | button = Gtk.Button() | |
748 | label = Gtk.Label() | |
749 | ||
750 | label.set_markup( | |
751 | 'Faraday News: <a href="' + url + '"> ' + | |
752 | description + "</a>") | |
753 | ||
754 | button.add(label) | |
755 | button.set_relief(Gtk.ReliefStyle.NONE) | |
756 | ||
757 | button.connect("clicked", lambda o: webbrowser.open(url)) | |
758 | ||
759 | label.show() | |
760 | button.show() | |
761 | self.update("\n") | |
762 | ||
763 | self.textView.add_child_at_anchor(button, anchor) | |
764 | ||
665 | 765 | def customEvent(self, text): |
666 | 766 | """Filters event so that only those with type 3131 get to the log. |
667 | 767 | Also split them, so we can add the correct formatting to the first |
668 | 768 | part of the message""" |
669 | 769 | |
670 | text = text.split('-') | |
770 | text = text.split('-', 1) | |
671 | 771 | if text[0] == "INFO ": |
672 | 772 | self.update("[ " + text[0] + "]", self.bold) |
673 | if text[0] == "DEBUG ": | |
773 | elif text[0] == "DEBUG ": | |
674 | 774 | self.update("[ " + text[0] + "]", self.bold, self.green) |
675 | if text[0] == "ERROR " or text[0] == "CRITICAL: ": | |
775 | elif text[0] == "ERROR " or text[0] == "CRITICAL: ": | |
676 | 776 | self.update("[ " + text[0] + "]", self.bold, self.red) |
677 | if text[0] == "WARNING ": | |
777 | elif text[0] == "WARNING ": | |
678 | 778 | self.update("[ " + text[0] + "]", self.bold, self.orange) |
679 | if text[0] == "NOTIFICATION ": | |
779 | elif text[0] == "NOTIFICATION ": | |
680 | 780 | self.update("[ " + text[0] + "]", self.bold, self.blue) |
781 | elif text[0] == "NEWS ": | |
782 | # Format of data : 'NEWS - URL|DESC' | |
783 | data_url_desc = text[1].split('|') | |
784 | self.news_button(data_url_desc[0], data_url_desc[1]) | |
785 | return | |
681 | 786 | |
682 | 787 | self.update("-" + '-'.join(text[1:]) + "\n") |
683 | 788 |
9 | 9 | import threading, time, requests |
10 | 10 | from model.guiapi import notification_center |
11 | 11 | from decorators import safe_io_with_server |
12 | from persistence.server import models | |
12 | from persistence.server import models, server_io_exceptions | |
13 | 13 | |
14 | 14 | class ServerIO(object): |
15 | 15 | def __init__(self, active_workspace): |
69 | 69 | def get_object(self, object_signature, object_id): |
70 | 70 | return models.get_object(self.active_workspace, object_signature, object_id) |
71 | 71 | |
72 | @safe_io_with_server(None) | |
73 | def get_host(self, host_id): | |
74 | return models.get_host(self.active_workspace, host_id) | |
75 | ||
72 | 76 | @safe_io_with_server((0,0,0,0)) |
73 | 77 | def get_workspace_numbers(self): |
74 | 78 | return models.get_workspace_numbers(self.active_workspace) |
110 | 114 | # not a change really right? |
111 | 115 | return None |
112 | 116 | |
117 | is_deleted = bool(change.get('deleted')) | |
118 | if obj_type is None and not is_deleted: | |
119 | # if obj_type is None it's a deleted change. retrieve its type later | |
120 | return None | |
121 | ||
113 | 122 | if obj_type is not None and obj_type not in cool_types: |
114 | # if obj_type is None it's a deleted change. retrieve its type later | |
115 | 123 | return None |
116 | 124 | |
117 | 125 | if change['changes'][0]['rev'] == local_changes.get(change['id']): |
162 | 170 | revision = change.get("changes")[-1].get('rev') |
163 | 171 | notification_dispatcher(obj_id, obj_type, obj_name, |
164 | 172 | deleted, revision) |
165 | except requests.exceptions.RequestException: | |
173 | except server_io_exceptions.ChangesStreamStoppedAbruptly: | |
166 | 174 | notification_center.WorkspaceProblem() |
167 | 175 | return False |
168 | 176 | else: |
93 | 93 | |
94 | 94 | def addObject(self, new_object): |
95 | 95 | self._notifyWidgets(events.AddObjectCustomEvent(new_object)) |
96 | ||
97 | def sendCustomLog(self, log_obj): | |
98 | self._notifyWidgets(events.LogCustomEvent(log_obj)) |
83 | 83 | |
84 | 84 | def run(self): |
85 | 85 | tmp_timer = 0 |
86 | tmp_timer_sentinel = 0 | |
86 | 87 | while not self._stop: |
87 | 88 | |
88 | 89 | time.sleep(1) |
89 | 90 | tmp_timer += 1 |
91 | tmp_timer_sentinel += 1 | |
92 | ||
93 | if tmp_timer_sentinel == 1800: | |
94 | tmp_timer_sentinel = 0 | |
95 | self.launchSentinel() | |
96 | ||
90 | 97 | if tmp_timer == self.timer: |
91 | 98 | try: |
92 | 99 | self.syncReports() |
99 | 106 | |
100 | 107 | def stop(self): |
101 | 108 | self._stop = True |
109 | ||
110 | def launchSentinel(self): | |
111 | psettings = CONF.getPluginSettings() | |
112 | ||
113 | name, cmd = "Sentinel", "sentinel" | |
114 | if name in psettings: | |
115 | if psettings[name]['settings']['Enable'] == "1": | |
116 | getLogger(self).info("Plugin Started: Sentinel") | |
117 | self.processor.onlinePlugin(cmd) | |
118 | getLogger(self).info("Plugin Ended: Sentinel") | |
102 | 119 | |
103 | 120 | def syncReports(self): |
104 | 121 | """ |
11 | 11 | |
12 | 12 | from model.workspace import Workspace |
13 | 13 | from persistence.server.models import create_workspace, get_workspaces_names, get_workspace, delete_workspace |
14 | from persistence.server.server import Unauthorized | |
14 | from persistence.server.server_io_exceptions import Unauthorized | |
15 | 15 | from model.guiapi import notification_center |
16 | 16 | |
17 | 17 | from config.configuration import getInstanceConfiguration |
43 | 43 | try: |
44 | 44 | create_workspace(name, description=desc, start_date=start_date, |
45 | 45 | finish_date=finish_date, customer=customer) |
46 | # XXX: Remove this hack! Only for testing | |
47 | time.sleep(2) | |
48 | 46 | except Unauthorized: |
49 | 47 | raise WorkspaceException( |
50 | 48 | ("You're not authorized to create workspaces\n" |
59 | 57 | self.mappersManager.createMappers(name) |
60 | 58 | self.setActiveWorkspace(workspace) |
61 | 59 | notification_center.workspaceChanged(workspace) |
62 | # XXX: REIMPLEMENT THIS | |
63 | #self.changesManager.watch(self.mappersManager, dbConnector) | |
64 | 60 | return name |
65 | 61 | |
66 | 62 | def openWorkspace(self, name): |
90 | 90 | # register all the api functions to be exposed by the server |
91 | 91 | _xmlrpc_api_server.register_function(createAndAddHost) |
92 | 92 | _xmlrpc_api_server.register_function(createAndAddInterface) |
93 | _xmlrpc_api_server.register_function(createAndAddServiceToApplication) | |
94 | 93 | _xmlrpc_api_server.register_function(createAndAddServiceToInterface) |
95 | _xmlrpc_api_server.register_function(createAndAddApplication) | |
96 | 94 | _xmlrpc_api_server.register_function(createAndAddNoteToService) |
97 | 95 | _xmlrpc_api_server.register_function(createAndAddNoteToHost) |
98 | 96 | _xmlrpc_api_server.register_function(createAndAddNoteToNote) |
101 | 99 | _xmlrpc_api_server.register_function(createAndAddVulnToHost) |
102 | 100 | _xmlrpc_api_server.register_function(addHost) |
103 | 101 | _xmlrpc_api_server.register_function(addInterface) |
104 | _xmlrpc_api_server.register_function(addServiceToApplication) | |
105 | 102 | _xmlrpc_api_server.register_function(addServiceToInterface) |
106 | _xmlrpc_api_server.register_function(addApplication) | |
107 | 103 | _xmlrpc_api_server.register_function(newHost) |
108 | 104 | _xmlrpc_api_server.register_function(newInterface) |
109 | 105 | _xmlrpc_api_server.register_function(newService) |
110 | _xmlrpc_api_server.register_function(newApplication) | |
111 | 106 | _xmlrpc_api_server.register_function(devlog) |
112 | 107 | |
113 | 108 | #TODO: check if all necessary APIs are registered here!! |
166 | 161 | return interface.getID() |
167 | 162 | return None |
168 | 163 | |
169 | def createAndAddApplication(host_id, name, status = "running", version = "unknown"): | |
170 | application = newApplication(name, status, version) | |
171 | if addApplication(host_id, application): | |
172 | return application.getID() | |
173 | return None | |
174 | ||
175 | def createAndAddServiceToApplication(host_id, application_id, name, protocol = "tcp?", | |
176 | ports = [], status = "running", version = "unknown", description = ""): | |
177 | service = newService(name, protocol, ports, status, version, description) | |
178 | if addServiceToApplication(host_id, application_id, service): | |
179 | return service.getID() | |
180 | return None | |
181 | ||
182 | 164 | def createAndAddServiceToInterface(host_id, interface_id, name, protocol = "tcp?", |
183 | 165 | ports = [], status = "running", version = "unknown", description = ""): |
184 | 166 | service = newService(name, protocol, ports, status, version, description, parent_id=interface_id) |
197 | 179 | def createAndAddVulnToInterface(host_id, interface_id, name, desc, ref, severity, resolution): |
198 | 180 | vuln = newVuln(name, desc, ref, severity, resolution, parent_id=interface_id) |
199 | 181 | if addVulnToInterface(host_id, interface_id, vuln): |
200 | return vuln.getID() | |
201 | return None | |
202 | ||
203 | def createAndAddVulnToApplication(host_id, application_id, name, desc, ref, severity, resolution): | |
204 | vuln = newVuln(name, desc, ref, severity, resolution) | |
205 | if addVulnToApplication(host_id, application_id, vuln): | |
206 | 182 | return vuln.getID() |
207 | 183 | return None |
208 | 184 | |
238 | 214 | return note.getID() |
239 | 215 | return None |
240 | 216 | |
241 | def createAndAddNoteToApplication(host_id, application_id, name, text): | |
242 | note = newNote(text) | |
243 | if addNoteToApplication(host_id, application_id, note): | |
244 | return note.getID() | |
245 | return None | |
246 | ||
247 | 217 | def createAndAddNoteToService(host_id, service_id, name, text): |
248 | 218 | note = newNote(name, text, parent_id=service_id) |
249 | 219 | if addNoteToService(host_id, service_id, note): |
280 | 250 | return True |
281 | 251 | return False |
282 | 252 | |
283 | def addApplication(host_id, application): | |
284 | if application is not None: | |
285 | __model_controller.addApplicationASYNC(host_id, application) | |
286 | return True | |
287 | return False | |
288 | ||
289 | def addServiceToApplication(host_id, application_id, service): | |
290 | if service is not None: | |
291 | __model_controller.addServiceToApplicationASYNC(host_id, application_id, service) | |
292 | return True | |
293 | return False | |
294 | 253 | |
295 | 254 | def addServiceToInterface(host_id, interface_id, service): |
296 | 255 | if service is not None: |
309 | 268 | def addVulnToInterface(host_id, interface_id, vuln): |
310 | 269 | if vuln is not None: |
311 | 270 | __model_controller.addVulnToInterfaceASYNC(host_id, interface_id, vuln) |
312 | return True | |
313 | return False | |
314 | ||
315 | def addVulnToApplication(host_id, application_id, vuln): | |
316 | if vuln is not None: | |
317 | __model_controller.addVulnToApplicationASYNC(host_id, application_id, vuln) | |
318 | 271 | return True |
319 | 272 | return False |
320 | 273 | |
331 | 284 | return True |
332 | 285 | return False |
333 | 286 | |
334 | ||
335 | ||
336 | 287 | # Notes |
337 | ||
338 | ||
339 | ||
340 | 288 | |
341 | 289 | def addNoteToHost(host_id, note): |
342 | 290 | if note is not None: |
350 | 298 | return True |
351 | 299 | return False |
352 | 300 | |
353 | def addNoteToApplication(host_id, application_id, note): | |
354 | if note is not None: | |
355 | __model_controller.addNoteToApplicationASYNC(host_id, application_id, note) | |
356 | return True | |
357 | return False | |
358 | ||
359 | 301 | def addNoteToService(host_id, service_id, note): |
360 | 302 | if note is not None: |
361 | 303 | __model_controller.addNoteToServiceASYNC(host_id, service_id, note) |
382 | 324 | __model_controller.delHostASYNC(hostname) |
383 | 325 | return True |
384 | 326 | |
385 | def delApplication(hostname,appname): | |
386 | __model_controller.delApplicationASYNC(hostname,appname) | |
387 | return True | |
388 | 327 | |
389 | 328 | def delInterface(hostname,intname): |
390 | 329 | __model_controller.delInterfaceASYNC(hostname,intname) |
398 | 337 | __model_controller.delServiceFromInterfaceASYNC(hostname,intname,service) |
399 | 338 | return True |
400 | 339 | |
401 | def delServiceFromApplication(hostname, appname, service): | |
402 | __model_controller.delServiceFromApplicationASYNC(hostname,appname,service) | |
403 | return True | |
404 | 340 | |
405 | 341 | # Vulnerability |
406 | #------------------------------------------------------------------------------- | |
407 | def delVulnFromApplication(vuln, hostname, appname): | |
408 | __model_controller.delVulnFromApplicationASYNC(hostname, appname, vuln) | |
409 | return True | |
410 | 342 | #------------------------------------------------------------------------------- |
411 | 343 | def delVulnFromInterface(vuln, hostname, intname): |
412 | 344 | __model_controller.delVulnFromInterfaceASYNC(hostname,intname, vuln) |
423 | 355 | |
424 | 356 | # Notes |
425 | 357 | #------------------------------------------------------------------------------- |
426 | def delNoteFromApplication(note, hostname, appname): | |
427 | __model_controller.delNoteFromApplicationASYNC(hostname, appname, note) | |
428 | return True | |
429 | #------------------------------------------------------------------------------- | |
430 | 358 | def delNoteFromInterface(note, hostname, intname): |
431 | 359 | __model_controller.delNoteFromInterfaceASYNC(hostname,intname, note) |
432 | 360 | return True |
449 | 377 | # CREATION APIS |
450 | 378 | #------------------------------------------------------------------------------- |
451 | 379 | |
452 | ||
453 | 380 | def newHost(name, os = "Unknown"): |
454 | 381 | """ |
455 | 382 | It creates and returns a Host object. |
456 | 383 | The object created is not added to the model. |
457 | 384 | """ |
458 | 385 | return __model_controller.newHost(name, os) |
459 | ||
460 | 386 | |
461 | 387 | def newInterface(name = "", mac = "00:00:00:00:00:00", |
462 | 388 | ipv4_address = "0.0.0.0", ipv4_mask = "0.0.0.0", |
521 | 447 | The created object is not added to the model. |
522 | 448 | """ |
523 | 449 | return __model_controller.newCred(username, password, parent_id) |
524 | ||
525 | ||
526 | #------------------------------------------------------------------------------- | |
527 | def newApplication(name, status = "running", version = "unknown"): | |
528 | """ | |
529 | It creates and returns an Application object. | |
530 | The created object is not added to the model. | |
531 | """ | |
532 | return model.common.factory.createModelObject("HostApplication",name, | |
533 | status = status, | |
534 | version = version) | |
535 | 450 | |
536 | 451 | #------------------------------------------------------------------------------- |
537 | 452 | |
653 | 568 | def pluginStart(name): |
654 | 569 | __model_controller.addPluginStart(name) |
655 | 570 | |
656 | ||
657 | 571 | def pluginEnd(name): |
658 | 572 | __model_controller.addPluginEnd(name) |
659 | 573 | |
670 | 584 | def getLocalDefaultGateway(): |
671 | 585 | return gateway() |
672 | 586 | |
673 | ||
674 | 587 | def getActiveWorkspace(): |
675 | 588 | return __workspace_manager.getActiveWorkspace() |
9 | 9 | import signal |
10 | 10 | import threading |
11 | 11 | import requests |
12 | ||
13 | from json import loads | |
14 | from time import sleep | |
12 | 15 | |
13 | 16 | from model.controller import ModelController |
14 | 17 | from managers.workspace_manager import WorkspaceManager |
37 | 40 | threading.Thread.__init__(self) |
38 | 41 | self.__event = threading.Event() |
39 | 42 | |
43 | def sendNewstoLogGTK(self, json_response): | |
44 | ||
45 | information = loads(json_response) | |
46 | ||
47 | for news in information["news"]: | |
48 | model.guiapi.notification_center.sendCustomLog( | |
49 | "NEWS -" + news["url"] + "|" + news["description"]) | |
50 | ||
40 | 51 | def run(self): |
41 | 52 | while not self.__event.is_set(): |
42 | 53 | try: |
54 | sleep(5) | |
43 | 55 | res = requests.get( |
44 | 56 | "https://www.faradaysec.com/scripts/updatedb.php", |
45 | 57 | params={'version': CONF.getVersion()}, |
46 | 58 | timeout=1, |
47 | 59 | verify=True) |
60 | ||
61 | self.sendNewstoLogGTK(res.text) | |
62 | ||
48 | 63 | except Exception: |
49 | model.api.devlog("CWE database couldn't be updated") | |
64 | model.api.devlog( | |
65 | "NEWS: Can't connect to faradaysec.com...") | |
66 | ||
50 | 67 | self.__event.wait(43200) |
51 | 68 | |
52 | 69 | def stop(self): |
4 | 4 | |
5 | 5 | ''' |
6 | 6 | import sys |
7 | import os | |
8 | 7 | import traceback |
9 | 8 | import threading |
10 | 9 | import SimpleXMLRPCServer |
11 | 10 | import xmlrpclib |
12 | from utils.decorators import updateLocalMetadata | |
13 | import json | |
14 | import model | |
15 | from conflict import ConflictUpdate | |
16 | from model.diff import ModelObjectDiff, MergeSolver | |
17 | 11 | |
18 | 12 | try: |
19 | 13 | import model.api as api |
20 | 14 | except AttributeError: |
21 | 15 | import api |
22 | from utils.common import * | |
23 | ||
24 | #----------- Metadata history for timeline support, prob. we should move this out model common | |
25 | ||
26 | from time import time | |
27 | import cPickle as pickle | |
16 | ||
28 | 17 | from config.configuration import getInstanceConfiguration |
29 | 18 | CONF = getInstanceConfiguration() |
30 | 19 | |
31 | """ | |
32 | Contains base classes used to represent the application model | |
33 | and some other common objects and functions used in the model | |
34 | """ | |
35 | ||
36 | ||
37 | class MetadataUpdateActions(object): | |
38 | """Constants for the actions made on the update""" | |
39 | UNDEFINED = -1 | |
40 | CREATE = 0 | |
41 | UPDATE = 1 | |
42 | ||
43 | ||
44 | class Metadata(object): | |
45 | """To save information about the modification of ModelObjects. | |
46 | All members declared public as this is only a wrapper""" | |
47 | ||
48 | class_signature = "Metadata" | |
49 | ||
50 | def __init__(self, user): | |
51 | self.creator = user | |
52 | self.owner = user | |
53 | self.create_time = time() | |
54 | self.update_time = time() | |
55 | self.update_user = user | |
56 | self.update_action = MetadataUpdateActions.CREATE | |
57 | self.update_controller_action = self.__getUpdateAction() | |
58 | ||
59 | def toDict(self): | |
60 | return self.__dict__ | |
61 | ||
62 | def fromDict(self, dictt): | |
63 | for k, v in dictt.items(): | |
64 | setattr(self, k, v) | |
65 | return self | |
66 | ||
67 | ||
68 | def update(self, user, action = MetadataUpdateActions.UPDATE): | |
69 | """Update the local metadata giving a user and an action. | |
70 | Update time gets modified to the current system time""" | |
71 | self.update_user = user | |
72 | self.update_time = time() | |
73 | self.update_action = action | |
74 | ||
75 | self.update_controller_action = self.__getUpdateAction() | |
76 | ||
77 | # api.devlog("Updating object (%s) metadata for user: (%s), utime = (%.4f), action=(%d), controller action = (%s)" | |
78 | # % (self, self.update_user, self.update_time, self.update_action, self.update_controller_action)) | |
79 | ||
80 | def __getUpdateAction(self): | |
81 | """This private method grabs the stackframes in look for the controller | |
82 | call that generated the update""" | |
83 | ||
84 | l_strace = traceback.extract_stack(limit = 10) | |
85 | controller_funcallnames = [ x[2] for x in l_strace if "controller" in x[0] ] | |
86 | ||
87 | if controller_funcallnames: | |
88 | return "ModelControler." + " ModelControler.".join(controller_funcallnames) | |
89 | return "No model controller call" | |
90 | ||
91 | ||
92 | class ModelObject(object): | |
93 | """ | |
94 | This is the base class for every object we need to represent in the | |
95 | system (like hosts, services, etc) | |
96 | It defines some generic methods to handle internal attributes that are | |
97 | dictionaries. It also has generic methods to deal with notes & vulns | |
98 | since every object could have them. | |
99 | """ | |
100 | # this static attribute used with a factory | |
101 | class_signature = "ModelObject" | |
102 | ||
103 | def __init__(self, parent_id=None): | |
104 | self._name = "" | |
105 | self._id = None | |
106 | self._parent_id = parent_id | |
107 | self._parent = None | |
108 | ||
109 | self.owner = api.getLoggedUser() | |
110 | self._metadata = Metadata(self.owner) | |
111 | ||
112 | # indicates if object was owned somehow | |
113 | # we could use this property to put a different color on the GUI | |
114 | self._is_owned = False | |
115 | ||
116 | # a custom description given by the user | |
117 | # this can be used to explain the purpose of the object | |
118 | self.description = "" | |
119 | ||
120 | self.publicattrs = {'Description':'description', | |
121 | 'Name':'getName','Owned':'isOwned' | |
122 | } | |
123 | ||
124 | self.publicattrsrefs = {'Description': '_description', | |
125 | 'Name': '_name','Owned': '_is_owned' | |
126 | } | |
127 | ||
128 | self._updatePublicAttributes() | |
129 | ||
130 | #TODO: I think notes and vulns should be a dict | |
131 | self._notes = {} | |
132 | self._vulns = {} | |
133 | self._creds = {} | |
134 | self.evidences = [] | |
135 | ||
136 | self.updates = [] | |
137 | ||
138 | def accept(self, visitor): | |
139 | visitor.visit(self) | |
140 | ||
141 | def defaultValues(self): | |
142 | return [-1, 0, '', 'unknown', None, [], {}] | |
143 | ||
144 | def __getAttribute(self, key): | |
145 | """ Looks for the attribute beneth the public attribute dict """ | |
146 | return self.publicattrsrefs.get(key) | |
147 | ||
148 | def propertyTieBreaker(self, key, prop1, prop2): | |
149 | """ Breakes the conflict between two properties. If either of them | |
150 | is a default value returns the true and only. | |
151 | If neither returns the default value. | |
152 | If conflicting returns a tuple with the values """ | |
153 | if prop1 in self.defaultValues(): return prop2 | |
154 | elif prop2 in self.defaultValues(): return prop1 | |
155 | elif self.tieBreakable(key): return self.tieBreak(key, prop1, prop2) | |
156 | else: return (prop1, prop2) | |
157 | ||
158 | def tieBreakable(self, key): | |
159 | return False | |
160 | ||
161 | def tieBreak(self, key, prop1, prop2): | |
162 | return None | |
163 | ||
164 | def addUpdate(self, newModelObject): | |
165 | conflict = False | |
166 | diff = ModelObjectDiff(self, newModelObject) | |
167 | for k, v in diff.getPropertiesDiff().items(): | |
168 | attribute = self.__getAttribute(k) | |
169 | prop_update = self.propertyTieBreaker(attribute, *v) | |
170 | ||
171 | if (not isinstance(prop_update, tuple) or | |
172 | CONF.getMergeStrategy()): | |
173 | # if there's a strategy set by the user, apply it | |
174 | if isinstance(prop_update, tuple): | |
175 | prop_update = MergeSolver( | |
176 | CONF.getMergeStrategy() | |
177 | ).solve(prop_update[0], prop_update[1]) | |
178 | ||
179 | setattr(self, attribute, prop_update) | |
180 | else: | |
181 | conflict = True | |
182 | if conflict: | |
183 | self.updates.append(ConflictUpdate(self, newModelObject)) | |
184 | return conflict | |
185 | ||
186 | def needs_merge(self, new_obj): | |
187 | return ModelObjectDiff(self, new_obj).existDiff() | |
188 | ||
189 | def getUpdates(self): | |
190 | return self.updates | |
191 | ||
192 | def updateResolved(self, update): | |
193 | self.updates.remove(update) | |
194 | ||
195 | ||
196 | # IMPORTANT: all delete methods are considered FULL delete | |
197 | # this means it will delete the reference from host and all | |
198 | # other objects containing them | |
199 | def _getValueByID(self, attrName, ID): | |
200 | """ | |
201 | attribute passed as a parameter MUST BE a dictionary indexed with a | |
202 | string ID | |
203 | if id is found as a part of a key it returns the object | |
204 | it returns None otherwise | |
205 | """ | |
206 | if ID: | |
207 | hash_id = get_hash([ID]) | |
208 | ref = self.__getattribute__(attrName) | |
209 | # we are assuming the value is unique inside the object ID's | |
210 | for key in ref: | |
211 | #XXX: this way of checking the ids doesn't allow us to use a real hash as key | |
212 | # because we are checking if "id" is part of the key... not a good way of | |
213 | # dealing with this... | |
214 | if hash_id == key or ID == key: | |
215 | return ref[key] | |
216 | # if id (hash) was not found then we try with element names | |
217 | for element in ref.itervalues(): | |
218 | #if id in element.name: | |
219 | if ID == element.name: | |
220 | return element | |
221 | return None | |
222 | ||
223 | ||
224 | def _addValue(self, attrName, newValue, setparent = False, update = False): | |
225 | # attribute passed as a parameter MUST BE the name | |
226 | # of an internal attribute which is a dictionary indexed | |
227 | # with a string ID | |
228 | valID = newValue.getID() | |
229 | ref = self.__getattribute__(attrName) | |
230 | if valID not in ref or update: | |
231 | ref[valID] = newValue | |
232 | if setparent: | |
233 | newValue.setParent(self) | |
234 | return True | |
235 | #return not update | |
236 | return False | |
237 | ||
238 | ||
239 | def _updatePublicAttributes(self): | |
240 | # can be overriden if needed | |
241 | pass | |
242 | ||
243 | def setID(self, ID=None): | |
244 | if ID is None: | |
245 | self.updateID() | |
246 | else: | |
247 | self._id = ID | |
248 | return self._id | |
249 | ||
250 | def updateID(self): | |
251 | raise NotImplementedError("This should be overwritten") | |
252 | ||
253 | def _prependParentId(self): | |
254 | if self._parent_id: | |
255 | self._id = '.'.join((self._parent_id, self.getID())) | |
256 | ||
257 | def getID(self): | |
258 | if self._id is None: | |
259 | self.updateID() | |
260 | return self._id | |
261 | ||
262 | id = property(getID, setID) | |
263 | ||
264 | def getMetadata(self): | |
265 | """Returns the current metadata of the object""" | |
266 | return self._metadata.__dict__ | |
267 | ||
268 | def setMetadata(self, metadata): | |
269 | self._metadata = metadata | |
270 | ||
271 | def getMetadataHistory(self): | |
272 | """Returns the current metadata of the object""" | |
273 | return self._metadataHistory | |
274 | ||
275 | def updateMetadata(self): | |
276 | """ We are only saving the previous state so the newst is not available""" | |
277 | self.getMetadata().update(self.owner) | |
278 | # self.getMetadataHistory().pushMetadataForId(self.getID(), self.getMetadata()) | |
279 | ||
280 | def getHost(self): | |
281 | #recursive method to recover the Host root | |
282 | if self.class_signature == "Host": | |
283 | return self | |
284 | return self.getParent().getHost() | |
285 | ||
286 | def setName(self, name): | |
287 | self._name = name | |
288 | ||
289 | def getName(self): | |
290 | return self._name | |
291 | ||
292 | name = property(getName, setName) | |
293 | ||
294 | def setDescription(self, description): | |
295 | self._description = description | |
296 | ||
297 | def getDescription(self): | |
298 | return self._description | |
299 | ||
300 | description = property(getDescription, setDescription) | |
301 | ||
302 | def isOwned(self): | |
303 | return self._is_owned | |
304 | ||
305 | def setOwned(self, owned=True): | |
306 | self._is_owned = owned | |
307 | ||
308 | def getOwner(self): | |
309 | return self.owner | |
310 | ||
311 | def setOwner(self, owner=None): | |
312 | self.owner = owner | |
313 | ||
314 | #@save | |
315 | def setParent(self, parent): | |
316 | self._parent = parent | |
317 | ||
318 | def getParent(self): | |
319 | return self._parent | |
320 | ||
321 | parent = property(getParent, setParent) | |
322 | ||
323 | #TODO: this should be removed and we could use some class | |
324 | # based on dict to implement this | |
325 | ||
326 | ||
327 | def _delValue(self, attrName, valID): | |
328 | # attribute passed as a parameter MUST BE the name | |
329 | # of an internal attribute which is a dictionary indexed | |
330 | # with a string ID | |
331 | api.devlog("(%s)._delValue(%s, %s)" % (self, attrName, valID)) | |
332 | ref = self.__getattribute__(attrName) | |
333 | api.devlog("ref.keys() = %s" % ref.keys()) | |
334 | if valID in ref: | |
335 | val = ref[valID] | |
336 | del ref[valID] | |
337 | val.delete() | |
338 | return True | |
339 | ||
340 | hash_id = get_hash([valID]) | |
341 | if hash_id in ref: | |
342 | val = ref[hash_id] | |
343 | del ref[hash_id] | |
344 | val.delete() | |
345 | return True | |
346 | ||
347 | for element in ref.itervalues(): | |
348 | if valID == element.name: | |
349 | val = ref[element.getID()] | |
350 | del ref[element.getID()] | |
351 | val.delete() | |
352 | return True | |
353 | ||
354 | # none of the ids were found | |
355 | return False | |
356 | ||
357 | def _delAllValues(self, attrName): | |
358 | ref = self.__getattribute__(attrName) | |
359 | try: | |
360 | ref.clear() | |
361 | return True | |
362 | except Exception: | |
363 | return False | |
364 | ||
365 | #@delete | |
366 | def delete(self): | |
367 | del self | |
368 | ||
369 | def _getAllValues(self, attrName, mode = 0): | |
370 | """ | |
371 | attribute passed as a parameter MUST BE a dictionary indexed with a | |
372 | string ID | |
373 | return all values in the dictionary | |
374 | mode = 0 returns a list of objects | |
375 | mode = 1 returns a dictionary of objects with their id as key | |
376 | """ | |
377 | ref = self.__getattribute__(attrName) | |
378 | if mode: | |
379 | return ref | |
380 | else: | |
381 | return sorted(ref.values()) | |
382 | ||
383 | def _getAllIDs(self, attrName): | |
384 | ref = self.__getattribute__(attrName) | |
385 | return ref.keys() | |
386 | ||
387 | def _getValueCount(self, attrName): | |
388 | ref = self.__getattribute__(attrName) | |
389 | return len(ref) | |
390 | ||
391 | def __repr__(self): | |
392 | return "<ModelObject %s at 0x%x>" % (self.__class__.__name__, id(self)) | |
393 | ||
394 | def __str__(self): | |
395 | return "<ModelObject %s ID = %s at 0x%x>" % (self.__class__.__name__, self._id, id(self)) | |
396 | ||
397 | #notes | |
398 | @updateLocalMetadata | |
399 | def addNote(self, newNote, update=False, setparent=True): | |
400 | self.addChild(newNote) | |
401 | return True | |
402 | ||
403 | def newNote(self, name, text): | |
404 | note = ModelObjectNote(name, text, self) | |
405 | self.addNote(note) | |
406 | ||
407 | @updateLocalMetadata | |
408 | def delNote(self, noteID): | |
409 | self.deleteChild(noteID) | |
410 | return True | |
411 | ||
412 | def getNotes(self): | |
413 | return self.getChildsByType(ModelObjectNote.__name__) | |
414 | ||
415 | def setNotes(self, notes): | |
416 | self._addChildsDict(notes) | |
417 | ||
418 | def getNote(self, noteID): | |
419 | return self.findChild(noteID) | |
420 | ||
421 | def notesCount(self): | |
422 | return len(self._notes.values()) | |
423 | ||
424 | #Vulnerability | |
425 | @updateLocalMetadata | |
426 | def addVuln(self, newVuln, update=False, setparent=True): | |
427 | self.addChild(newVuln) | |
428 | return True | |
429 | ||
430 | @updateLocalMetadata | |
431 | def delVuln(self, vulnID): | |
432 | self.deleteChild(vulnID) | |
433 | return True | |
434 | ||
435 | def getVulns(self): | |
436 | return self.getChildsByType(ModelObjectVuln.__name__) + self.getChildsByType(ModelObjectVulnWeb.__name__) | |
437 | ||
438 | def setVulns(self, vulns): | |
439 | self._addChildsDict(vulns) | |
440 | ||
441 | def getVuln(self, vulnID): | |
442 | return self.findChild(vulnID) | |
443 | ||
444 | def vulnsCount(self): | |
445 | return len(self._vulns.values()) | |
446 | ||
447 | def vulnsToDict(self): | |
448 | d = [] | |
449 | for vuln in self._vulns.values(): | |
450 | d.append(vuln.toDictFull()) | |
451 | return d | |
452 | ||
453 | @updateLocalMetadata | |
454 | def delCred(self, credID): | |
455 | return self._delValue("_creds", credID) | |
456 | ||
457 | def getCreds(self): | |
458 | return self.getChildsByType(ModelObjectCred.__name__) | |
459 | ||
460 | def setCreds(self, creds): | |
461 | self._addChildsDict(creds) | |
462 | ||
463 | def getCred(self, credID): | |
464 | return self._getValueByID("_creds", credID) | |
465 | ||
466 | def credsToDict(self): | |
467 | d = [] | |
468 | for cred in self._creds.values(): | |
469 | d.append(cred.toDictFull()) | |
470 | return d | |
471 | ||
472 | ||
473 | def credsCount(self): | |
474 | return len(self._creds.values()) | |
475 | ||
476 | def __getClassname(self, val): | |
477 | supported = factory.listModelObjectTypes() | |
478 | return filter(lambda x: val.lower().replace('_', '')[:-1] in x.lower(), supported)[0] | |
479 | ||
480 | def _asdict(self): | |
481 | return self.toDictFull() | |
482 | ||
483 | def ancestors_path(self): | |
484 | if self.getParent() is None: | |
485 | return str(self.getID()) | |
486 | return ".".join([self.getParent().ancestors_path()] + [str(self.getID())]) | |
487 | ||
488 | def _addChildsDict(self, dictt): | |
489 | [self.addChild(v) for k, v in dictt.items()] | |
490 | ||
491 | ||
492 | class ModelComposite(ModelObject): | |
493 | """ Model Objects Composite Abstract Class """ | |
494 | ||
495 | def __init__(self, parent_id=None): | |
496 | ModelObject.__init__(self, parent_id) | |
497 | self.childs = {} | |
498 | ||
499 | def addChild(self, model_object): | |
500 | model_object.setParent(self) | |
501 | self.childs[model_object.getID()] = model_object | |
502 | ||
503 | def getChilds(self): | |
504 | return self.childs | |
505 | ||
506 | def getChildsByType(self, signature): | |
507 | return [c for c in self.childs.values() | |
508 | if c.__class__.__name__ == signature] | |
509 | ||
510 | def deleteChild(self, iid): | |
511 | del self.childs[iid] | |
512 | ||
513 | def findChild(self, iid): | |
514 | return self.childs.get(iid) | |
515 | ||
516 | class ModelLeaf(ModelObject): | |
517 | def __init__(self, parent_id=None): | |
518 | ModelObject.__init__(self, parent_id) | |
519 | ||
520 | def getChildsByType(self, signature): | |
521 | return [] | |
522 | ||
523 | def getChilds(self): | |
524 | return {} | |
525 | ||
526 | #------------------------------------------------------------------------------- | |
527 | #TODO: refactor this class to make it generic so this can be used also for plugins | |
528 | # then create a subclass and inherit the generic factory | |
20 | ||
21 | # ------------------------------------------------------------------------------- | |
22 | # TODO: refactor this class to make it generic so this can be used also for plugins | |
23 | # then create a subclass and inherit the generic factory | |
529 | 24 | class ModelObjectFactory(object): |
530 | 25 | """ |
531 | 26 | Factory to creat any ModelObject type |
551 | 46 | names.sort() |
552 | 47 | return names |
553 | 48 | |
554 | def generateID(self, classname, parent_id=None, **objargs): | |
555 | tmpObj = self._registered_objects[classname](**objargs) | |
556 | if parent_id: | |
557 | return '.'.join([parent_id, tmpObj.getID()]) | |
558 | return tmpObj.getID() | |
559 | ||
560 | def createModelObject(self, classname, object_name=None, **objargs): | |
49 | def generateID(self, classname, parent_id='', **objargs): | |
50 | """Given a classname, parent_id and necessary objargs, return the ID | |
51 | of the object. | |
52 | ||
53 | Necesary objargs vary according to the object: | |
54 | Host --> name | |
55 | Cred --> Name, password | |
56 | Note --> Name, text | |
57 | Service --> Protocol, ports | |
58 | Interface --> Network segments, ipv4_address, ipv6_address | |
59 | Vuln --> name, desc | |
60 | VulnWeb --> name, website | |
61 | """ | |
62 | ||
63 | # see how nicely formated that dictionary is | |
64 | # it's a building about to go down on an eathquake! | |
65 | # let's try not to make that an analogy about my code, ok? thank you :) | |
66 | # appropiate_class = self._registered_objects[classname] | |
67 | # class_to_args = {'Host': (objargs.get('name'),), | |
68 | # 'Cred': (objargs.get('name'), objargs.get('password')), | |
69 | # 'Note': (objargs.get('name'), | |
70 | # objargs.get('text')), | |
71 | # 'Service': (objargs.get('protocol'), | |
72 | # objargs.get('ports')), | |
73 | # 'Interface': (objargs.get('network_segment'), | |
74 | # objargs.get('ipv4_address'), | |
75 | # objargs.get('ipv6_address')), | |
76 | # 'Vulnerability': (objargs.get('name'), | |
77 | # objargs.get('desc')), | |
78 | # 'VulnerabilityWeb': (objargs.get('name'), | |
79 | # objargs.get('website')) | |
80 | # } | |
81 | # try: | |
82 | # id = appropiate_class.generateID(parent_id, *class_to_args[classname]) | |
83 | # except KeyError: | |
84 | # raise Exception("You've provided an invalid classname") | |
85 | # return id | |
86 | ||
87 | def createModelObject(self, classname, object_name, workspace_name=None, parent_id=None, **objargs): | |
88 | """Given a registered classname, create an object of name object_name and | |
89 | with the properties found on objargs. ID will be generated for you. | |
90 | ||
91 | If workspace_name is None, it will be inferred from the CONF module. | |
92 | parent_id should only be None if classname is 'Host' or maybe 'Note' or 'Credential'. | |
93 | """ | |
94 | if not workspace_name: | |
95 | workspace_name = CONF.getLastWorkspace() | |
561 | 96 | if classname in self._registered_objects: |
562 | 97 | if object_name is not None: |
563 | tmpObj = self._registered_objects[classname](object_name,**objargs) | |
98 | objargs['name'] = object_name | |
99 | objargs['_id'] = -1 # they still don't have a server id | |
100 | objargs['id'] = -1 # we'll generate it after making sure the objects are okey | |
101 | tmpObj = self._registered_objects[classname](objargs, workspace_name) | |
102 | tmpObj.setID(parent_id) | |
564 | 103 | return tmpObj |
565 | 104 | else: |
566 | 105 | raise Exception("Object name parameter missing. Cannot create object class: %s" % classname) |
567 | 106 | else: |
568 | 107 | raise Exception("Object class %s not registered in factory. Cannot create object." % classname) |
569 | 108 | |
570 | #------------------------------------------------------------------------------- | |
109 | # ------------------------------------------------------------------------------- | |
571 | 110 | # global reference kind of a singleton |
572 | 111 | factory = ModelObjectFactory() |
573 | 112 | |
574 | #------------------------------------------------------------------------------- | |
113 | # ------------------------------------------------------------------------------- | |
575 | 114 | |
576 | 115 | class CustomXMLRPCRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler): |
577 | 116 | |
627 | 166 | response = self.server._marshaled_dispatch( |
628 | 167 | data, getattr(self, '_dispatch', None) |
629 | 168 | ) |
630 | except Exception, e: # This should only happen if the module is buggy | |
169 | except Exception, e: # This should only happen if the module is buggy | |
631 | 170 | # internal error, report as HTTP server error |
632 | 171 | self.send_response(500) |
633 | 172 | |
649 | 188 | # shut down the connection |
650 | 189 | self.wfile.flush() |
651 | 190 | self.connection.shutdown(1) |
652 | #------------------------------------------------------------------------------- | |
191 | # ------------------------------------------------------------------------------- | |
653 | 192 | # custom XMLRPC server with stopping function |
654 | #TODO: check http://epydoc.sourceforge.net/stdlib/SimpleXMLRPCServer.SimpleXMLRPCServer-class.html | |
193 | # TODO: check http://epydoc.sourceforge.net/stdlib/SimpleXMLRPCServer.SimpleXMLRPCServer-class.html | |
655 | 194 | # see if there is a way to know the ip caller |
656 | 195 | # looks like the request handler can give us that info |
657 | 196 | # http://epydoc.sourceforge.net/stdlib/BaseHTTPServer.BaseHTTPRequestHandler-class.html#address_string |
664 | 203 | """ |
665 | 204 | def __init__(self, *args, **kwargs): |
666 | 205 | threading.Thread.__init__(self) |
667 | SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(self, requestHandler = CustomXMLRPCRequestHandler, allow_none = True, *args,**kwargs) | |
206 | SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(self, | |
207 | requestHandler=CustomXMLRPCRequestHandler, | |
208 | allow_none=True, *args, **kwargs) | |
668 | 209 | self._stop = False |
669 | 210 | # set timeout for handle_request. If we don't the server will hang |
670 | 211 | self.timeout = 2 |
736 | 277 | # the info comes in form of args and kwargs |
737 | 278 | # so params has 2 items, the first being a list or tuple |
738 | 279 | # and the second a dictionary |
739 | if len(params) == 2 and isinstance(params[1],dict) and\ | |
740 | ( isinstance(params[0],list) or isinstance(params[0],tuple) ) : | |
280 | if len(params) == 2 and isinstance(params[1], dict) and\ | |
281 | (isinstance(params[0], list) or isinstance(params[-1], tuple)): | |
741 | 282 | return func(*params[0], **params[1]) |
742 | 283 | else: |
743 | 284 | # this is the default way in case a normal xmlrpclib.ServerProxy is used |
749 | 290 | else: |
750 | 291 | raise Exception('method "%s" is not supported' % method) |
751 | 292 | |
752 | ||
753 | def _marshaled_dispatch(self, data, dispatch_method = None): | |
293 | def _marshaled_dispatch(self, data, dispatch_method=None): | |
754 | 294 | """Dispatches an XML-RPC method from marshalled (XML) data. |
755 | 295 | |
756 | 296 | XML-RPC methods are dispatched from the marshalled (XML) data |
774 | 314 | response = (response,) |
775 | 315 | response = xmlrpclib.dumps(response, methodresponse=1, |
776 | 316 | allow_none=self.allow_none, encoding=self.encoding) |
777 | except Fault, fault: | |
778 | response = xmlrpclib.dumps(fault, allow_none=self.allow_none, | |
779 | encoding=self.encoding) | |
780 | 317 | except Exception: |
781 | 318 | # report exception back to server |
782 | 319 | exc_type, exc_value, exc_tb = sys.exc_info() |
787 | 324 | |
788 | 325 | return response |
789 | 326 | |
790 | #------------------------------------------------------------------------------- | |
791 | ||
792 | 327 | class XMLRPCKeywordProxy(object): |
793 | 328 | """ |
794 | 329 | custom XMLRPC Server Proxy capable of receiving keyword arguments |
796 | 331 | """ |
797 | 332 | def __init__(self, *args, **kwargs): |
798 | 333 | self._xmlrpc_server_proxy = xmlrpclib.ServerProxy(*args, **kwargs) |
334 | ||
799 | 335 | def __getattr__(self, name): |
800 | 336 | call_proxy = getattr(self._xmlrpc_server_proxy, name) |
337 | ||
801 | 338 | def _call(*args, **kwargs): |
802 | 339 | return call_proxy(args, kwargs) |
803 | 340 | return _call |
804 | ||
805 | ||
806 | ||
807 | #------------------------------------------------------------------------------- | |
808 | class ModelObjectNote(ModelComposite): | |
809 | """ | |
810 | Simple class to store notes about any object. | |
811 | id will be used to number notes (based on a counter on the object being commented) | |
812 | parent will be a reference to the object being commented. | |
813 | To assing new text this: | |
814 | >>> note.text = "foobar" | |
815 | to append text + or += operators can be used (no need to use text property): | |
816 | >>> note += " hello world!" | |
817 | """ | |
818 | class_signature = "Note" | |
819 | ||
820 | def __init__(self, name="", text="", parent_id=None): | |
821 | ModelComposite.__init__(self, parent_id) | |
822 | self.name = str(name) | |
823 | self._text = str(text) | |
824 | ||
825 | def updateID(self): | |
826 | self._id = get_hash([self.name, self._text]) | |
827 | self._prependParentId() | |
828 | ||
829 | def _setText(self, text): | |
830 | # clear buffer then write new text | |
831 | # self._text.seek(0) | |
832 | # self._text.truncate() | |
833 | # self._text.write(text) | |
834 | self._text = str(text) | |
835 | ||
836 | def _getText(self): | |
837 | # return self._text.getvalue() | |
838 | return self._text | |
839 | ||
840 | def getText(self): | |
841 | # return self._text.getvalue() | |
842 | return self._text | |
843 | ||
844 | def setText(self, text): | |
845 | # return self._text.getvalue() | |
846 | self._text = str(text) | |
847 | ||
848 | text = property(_getText, _setText) | |
849 | ||
850 | #@save | |
851 | @updateLocalMetadata | |
852 | def updateAttributes(self, name=None, text=None): | |
853 | if name is not None: | |
854 | self.setName(name) | |
855 | if text is not None: | |
856 | self.text = text | |
857 | ||
858 | def __str__(self): | |
859 | return "%s: %s" % (self.name, self.text) | |
860 | ||
861 | def __repr__(self): | |
862 | return "%s: %s" % (self.name, self.text) | |
863 | ||
864 | ||
865 | class ModelObjectVuln(ModelComposite): | |
866 | class_signature = "Vulnerability" | |
867 | ||
868 | def __init__(self, name="", desc="", ref=None, severity="", resolution="", | |
869 | confirmed=False, parent_id=None): | |
870 | """ | |
871 | The parameters refs can be a single value or a list with values | |
872 | """ | |
873 | ModelComposite.__init__(self, parent_id) | |
874 | self.name = name | |
875 | self._desc = desc | |
876 | self.data = "" | |
877 | self.confirmed = confirmed | |
878 | self.refs = [] | |
879 | ||
880 | if isinstance(ref, list): | |
881 | self.refs.extend(ref) | |
882 | elif ref is not None: | |
883 | self.refs.append(ref) | |
884 | ||
885 | # Severity Standarization | |
886 | self.severity = self.standarize(severity) | |
887 | self.resolution = resolution | |
888 | ||
889 | def _updatePublicAttributes(self): | |
890 | ||
891 | self.publicattrs['Name'] = 'getName' | |
892 | self.publicattrs['Description'] = 'getDescription' | |
893 | self.publicattrs['Data'] = "getData" | |
894 | self.publicattrs['Severity'] = 'getSeverity' | |
895 | self.publicattrs['Refs'] = 'getRefs' | |
896 | self.publicattrs['Resolution'] = 'getResolution' | |
897 | ||
898 | self.publicattrsrefs['Name'] = 'name' | |
899 | self.publicattrsrefs['Description'] = '_desc' | |
900 | self.publicattrsrefs['Data'] = "data" | |
901 | self.publicattrsrefs['Severity'] = 'severity' | |
902 | self.publicattrsrefs['Refs'] = 'refs' | |
903 | self.publicattrsrefs['Resolution'] = 'resolution' | |
904 | ||
905 | def standarize(self, severity): | |
906 | # Transform all severities into lower strings | |
907 | severity = str(severity).lower() | |
908 | # If it has info, med, high, critical in it, standarized to it: | |
909 | ||
910 | ||
911 | def align_string_based_vulns(severity): | |
912 | severities = ['info','low', 'med', 'high', 'critical'] | |
913 | for sev in severities: | |
914 | if severity[0:3] in sev: | |
915 | return sev | |
916 | return severity | |
917 | ||
918 | severity = align_string_based_vulns(severity) | |
919 | ||
920 | # Transform numeric severity into desc severity | |
921 | numeric_severities = { '0' : 'info', | |
922 | '1' : 'low', | |
923 | '2' : 'med', | |
924 | '3' : 'high', | |
925 | "4" : 'critical' } | |
926 | ||
927 | ||
928 | if not severity in numeric_severities.values(): | |
929 | severity = numeric_severities.get(severity, 'unclassified') | |
930 | ||
931 | return severity | |
932 | ||
933 | def updateID(self): | |
934 | self._id = get_hash([self.name, self._desc]) | |
935 | self._prependParentId() | |
936 | ||
937 | def tieBreakable(self, key): | |
938 | ''' | |
939 | If the confirmed property has a conflict, there's two possible reasons: | |
940 | confirmed is false, and the new value is true => returns true | |
941 | confirmed is true, and the new value is false => returns true | |
942 | ''' | |
943 | if key == "confirmed": | |
944 | return True | |
945 | return False | |
946 | ||
947 | def tieBreak(self, key, prop1, prop2): | |
948 | if key == "confirmed": | |
949 | return True | |
950 | return (prop1, prop2) | |
951 | ||
952 | def _setDesc(self, desc): | |
953 | self._desc = desc | |
954 | ||
955 | #@save | |
956 | @updateLocalMetadata | |
957 | def updateAttributes(self, name=None, desc=None, data=None, | |
958 | severity=None, resolution=None, refs=None): | |
959 | if name is not None: | |
960 | self.setName(name) | |
961 | if desc is not None: | |
962 | self.setDescription(desc) | |
963 | if data is not None: | |
964 | self.setData(data) | |
965 | if resolution is not None: | |
966 | self.setResolution(resolution) | |
967 | if severity is not None: | |
968 | self.severity = self.standarize(severity) | |
969 | if refs is not None: | |
970 | self.refs = refs | |
971 | ||
972 | def _getDesc(self): | |
973 | return self._desc | |
974 | ||
975 | desc = property(_getDesc, _setDesc) | |
976 | ||
977 | def setDesc(self, desc): | |
978 | self._desc = desc | |
979 | ||
980 | def getDesc(self): | |
981 | return self._desc | |
982 | ||
983 | def getDescription(self): | |
984 | return self.getDesc() | |
985 | ||
986 | def setDescription(self, desc): | |
987 | self.setDesc(desc) | |
988 | ||
989 | def setResolution(self, resolution): | |
990 | self.resolution = resolution | |
991 | ||
992 | def getResolution(self): | |
993 | return self.resolution | |
994 | ||
995 | def getSeverity(self): | |
996 | return self.severity | |
997 | ||
998 | def setSeverity(self, severity): | |
999 | self.severity = self.standarize(severity) | |
1000 | ||
1001 | def getRefs(self): | |
1002 | return self.refs | |
1003 | ||
1004 | def setRefs(self, refs): | |
1005 | if isinstance(refs, list): | |
1006 | self.refs.extend(refs) | |
1007 | elif ref is not None: | |
1008 | self.refs.append(refs) | |
1009 | ||
1010 | def setData(self, data): | |
1011 | self.data = data | |
1012 | ||
1013 | def getData(self): | |
1014 | return self.data | |
1015 | ||
1016 | def setConfirmed(self, confirmed): | |
1017 | self.confirmed = confirmed | |
1018 | ||
1019 | def getConfirmed(self): | |
1020 | return self.confirmed | |
1021 | ||
1022 | def __str__(self): | |
1023 | return "vuln id:%s - %s" % (self.id, self.name) | |
1024 | ||
1025 | def __repr__(self): | |
1026 | return self.__str__() | |
1027 | ||
1028 | ||
1029 | class ModelObjectVulnWeb(ModelObjectVuln): | |
1030 | """ | |
1031 | Simple class to store vulnerability web about any object. | |
1032 | This Vuln support path, hostname, request and response | |
1033 | parent will be a reference to the ModelObjectVuln being commented. | |
1034 | """ | |
1035 | class_signature = "VulnerabilityWeb" | |
1036 | ||
1037 | def __init__(self, name="", desc="", website="", path="", ref=None, | |
1038 | severity="", resolution="", request="", response="", | |
1039 | method="", pname="", params="", query="", category="", | |
1040 | confirmed=False, parent_id=None): | |
1041 | """ | |
1042 | The parameters ref can be a single value or a list with values | |
1043 | """ | |
1044 | ModelObjectVuln.__init__( | |
1045 | self, name, desc, ref, severity, resolution, confirmed, | |
1046 | parent_id) | |
1047 | self.path = path | |
1048 | self.website = website | |
1049 | self.request = request | |
1050 | self.response = response | |
1051 | self.method = method | |
1052 | self.pname = pname | |
1053 | self.params = params | |
1054 | self.query = query | |
1055 | self.category = category | |
1056 | ||
1057 | def _updatePublicAttributes(self): | |
1058 | ||
1059 | self.publicattrs['Name'] = 'getName' | |
1060 | self.publicattrs['Desc'] = 'getDescription' | |
1061 | self.publicattrs['Data'] = 'getData' | |
1062 | self.publicattrs['Severity'] = 'getSeverity' | |
1063 | self.publicattrs['Refs'] = 'getRefs' | |
1064 | self.publicattrs['Path'] = 'getPath' | |
1065 | self.publicattrs['Website'] = 'getWebsite' | |
1066 | self.publicattrs['Request'] = 'getRequest' | |
1067 | self.publicattrs['Response'] = 'getResponse' | |
1068 | self.publicattrs['Method'] = 'getMethod' | |
1069 | self.publicattrs['Pname'] = 'getPname' | |
1070 | self.publicattrs['Params'] = 'getParams' | |
1071 | self.publicattrs['Query'] = 'getQuery' | |
1072 | self.publicattrs['Category'] = 'getCategory' | |
1073 | ||
1074 | self.publicattrsrefs['Name'] = 'name' | |
1075 | self.publicattrsrefs['Desc'] = '_desc' | |
1076 | self.publicattrsrefs['Data'] = 'data' | |
1077 | self.publicattrsrefs['Severity'] = 'severity' | |
1078 | self.publicattrsrefs['Refs'] = 'refs' | |
1079 | self.publicattrsrefs['Path'] = 'path' | |
1080 | self.publicattrsrefs['Website'] = 'website' | |
1081 | self.publicattrsrefs['Request'] = 'request' | |
1082 | self.publicattrsrefs['Response'] = 'response' | |
1083 | self.publicattrsrefs['Method'] = 'method' | |
1084 | self.publicattrsrefs['Pname'] = 'pname' | |
1085 | self.publicattrsrefs['Params'] = 'params' | |
1086 | self.publicattrsrefs['Query'] = 'query' | |
1087 | self.publicattrsrefs['Category'] = 'category' | |
1088 | ||
1089 | def updateID(self): | |
1090 | self._id = get_hash([self.name, self.website, self.path, self.desc ]) | |
1091 | self._prependParentId() | |
1092 | ||
1093 | def getPath(self): | |
1094 | return self.path | |
1095 | ||
1096 | def setPath(self, path): | |
1097 | self.path = path | |
1098 | ||
1099 | def getWebsite(self): | |
1100 | return self.website | |
1101 | ||
1102 | def setWebsite(self, website): | |
1103 | self.website = website | |
1104 | ||
1105 | def getRequest(self): | |
1106 | return self.request | |
1107 | ||
1108 | def setRequest(self, request): | |
1109 | self.request = request | |
1110 | ||
1111 | def getResponse(self): | |
1112 | return self.response | |
1113 | ||
1114 | def setResponse(self, response): | |
1115 | self.response = response | |
1116 | ||
1117 | def getMethod(self): | |
1118 | return self.method | |
1119 | ||
1120 | def setMethod(self, method): | |
1121 | self.method = method | |
1122 | ||
1123 | def getPname(self): | |
1124 | return self.pname | |
1125 | ||
1126 | def setPname(self, pname): | |
1127 | self.pname = pname | |
1128 | ||
1129 | def getParams(self): | |
1130 | return self.params | |
1131 | ||
1132 | def setParams(self, params): | |
1133 | self.params = params | |
1134 | ||
1135 | def getQuery(self): | |
1136 | return self.query | |
1137 | ||
1138 | def setQuery(self, query): | |
1139 | self.query = query | |
1140 | ||
1141 | def getCategory(self): | |
1142 | return self.category | |
1143 | ||
1144 | def setCategory(self, category): | |
1145 | self.category = category | |
1146 | ||
1147 | #@save | |
1148 | @updateLocalMetadata | |
1149 | def updateAttributes(self, name=None, desc=None, data=None, website=None, path=None, refs=None, | |
1150 | severity=None, resolution=None, request=None,response=None, method=None, | |
1151 | pname=None, params=None, query=None, category=None): | |
1152 | super(ModelObjectVulnWeb, self).updateAttributes(name, desc, data, severity, resolution, refs) | |
1153 | if website is not None: | |
1154 | self.website = website | |
1155 | if path is not None: | |
1156 | self.path = path | |
1157 | if request is not None: | |
1158 | self.request = request | |
1159 | if response is not None: | |
1160 | self.response = response | |
1161 | if method is not None: | |
1162 | self.method = method | |
1163 | if pname is not None: | |
1164 | self.pname = pname | |
1165 | if params is not None: | |
1166 | self.params = params | |
1167 | if query is not None: | |
1168 | self.query = query | |
1169 | if category is not None: | |
1170 | self.category = category | |
1171 | ||
1172 | ||
1173 | #------------------------------------------------------------------------------- | |
1174 | class ModelObjectCred(ModelLeaf): | |
1175 | """ | |
1176 | Simple class to store credentials about any object. | |
1177 | id will be used to number credentials (based on a counter on the object being commented) | |
1178 | parent will be a reference to the object being commented. | |
1179 | To assing new password this: | |
1180 | >>> cred.password = "foobar" | |
1181 | to append text + or += operators can be used (no need to use password property): | |
1182 | >>> cred += " hello world!" | |
1183 | """ | |
1184 | class_signature = "Cred" | |
1185 | ||
1186 | def __init__(self, username="", password="", parent_id=None): | |
1187 | ModelLeaf.__init__(self, parent_id) | |
1188 | self._username = str(username) | |
1189 | self._password = str(password) | |
1190 | ||
1191 | def updateID(self): | |
1192 | self._id = get_hash([self._username, self._password]) | |
1193 | self._prependParentId() | |
1194 | ||
1195 | def setPassword(self, password): | |
1196 | self._password = str(password) | |
1197 | ||
1198 | def getPassword(self): | |
1199 | return self._password | |
1200 | ||
1201 | def getUsername(self): | |
1202 | return self._username | |
1203 | ||
1204 | def setUsername(self, username): | |
1205 | self._username = str(username) | |
1206 | ||
1207 | password = property(getPassword, setPassword) | |
1208 | ||
1209 | username = property(getUsername, setUsername) | |
1210 | ||
1211 | #@save | |
1212 | @updateLocalMetadata | |
1213 | def updateAttributes(self, username=None, password=None): | |
1214 | if username is not None: | |
1215 | self.setUsername(username) | |
1216 | if password is not None: | |
1217 | self.setPassword(password) |
8 | 8 | import Queue |
9 | 9 | import traceback |
10 | 10 | import model.common # this is to make sure the factory is created |
11 | import model.hosts | |
12 | 11 | |
13 | 12 | from config.configuration import getInstanceConfiguration |
14 | 13 | from utils.logs import getLogger |
16 | 15 | from model.guiapi import notification_center as notifier |
17 | 16 | from gui.customevents import * |
18 | 17 | from functools import wraps |
19 | ||
18 | from persistence.server import models | |
20 | 19 | |
21 | 20 | # XXX: consider re-writing this module! There's alot of repeated code |
22 | 21 | # and things are really messy |
30 | 29 | ADDINTERFACE = 2002 |
31 | 30 | DELINTERFACE = 2003 |
32 | 31 | ADDSERVICEINT = 2004 |
33 | ADDSERVICEAPP = 2005 | |
34 | 32 | DELSERVICEINT = 2006 |
35 | DELSERVICEAPP = 2007 | |
36 | ADDAPPLICATION = 2009 | |
37 | 33 | ADDCATEGORY = 2011 |
38 | 34 | ADDVULNINT = 2013 |
39 | 35 | DELVULNINT = 2014 |
40 | ADDVULNAPP = 2015 | |
41 | DELVULNAPP = 2016 | |
42 | 36 | ADDVULNHOST = 2017 |
43 | 37 | DELVULNHOST = 2018 |
44 | 38 | ADDVULNSRV = 2019 |
45 | 39 | DELVULNSRV = 2020 |
46 | 40 | ADDNOTEINT = 2021 |
47 | 41 | DELNOTEINT = 2022 |
48 | ADDNOTEAPP = 2023 | |
49 | DELNOTEAPP = 2024 | |
50 | 42 | ADDNOTEHOST = 2025 |
51 | 43 | DELNOTEHOST = 2026 |
52 | 44 | ADDNOTESRV = 2027 |
56 | 48 | DELNOTEVULN = 2031 |
57 | 49 | EDITHOST = 2032 |
58 | 50 | EDITINTERFACE = 2033 |
59 | EDITAPPLICATION = 2034 | |
60 | 51 | EDITSERVICE = 2035 |
61 | 52 | ADDCREDSRV = 2036 |
62 | 53 | DELCREDSRV = 2037 |
82 | 73 | ADDINTERFACE: "ADDINTERFACE", |
83 | 74 | DELINTERFACE: "DELINTERFACE", |
84 | 75 | ADDSERVICEINT: "ADDSERVICEINT", |
85 | ADDSERVICEAPP: "ADDSERVICEAPP", | |
86 | 76 | DELSERVICEINT: "DELSERVICEINT", |
87 | DELSERVICEAPP: "DELSERVICEAPP", | |
88 | ADDAPPLICATION: "ADDAPPLICATION", | |
89 | 77 | ADDCATEGORY: "ADDCATEGORY", |
90 | 78 | ADDVULNINT: "ADDVULNINT", |
91 | 79 | DELVULNINT: "DELVULNINT", |
92 | ADDVULNAPP: "ADDVULNAPP", | |
93 | DELVULNAPP: "DELVULNAPP", | |
94 | 80 | ADDVULNHOST: "ADDVULNHOST", |
95 | 81 | DELVULNHOST: "DELVULNHOST", |
96 | 82 | ADDVULNSRV: "ADDVULNSRV", |
101 | 87 | DELNOTENOTE: "DELNOTENOTE", |
102 | 88 | EDITHOST: "EDITHOST", |
103 | 89 | EDITINTERFACE: "EDITINTERFACE", |
104 | EDITAPPLICATION: "EDITAPPLICATION", | |
105 | EDITSERVICE: "EDITAPPLICATION", | |
106 | 90 | ADDCREDSRV: "ADDCREDSRV", |
107 | 91 | DELCREDSRV: "DELCREDSRV", |
108 | 92 | ADDVULNWEBSRV: "ADDVULNSWEBRV", |
189 | 173 | """ |
190 | 174 | # This could be done in hosts module, but it seems easier to maintain |
191 | 175 | # if we have all in one place inside the controller |
192 | self._object_factory.register(model.hosts.Host) | |
193 | self._object_factory.register(model.hosts.Interface) | |
194 | self._object_factory.register(model.hosts.Service) | |
195 | self._object_factory.register(model.hosts.HostApplication) | |
196 | self._object_factory.register(model.common.ModelObjectVuln) | |
197 | self._object_factory.register(model.common.ModelObjectVulnWeb) | |
198 | self._object_factory.register(model.common.ModelObjectNote) | |
199 | self._object_factory.register(model.common.ModelObjectCred) | |
176 | self._object_factory.register(models.Host) | |
177 | self._object_factory.register(models.Interface) | |
178 | self._object_factory.register(models.Service) | |
179 | self._object_factory.register(models.Vuln) | |
180 | self._object_factory.register(models.VulnWeb) | |
181 | self._object_factory.register(models.Note) | |
182 | self._object_factory.register(models.Credential) | |
200 | 183 | |
201 | 184 | def _checkParent(self, parent_type): |
202 | 185 | """Takes a parent_type and returns the appropiate checkParentDecorator, |
445 | 428 | new host must be added to the model |
446 | 429 | """ |
447 | 430 | self.__addPendingAction(modelactions.ADDHOST, |
448 | host, category, update, old_hostname)\ | |
431 | host, category, update, old_hostname) | |
449 | 432 | |
450 | 433 | def addHostSYNC(self, host, category=None, update=False, old_hostname=None): |
451 | 434 | """ |
472 | 455 | def __edit(self, obj, *args, **kwargs): |
473 | 456 | obj.updateAttributes(*args, **kwargs) |
474 | 457 | self.mappers_manager.update(obj) |
475 | ||
476 | # if obj.class_signature == model.hosts.Host.class_signature: | |
477 | 458 | notifier.editHost(obj) |
478 | # else: | |
479 | # notifier.editHost(obj.getHost()) | |
480 | 459 | return True |
481 | 460 | |
482 | 461 | def __del(self, objId, *args): |
490 | 469 | |
491 | 470 | self.mappers_manager.remove(objId, obj.class_signature) |
492 | 471 | |
493 | if obj.class_signature == model.hosts.Host.class_signature: | |
472 | if obj.class_signature == models.Host.class_signature: | |
494 | 473 | notifier.delHost(objId) |
495 | 474 | else: |
496 | 475 | notifier.editHost(obj.getHost()) |
600 | 579 | Delete a service in a host and interface from the model |
601 | 580 | """ |
602 | 581 | self._processAction(modelactions.DELSERVICEINT, [serviceId], sync=True) |
603 | ||
604 | def delServiceFromApplicationASYNC(self, host, appname, service): | |
605 | """ | |
606 | ASYNC API | |
607 | Adds an action to the ModelController actions queue indicating a | |
608 | particular service in a host and interface must be removed from the model | |
609 | appname parameter can be "ALL" | |
610 | """ | |
611 | self.__addPendingAction( | |
612 | modelactions.DELSERVICEAPP, host, appname, service) | |
613 | ||
614 | def delServiceFromApplicationSYNC(self, host, appname, service): | |
615 | """ | |
616 | SYNC API | |
617 | Delete a service in a host and application from the model | |
618 | """ | |
619 | self._processAction(modelactions.DELSERVICEAPP, [ | |
620 | host, appname, service], sync=True) | |
621 | 582 | |
622 | 583 | def editServiceSYNC(self, service, name, description, protocol, ports, status, version, owned): |
623 | 584 | """ |
673 | 634 | self._processAction(modelactions.ADDVULNINT, [ |
674 | 635 | newVuln, intId], sync=True) |
675 | 636 | |
676 | def addVulnToApplicationASYNC(self, host, appname, newVuln): | |
677 | self.__addPendingAction(modelactions.ADDVULNAPP, | |
678 | host, appname, newVuln) | |
679 | ||
680 | def addVulnToApplicationSYNC(self, host, appname, newVuln): | |
681 | self._processAction(modelactions.ADDVULNAPP, [ | |
682 | host, appname, newVuln], sync=True) | |
683 | ||
684 | 637 | def addVulnToHostASYNC(self, hostId, newVuln): |
685 | 638 | self.__addPendingAction(modelactions.ADDVULNHOST, newVuln, hostId) |
686 | 639 | |
705 | 658 | def addVulnWebToServiceSYNC(self, host, srvId, newVuln): |
706 | 659 | self._processAction(modelactions.ADDVULNWEBSRV, |
707 | 660 | [newVuln, srvId], sync=True) |
708 | ||
709 | def delVulnFromApplicationASYNC(self, hostname, appname, vuln): | |
710 | self.__addPendingAction(modelactions.DELVULNAPP, | |
711 | hostname, appname, vuln) | |
712 | ||
713 | def delVulnFromApplicationSYNC(self, hostname, appname, vuln): | |
714 | self._processAction(modelactions.DELVULNAPP, [ | |
715 | hostname, appname, vuln], sync=True) | |
716 | 661 | |
717 | 662 | def delVulnFromInterfaceASYNC(self, hostname, intname, vuln): |
718 | 663 | self.__addPendingAction(modelactions.DELVULNINT, |
768 | 713 | self._processAction(modelactions.ADDNOTEINT, [ |
769 | 714 | newNote, intId], sync=True) |
770 | 715 | |
771 | def addNoteToApplicationASYNC(self, host, appname, newNote): | |
772 | self.__addPendingAction(modelactions.ADDNOTEAPP, | |
773 | host, appname, newNote) | |
774 | ||
775 | def addNoteToApplicationSYNC(self, host, appname, newNote): | |
776 | self._processAction(modelactions.ADDNOTEAPP, [ | |
777 | host, appname, newNote], sync=True) | |
778 | ||
779 | 716 | def addNoteToHostASYNC(self, hostId, newNote): |
780 | 717 | self.__addPendingAction(modelactions.ADDNOTEHOST, newNote, hostId) |
781 | 718 | |
801 | 738 | self._processAction(modelactions.ADDNOTE, [ |
802 | 739 | newNote, model_object], sync=True) |
803 | 740 | |
804 | def delNoteFromApplicationASYNC(self, hostname, appname, note): | |
805 | self.__addPendingAction(modelactions.DELNOTEAPP, | |
806 | hostname, appname, note) | |
807 | ||
808 | def delNoteFromApplicationSYNC(self, hostname, appname, note): | |
809 | self._processAction(modelactions.DELNOTEAPP, [ | |
810 | hostname, appname, note], sync=True) | |
811 | ||
812 | 741 | def delNoteFromInterfaceASYNC(self, hostname, intname, noteId): |
813 | 742 | self.__addPendingAction(modelactions.DELNOTEINT, noteId) |
814 | 743 | |
867 | 796 | |
868 | 797 | def newHost(self, name, os="Unknown"): |
869 | 798 | return model.common.factory.createModelObject( |
870 | model.hosts.Host.class_signature, | |
871 | name, os=os, parent_id=None) | |
799 | models.Host.class_signature, name, | |
800 | self.mappers_manager.workspace_name, os=os, parent_id=None) | |
872 | 801 | |
873 | 802 | def newInterface(self, name, mac="00:00:00:00:00:00", |
874 | 803 | ipv4_address="0.0.0.0", |
879 | 808 | ipv6_dns=[], network_segment="", hostname_resolution=[], |
880 | 809 | parent_id=None): |
881 | 810 | return model.common.factory.createModelObject( |
882 | model.hosts.Interface.class_signature, | |
883 | name, mac=mac, ipv4_address=ipv4_address, | |
811 | models.Interface.class_signature, name, | |
812 | self.mappers_manager.workspace_name, mac=mac, ipv4_address=ipv4_address, | |
884 | 813 | ipv4_mask=ipv4_mask, ipv4_gateway=ipv4_gateway, ipv4_dns=ipv4_dns, |
885 | 814 | ipv6_address=ipv6_address, ipv6_prefix=ipv6_prefix, |
886 | 815 | ipv6_gateway=ipv6_gateway, ipv6_dns=ipv6_dns, |
890 | 819 | def newService(self, name, protocol="tcp?", ports=[], status="running", |
891 | 820 | version="unknown", description="", parent_id=None): |
892 | 821 | return model.common.factory.createModelObject( |
893 | model.hosts.Service.class_signature, | |
894 | name, protocol=protocol, ports=ports, status=status, | |
822 | models.Service.class_signature, name, | |
823 | self.mappers_manager.workspace_name, protocol=protocol, ports=ports, status=status, | |
895 | 824 | version=version, description=description, parent_id=parent_id) |
896 | 825 | |
897 | 826 | def newVuln(self, name, desc="", ref=None, severity="", resolution="", |
898 | 827 | confirmed=False, parent_id=None): |
899 | 828 | return model.common.factory.createModelObject( |
900 | model.common.ModelObjectVuln.class_signature, | |
901 | name, desc=desc, ref=ref, severity=severity, resolution=resolution, | |
829 | models.Vuln.class_signature, name, | |
830 | self.mappers_manager.workspace_name, desc=desc, ref=ref, severity=severity, resolution=resolution, | |
902 | 831 | confirmed=confirmed, parent_id=parent_id) |
903 | 832 | |
904 | 833 | def newVulnWeb(self, name, desc="", ref=None, severity="", resolution="", |
906 | 835 | pname="", params="", query="", category="", confirmed=False, |
907 | 836 | parent_id=None): |
908 | 837 | return model.common.factory.createModelObject( |
909 | model.common.ModelObjectVulnWeb.class_signature, | |
910 | name, desc=desc, ref=ref, severity=severity, resolution=resolution, | |
838 | models.VulnWeb.class_signature, name, | |
839 | self.mappers_manager.workspace_name, desc=desc, ref=ref, severity=severity, resolution=resolution, | |
911 | 840 | website=website, path=path, request=request, response=response, |
912 | 841 | method=method, pname=pname, params=params, query=query, |
913 | 842 | category=category, confirmed=confirmed, parent_id=parent_id) |
914 | 843 | |
915 | 844 | def newNote(self, name, text, parent_id=None): |
916 | 845 | return model.common.factory.createModelObject( |
917 | model.common.ModelObjectNote.class_signature, | |
918 | name, text=text, parent_id=parent_id) | |
846 | models.Note.class_signature, name, | |
847 | self.mappers_manager.workspace_name, text=text, parent_id=parent_id) | |
919 | 848 | |
920 | 849 | def newCred(self, username, password, parent_id=None): |
921 | 850 | return model.common.factory.createModelObject( |
922 | model.common.ModelObjectCred.class_signature, | |
851 | models.Credential.class_signature, name, | |
923 | 852 | username, password=password, parent_id=parent_id) |
924 | 853 | |
925 | 854 | def getHost(self, name): |
926 | hosts_mapper = self.mappers_manager.getMapper( | |
927 | model.hosts.Host.class_signature) | |
855 | hosts_mapper = self.mappers_manager.getMapper(models.Host.class_signature) | |
928 | 856 | return hosts_mapper.find(name) |
929 | 857 | |
930 | 858 | def getAllHosts(self): |
933 | 861 | """ |
934 | 862 | try: |
935 | 863 | hosts = self.mappers_manager.getMapper( |
936 | model.hosts.Host.class_signature).getAll() | |
864 | models.Host.class_signature.getAll()) | |
937 | 865 | except: |
938 | 866 | hosts = [] |
939 | 867 | return hosts |
940 | 868 | |
941 | 869 | def getWebVulns(self): |
942 | 870 | return self.mappers_manager.getMapper( |
943 | model.common.ModelObjectVulnWeb.class_signature).getAll() | |
871 | models.Vuln.class_signature).getAll() | |
944 | 872 | |
945 | 873 | def getHostsCount(self): |
946 | 874 | """Get how many hosts are in the workspace. If it can't, it will |
947 | 875 | return zero.""" |
948 | 876 | try: |
949 | hosts = model.hosts.Host.class_signature | |
877 | hosts = models.Hosts.class_signature | |
950 | 878 | count = self.mappers_manager.getMapper(hosts).getCount() |
951 | 879 | except: |
952 | 880 | getLogger(self).debug( |
958 | 886 | """Get how many services are in the workspace. If it can't, it will |
959 | 887 | return zero.""" |
960 | 888 | try: |
961 | services = model.hosts.Service.class_signature | |
889 | services = models.Service.class_signature | |
962 | 890 | count = self.mappers_manager.getMapper(services).getCount() |
963 | 891 | except: |
964 | 892 | getLogger(self).debug( |
970 | 898 | """Get how many vulns (web + normal) are in the workspace. |
971 | 899 | If it can't, it will return zero.""" |
972 | 900 | try: |
973 | vulns = model.common.ModelObjectVuln.class_signature | |
974 | web_vulns = model.common.ModelObjectVulnWeb.class_signature | |
901 | vulns = models.Vuln.class_signature | |
902 | web_vulns = models.WebVuln.class_signature | |
975 | 903 | count = (self.mappers_manager.getMapper(vulns).getCount() + |
976 | 904 | self.mappers_manager.getMapper(web_vulns).getCount()) |
977 | 905 | except: |
29 | 29 | def getPropertiesDiff(self): |
30 | 30 | prop_diff = {} |
31 | 31 | for attrname in self.obj1.publicattrsrefs().keys(): |
32 | info = lambda attr_ref: attr_ref() if callable(attr_ref) else attr_ref | |
32 | def info(attr_ref): return attr_ref() if callable(attr_ref) else attr_ref | |
33 | 33 | prop1 = info(self.obj1.__getattribute__(self.obj1.publicattrsrefs().get(attrname))) |
34 | prop2 = info(self.obj2.__getattribute__(self.obj2.publicattrsrefs.get(attrname))) | |
34 | prop2 = info(self.obj2.__getattribute__(self.obj2.publicattrsrefs().get(attrname))) | |
35 | 35 | if prop1 != prop2: |
36 | 36 | prop_diff[attrname] = (prop1, prop2) |
37 | 37 |
0 | #!/usr/bin/env python | |
1 | ''' | |
2 | Faraday Penetration Test IDE | |
3 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
4 | See the file 'doc/LICENSE' for the license information | |
5 | ||
6 | ''' | |
7 | ||
8 | from config.configuration import getInstanceConfiguration | |
9 | from model.common import ModelObject, ModelObjectNote, ModelObjectVuln, ModelObjectVulnWeb, ModelObjectCred, ModelComposite, ModelLeaf | |
10 | from model.common import Metadata | |
11 | from utils.common import * | |
12 | from utils.decorators import updateLocalMetadata | |
13 | ||
14 | import model.api as api | |
15 | try: | |
16 | import IPy | |
17 | except ImportError: | |
18 | print "[-] Python module IPy was not found in the system, please install it and try again" | |
19 | print "[-] ex: sudo pip install IPy" | |
20 | CONF = getInstanceConfiguration() | |
21 | ||
22 | class Host(ModelComposite): | |
23 | """ | |
24 | Represents a host found in the network. | |
25 | A hosts can have 1 or more interfaces and also 1 or more services (apps) | |
26 | Services can be reached through all host interfaces or only some of them | |
27 | The host has some attributes that are filled by the pen test tools run by | |
28 | the user | |
29 | """ | |
30 | ||
31 | class_signature = "Host" | |
32 | ||
33 | def __init__(self, name, os = "Unknown", default_gateway=None, dic=None, parent_id=None): | |
34 | ModelComposite.__init__(self, parent_id) | |
35 | self._interfaces = {} | |
36 | self._applications = {} | |
37 | self.categories = [] | |
38 | if dic is not None: | |
39 | self._fromDict(dic) | |
40 | else: | |
41 | self.__init(name, os, default_gateway) | |
42 | ||
43 | def __init(self, name, os = "Unknown", default_gateway=None): | |
44 | self._name = None | |
45 | if name is not None: | |
46 | self.setName(name) | |
47 | self._name = name | |
48 | self._operating_system = os if os else "Unknown" | |
49 | self._default_gateway = api.getLocalDefaultGateway() \ | |
50 | if default_gateway is None else default_gateway | |
51 | ||
52 | def __str__(self): | |
53 | return "{0} ({1})".format(self.name, self.getVulnAmount()) | |
54 | ||
55 | def _updatePublicAttributes(self): | |
56 | ||
57 | self.publicattrs['Operating System'] = 'getOS' | |
58 | self.publicattrsrefs['Operating System'] = '_operating_system' | |
59 | ||
60 | def getVulnAmount(self): | |
61 | vuln_count = 0 | |
62 | vuln_count += len(self.getVulns()) | |
63 | for interface in self.getAllInterfaces(): | |
64 | vuln_count += len(interface.getVulns()) | |
65 | for service in interface.getAllServices(): | |
66 | vuln_count += len(service.getVulns()) | |
67 | return vuln_count | |
68 | ||
69 | ||
70 | def accept(self, visitor): | |
71 | """ Accept method for visitor in the host leaf""" | |
72 | for ints in self.getAllInterfaces(): | |
73 | ints.accept(visitor) | |
74 | visitor.visit(self) | |
75 | ||
76 | def getCategories(self): | |
77 | return self.categories | |
78 | ||
79 | def getCurrentCategory(self): | |
80 | ||
81 | cat = CONF.getDefaultCategory() | |
82 | try: | |
83 | cat = self.getCategories()[0] | |
84 | except: | |
85 | pass | |
86 | return cat | |
87 | ||
88 | def updateID(self): | |
89 | self._id = get_hash([self._name]) | |
90 | self._prependParentId() | |
91 | ||
92 | def setOS(self, newOS): | |
93 | self._operating_system = newOS | |
94 | ||
95 | def getOS(self): | |
96 | return self._operating_system | |
97 | ||
98 | operating_system = property(getOS, setOS) | |
99 | ||
100 | def setName(self, newName): | |
101 | ||
102 | self._name = newName | |
103 | ||
104 | def getName(self): | |
105 | return self._name | |
106 | ||
107 | name = property(getName, setName) | |
108 | ||
109 | def getDefaultGateway(self): | |
110 | return self._default_gateway | |
111 | ||
112 | def setDefaultGateway(self, default_gateway): | |
113 | self._default_gateway = default_gateway | |
114 | ||
115 | ||
116 | #@save | |
117 | @updateLocalMetadata | |
118 | def updateAttributes(self, name=None, description=None, os=None, owned=None): | |
119 | if name is not None: | |
120 | self.setName(name) | |
121 | if description is not None: | |
122 | self.setDescription(description) | |
123 | if os is not None: | |
124 | self.setOS(os) | |
125 | if owned is not None: | |
126 | self.setOwned(owned) | |
127 | ||
128 | def setInterfaces(self, interfaces): | |
129 | self._addChildsDict(interfaces) | |
130 | ||
131 | def getAllInterfaces(self, mode = 0): | |
132 | return self.getChildsByType(Interface.__name__) | |
133 | ||
134 | def getInterface(self, ID): | |
135 | """Return the interface of id ID, None if ID wasn't an interface or | |
136 | wasn't found among the children. | |
137 | """ | |
138 | interface = self.findChild(ID) | |
139 | return interface if interface.class_signature == "Interface" else None | |
140 | ||
141 | def getService(self, name): | |
142 | """ | |
143 | if name is found it returns the service object | |
144 | it returns None otherwise | |
145 | """ | |
146 | service = None | |
147 | for interface in self.getAllInterfaces(): | |
148 | if interface.getService(name): | |
149 | service = interface.getService(name) | |
150 | break | |
151 | return service | |
152 | ||
153 | @updateLocalMetadata | |
154 | def addApplication(self, newApp, update=False, setparent=True): # Deprecated | |
155 | return self._addValue("_applications", newApp, | |
156 | setparent=setparent, update=update) | |
157 | ||
158 | @updateLocalMetadata | |
159 | def delApplication(self, appID): # Deprecated | |
160 | ||
161 | app = self.getApplication(appID) | |
162 | if app is not None: | |
163 | for srv in app.getAllServices(): | |
164 | srv.delApplication(appID) | |
165 | ||
166 | return self._delValue("_applications", appID) | |
167 | ||
168 | def addApplicationFull(self, app): # Deprecated # Deprecated | |
169 | self.addApplication(app) | |
170 | ||
171 | def getAllApplications(self, mode = 0): # Deprecated | |
172 | """ | |
173 | return all applications in this interface | |
174 | mode = 0 returns a list of service objects | |
175 | mode = 1 returns a dictionary of service objects with their id as key | |
176 | """ | |
177 | return self._getAllValues("_applications", mode) | |
178 | ||
179 | def getApplication(self, name): # Deprecated | |
180 | """ | |
181 | if name is found it returns the application object | |
182 | it returns None otherwise | |
183 | """ | |
184 | return self._getValueByID("_applications", name) | |
185 | ||
186 | def __eq__(self, other_host): | |
187 | if isinstance(other_host, Host): | |
188 | if self._name == other_host.getName(): | |
189 | return True | |
190 | else: | |
191 | ||
192 | ip_addr_this = self.getIPv4Addresses() | |
193 | ip_addr_other = other_host.getIPv4Addresses() | |
194 | ||
195 | ||
196 | ||
197 | ||
198 | ||
199 | ||
200 | ||
201 | ||
202 | ||
203 | ||
204 | for addr in ip_addr_this: | |
205 | if addr in ip_addr_other and IPy.IP(addr).iptype() == "PUBLIC": | |
206 | return True | |
207 | ||
208 | return False | |
209 | ||
210 | def __ne__(self, other_host): | |
211 | return not self == other_host | |
212 | ||
213 | def getIPv4Addresses(self): | |
214 | """ | |
215 | returns a list of all ipv4 addresses from all interfaces on this host | |
216 | """ | |
217 | l = [interface.ipv4['address'] for name, interface in self._interfaces.items()] | |
218 | l.sort() | |
219 | return l | |
220 | ||
221 | def getIPv6Addresses(self): | |
222 | """ | |
223 | returns a list of all ipv4 addresses from all interfaces on this host | |
224 | """ | |
225 | l = [interface.ipv6['address'] for name, interface in self._interfaces.items()] | |
226 | l.sort() | |
227 | return l | |
228 | ||
229 | class Interface(ModelComposite): | |
230 | """ | |
231 | An interface in a host | |
232 | """ | |
233 | ||
234 | class_signature = "Interface" | |
235 | ||
236 | def __init__(self, name = "", mac = "00:00:00:00:00:00", | |
237 | ipv4_address = "0.0.0.0", ipv4_mask = "0.0.0.0", | |
238 | ipv4_gateway = "0.0.0.0", ipv4_dns = [], | |
239 | ipv6_address = "0000:0000:0000:0000:0000:0000:0000:0000", ipv6_prefix = "00", | |
240 | ipv6_gateway = "0000:0000:0000:0000:0000:0000:0000:0000", ipv6_dns = [], | |
241 | network_segment = "", hostname_resolution = None, parent_id=None): | |
242 | ||
243 | ModelComposite.__init__(self, parent_id) | |
244 | ||
245 | ||
246 | self._name = name | |
247 | self.mac = mac | |
248 | self.ipv4 = { | |
249 | "address" : ipv4_address, | |
250 | "mask" : ipv4_mask, | |
251 | "gateway" : ipv4_gateway, | |
252 | "DNS" : ipv4_dns | |
253 | } | |
254 | ||
255 | self.ipv6 = { | |
256 | "address" : ipv6_address, | |
257 | "prefix" : ipv6_prefix, | |
258 | "gateway" : ipv6_gateway, | |
259 | "DNS" : ipv6_dns | |
260 | } | |
261 | ||
262 | ||
263 | self._services = {} | |
264 | ||
265 | ||
266 | self.network_segment = network_segment | |
267 | ||
268 | ||
269 | self._hostnames=[] | |
270 | if hostname_resolution is not None: | |
271 | if isinstance(hostname_resolution, (str,unicode)): | |
272 | self._hostnames.append(hostname_resolution) | |
273 | else: | |
274 | self._hostnames = hostname_resolution | |
275 | ||
276 | self.amount_ports_opened = 0 | |
277 | self.amount_ports_closed = 0 | |
278 | self.amount_ports_filtered = 0 | |
279 | ||
280 | def __str__(self): | |
281 | return "{0}".format(self.name) | |
282 | ||
283 | def _updatePublicAttributes(self): | |
284 | ||
285 | self.publicattrs['MAC Address'] = 'mac' | |
286 | self.publicattrs['IPV4 Settings'] = 'ipv4' | |
287 | self.publicattrs['IPV6 Settings'] = 'ipv6' | |
288 | self.publicattrs['Network Segment'] = 'network_segment' | |
289 | self.publicattrs['Hostnames'] = 'getHostnames' | |
290 | self.publicattrs['Ports opened'] = 'amount_ports_opened' | |
291 | self.publicattrs['Ports closed'] = 'amount_ports_closed' | |
292 | self.publicattrs['Ports filtered'] = 'amount_ports_filtered' | |
293 | ||
294 | self.publicattrsrefs['MAC Address'] = 'mac' | |
295 | self.publicattrsrefs['IPV4 Settings'] = 'ipv4' | |
296 | self.publicattrsrefs['IPV6 Settings'] = 'ipv6' | |
297 | self.publicattrsrefs['Network Segment'] = 'network_segment' | |
298 | self.publicattrsrefs['Hostnames'] = '_hostnames' | |
299 | ||
300 | def defaultValues(self): | |
301 | defVals = ModelObject.defaultValues(self) | |
302 | defVals.extend([{ | |
303 | "address" : "0.0.0.0", | |
304 | "mask" : "0.0.0.0", | |
305 | "gateway" : "0.0.0.0", | |
306 | "DNS" : [] | |
307 | }, {'prefix': '00', 'gateway': '0000:0000:0000:0000:0000:0000:0000:0000', 'DNS': [], 'address': '0000:0000:0000:0000:0000:0000:0000:0000'}]) | |
308 | return defVals | |
309 | ||
310 | def accept(self, visitor): | |
311 | for servs in self.getAllServices(): | |
312 | servs.accept(visitor) | |
313 | visitor.visit(self) | |
314 | ||
315 | def tieBreakable(self, property_key): | |
316 | if property_key in ["_hostnames"]: | |
317 | return True | |
318 | return False | |
319 | ||
320 | def tieBreak(self, key, prop1, prop2): | |
321 | if key == "_hostnames": | |
322 | prop1.extend(prop2) | |
323 | return list(set(prop1)) | |
324 | return None | |
325 | ||
326 | def updateID(self): | |
327 | self._id = get_hash([self.network_segment, self.ipv4["address"], self.ipv6["address"]]) | |
328 | self._prependParentId() | |
329 | ||
330 | def setName(self, name): | |
331 | self._name = name | |
332 | ||
333 | def getName(self): | |
334 | return self._name | |
335 | ||
336 | def setMAC(self, mac): | |
337 | self.mac = mac | |
338 | ||
339 | def getMAC(self): | |
340 | return self.mac | |
341 | ||
342 | def setNetworkSegment(self, network_segment): | |
343 | self.network_segment = network_segment | |
344 | ||
345 | def getNetworkSegment(self): | |
346 | return self.network_segment | |
347 | ||
348 | def setIPv4(self, ipv4): | |
349 | self.ipv4["address"] = ipv4.get("address", None) | |
350 | self.ipv4["mask"] = ipv4.get("mask", None) | |
351 | self.ipv4["gateway"] = ipv4.get("gateway", None) | |
352 | self.ipv4["DNS"] = ipv4.get("DNS", None) | |
353 | ||
354 | def getIPv4(self): | |
355 | return self.ipv4 | |
356 | ||
357 | def getIPv4Address(self): | |
358 | return self.ipv4["address"] | |
359 | ||
360 | def getIPv4Mask(self): | |
361 | return self.ipv4["mask"] | |
362 | ||
363 | def getIPv4Gateway(self): | |
364 | return self.ipv4["gateway"] | |
365 | ||
366 | def getIPv4DNS(self): | |
367 | return self.ipv4["DNS"] | |
368 | ||
369 | def setIPv6(self, ipv6): | |
370 | self.ipv6["address"] = ipv6.get("address", None) | |
371 | self.ipv6["prefix"] = ipv6.get("prefix", None) | |
372 | self.ipv6["gateway"] = ipv6.get("gateway", None) | |
373 | self.ipv6["DNS"] = ipv6.get("DNS", None) | |
374 | ||
375 | def getIPv6(self): | |
376 | return self.ipv6 | |
377 | ||
378 | def getIPv6Address(self): | |
379 | return self.ipv6["address"] | |
380 | ||
381 | def getIPv6Prefix(self): | |
382 | return self.ipv6["prefix"] | |
383 | ||
384 | def getIPv6Gateway(self): | |
385 | return self.ipv6["gateway"] | |
386 | ||
387 | def getIPv6DNS(self): | |
388 | return self.ipv6["DNS"] | |
389 | ||
390 | def setPortsOpened(self, ports_opened): | |
391 | self.amount_ports_opened = ports_opened | |
392 | ||
393 | def getPortsOpened(self): | |
394 | return self.amount_ports_opened | |
395 | ||
396 | def setPortsClosed(self, ports_closed): | |
397 | self.amount_ports_closed = ports_closed | |
398 | ||
399 | def getPortsClosed(self): | |
400 | return self.amount_ports_closed | |
401 | ||
402 | def setPortsFiltered(self, ports_filtered): | |
403 | self.amount_ports_filtered = ports_filtered | |
404 | ||
405 | def getPortsFiltered(self): | |
406 | return self.amount_ports_filtered | |
407 | ||
408 | @updateLocalMetadata | |
409 | def addService(self, newService, update=False, setparent=True): # Deprecated | |
410 | res = self._addValue("_services", newService, setparent=setparent, update=update) | |
411 | if res: newService.addInterface(self) | |
412 | return res | |
413 | ||
414 | @updateLocalMetadata | |
415 | def delService(self, srvID, checkFullDelete=True): # Deprecated | |
416 | res = True | |
417 | res = self._delValue("_services", srvID) | |
418 | return res | |
419 | ||
420 | def getAllServices(self, mode = 0): | |
421 | return self.getChildsByType(Service.__name__) | |
422 | ||
423 | def getService(self, ID): | |
424 | """Get a Service from an ID. Return the service object if found, | |
425 | None if ID wasn't a service or wasn't found among the children. | |
426 | """ | |
427 | service = self.findChild(ID) | |
428 | return service if service.class_signature == "Service" else None | |
429 | ||
430 | def setServices(self, services): | |
431 | self._addChildsDict(services) | |
432 | ||
433 | def addHostname(self, hostname): | |
434 | if hostname not in self._hostnames: | |
435 | self._hostnames.append(hostname) | |
436 | ||
437 | def removeHostname(self, hostname): | |
438 | if hostname in self._hostnames: | |
439 | self._hostnames.remove(hostname) | |
440 | ||
441 | def getHostnames(self): | |
442 | return self._hostnames | |
443 | ||
444 | def setHostnames(self, hostnames): | |
445 | self._hostnames = hostnames | |
446 | ||
447 | #@save | |
448 | @updateLocalMetadata | |
449 | def updateAttributes(self, name=None, description=None, hostnames=None, mac=None, ipv4=None, ipv6=None, | |
450 | network_segment=None, amount_ports_opened=None, amount_ports_closed=None, | |
451 | amount_ports_filtered=None, owned=None): | |
452 | ||
453 | if name is not None: | |
454 | self.setName(name) | |
455 | if description is not None: | |
456 | self.setDescription(description) | |
457 | if hostnames is not None: | |
458 | self.setHostnames(hostnames) | |
459 | if mac is not None: | |
460 | self.setMAC(mac) | |
461 | if ipv4 is not None: | |
462 | self.setIPv4(ipv4) | |
463 | if ipv6 is not None: | |
464 | self.setIPv6(ipv6) | |
465 | if network_segment is not None: | |
466 | self.setNetworkSegment(network_segment) | |
467 | if amount_ports_opened is not None: | |
468 | self.setPortsOpened(amount_ports_opened) | |
469 | if amount_ports_closed is not None: | |
470 | self.setPortsClosed(amount_ports_closed) | |
471 | if amount_ports_filtered is not None: | |
472 | self.setPortsFiltered(amount_ports_filtered) | |
473 | if owned is not None: | |
474 | self.setOwned(owned) | |
475 | ||
476 | ||
477 | class Service(ModelComposite): | |
478 | """ | |
479 | A service or application running in a host | |
480 | Commonly a service will have a name or description, a set of ports in which | |
481 | is listening and also a particular version | |
482 | """ | |
483 | ||
484 | class_signature = "Service" | |
485 | ||
486 | def __init__(self, name, protocol="TCP", ports=None, status="running", | |
487 | version="unknown", description = "", parent_id=None): | |
488 | ModelComposite.__init__(self, parent_id) | |
489 | ||
490 | self._name = name | |
491 | self.description = description | |
492 | self.setProtocol(protocol) | |
493 | self._ports=[] | |
494 | self.setPorts(ports) | |
495 | self._status = status | |
496 | self._version = version | |
497 | self._interfaces = {} | |
498 | self._applications = {} | |
499 | self._creds = {} | |
500 | ||
501 | def _updatePublicAttributes(self): | |
502 | ||
503 | self.publicattrsrefs['Ports'] = '_ports' | |
504 | self.publicattrsrefs['Protocol'] = '_protocol' | |
505 | self.publicattrsrefs['Status'] = '_status' | |
506 | self.publicattrsrefs['Version'] = '_version' | |
507 | ||
508 | self.publicattrs['Ports'] = 'getPorts' | |
509 | self.publicattrs['Protocol'] = 'getProtocol' | |
510 | self.publicattrs['Status'] = 'getStatus' | |
511 | self.publicattrs['Version'] = 'getVersion' | |
512 | ||
513 | def __str__(self): | |
514 | return "{0} ({1})".format(self.name, self.getVulnAmount()) | |
515 | ||
516 | def getVulnAmount(self): | |
517 | return len(self.getVulns()) | |
518 | ||
519 | def setName(self, name): | |
520 | self._name = name | |
521 | ||
522 | def getName(self): | |
523 | return self._name | |
524 | ||
525 | ||
526 | def setProtocol(self, protocol): | |
527 | self._protocol = protocol.lower() | |
528 | ||
529 | def getProtocol(self): | |
530 | return self._protocol | |
531 | ||
532 | def addPort(self, port): | |
533 | if port not in self._ports: | |
534 | self._ports.append(port) | |
535 | ||
536 | def removePort(self, port): | |
537 | if port in self._ports: | |
538 | self._ports.remove(port) | |
539 | ||
540 | def getPorts(self): | |
541 | return self._ports | |
542 | ||
543 | def setPorts(self, ports): | |
544 | if ports is not None: | |
545 | if isinstance(ports, (str,unicode)): | |
546 | self._ports = [int(ports)] | |
547 | elif isinstance(ports, int): | |
548 | self._ports = [ports] | |
549 | elif isinstance(ports, list): | |
550 | self._ports = [int(p) for p in ports] | |
551 | else: | |
552 | api.devlog("ports must be a string, an int o a list of any of those types") | |
553 | ||
554 | def setStatus(self, status): | |
555 | self._status = status | |
556 | ||
557 | def getStatus(self): | |
558 | return self._status | |
559 | ||
560 | def setVersion(self, version): | |
561 | self._version = version | |
562 | ||
563 | def getVersion(self): | |
564 | return self._version | |
565 | ||
566 | def updateID(self): | |
567 | self._id = get_hash([self._protocol, ":".join(str(self._ports))]) | |
568 | self._prependParentId() | |
569 | ||
570 | #@save | |
571 | @updateLocalMetadata | |
572 | def updateAttributes(self, name=None, description=None, protocol=None, ports=None, | |
573 | status=None, version=None, owned=None): | |
574 | if name is not None: | |
575 | self.setName(name) | |
576 | if description is not None: | |
577 | self.setDescription(description) | |
578 | if protocol is not None: | |
579 | self.setProtocol(protocol) | |
580 | if ports is not None: | |
581 | self.setPorts(ports) | |
582 | if status is not None: | |
583 | self.setStatus(status) | |
584 | if version is not None: | |
585 | self.setVersion(version) | |
586 | if owned is not None: | |
587 | self.setOwned(owned) | |
588 | ||
589 | def _checkFullDelete(self): | |
590 | api.devlog("Doing service checkFullDelete") | |
591 | if not self._interfaces and not self._applications: | |
592 | if self.getParent() is not None: | |
593 | self.getParent().delService(self.getID()) | |
594 | ||
595 | def getAllInterfaces(self, mode = 0): | |
596 | """ | |
597 | return all interfaces in this host | |
598 | mode = 0 returns a list of interface objects | |
599 | mode = 1 returns a dictionary of interface objects with their id as key | |
600 | """ | |
601 | return self._getAllValues("_interfaces", mode) | |
602 | ||
603 | def getInterface(self, ID): | |
604 | """Gets the interface with id ID. If ID isn't found or isn't an | |
605 | interface, return None. | |
606 | """ | |
607 | interface = self.findChild(ID) | |
608 | return interface if interface.class_signature == "Interface" else None | |
609 | ||
610 | def addApplication(self, newApp, update=False): # Deprecated | |
611 | res = self._addValue("_applications", newApp, update=update) | |
612 | if res: newApp.addService(self) | |
613 | return res | |
614 | ||
615 | def delApplication(self, appID, checkFullDelete=True): # Deprecated | |
616 | app = self.getApplication(appID) | |
617 | res = self._delValue("_applications", appID) | |
618 | if res: | |
619 | if app is not None: | |
620 | app.delService(self.getID(), checkFullDelete) | |
621 | ||
622 | if checkFullDelete: self._checkFullDelete() | |
623 | return res | |
624 | ||
625 | def getAllApplications(self, mode = 0): # Deprecated | |
626 | """ | |
627 | return all applications in this service | |
628 | mode = 0 returns a list of applications objects | |
629 | mode = 1 returns a dictionary of application objects with their id as key | |
630 | """ | |
631 | return self._getAllValues("_applications", mode) | |
632 | ||
633 | def getApplication(self, name): # Deprecated | |
634 | """ | |
635 | if name is found it returns the application object | |
636 | it returns None otherwise | |
637 | """ | |
638 | return self._getValueByID("_applications", name) | |
639 | ||
640 | class HostApplication(ModelComposite): # Deprecated | |
641 | """ | |
642 | An application running in a host | |
643 | The application can be related to more than one service | |
644 | Commonly this will have a name, description, version and status | |
645 | """ | |
646 | ||
647 | class_signature = "HostApplication" | |
648 | ||
649 | def __init__(self, name, status = "running", version = "unknonw"): | |
650 | ModelComposite.__init__(self) | |
651 | ||
652 | self._name = name | |
653 | self._status = status | |
654 | self._version = version | |
655 | ||
656 | self._services = {} | |
657 | ||
658 | def _updatePublicAttributes(self): | |
659 | self.publicattrs['Status'] = 'getStatus' | |
660 | self.publicattrs['Version'] = 'getVersion' | |
661 | ||
662 | def setName(self, name): | |
663 | self._name = name | |
664 | ||
665 | def getName(self): | |
666 | return self._name | |
667 | ||
668 | def setStatus(self, status): | |
669 | self._status = status | |
670 | ||
671 | def getStatus(self): | |
672 | return self._status | |
673 | ||
674 | def setVersion(self, version): | |
675 | self._version = version | |
676 | ||
677 | def getVersion(self): | |
678 | return self._version | |
679 | ||
680 | def updateID(self): | |
681 | self._id = get_hash([self._name, self._version]) | |
682 | self._prependParentId() | |
683 | ||
684 | @updateLocalMetadata | |
685 | def updateAttributes(self, name=None, description=None, status=None, version=None, owned=None): | |
686 | if name is not None: | |
687 | self.setName(name) | |
688 | if description is not None: | |
689 | self.setDescription(description) | |
690 | if status is not None: | |
691 | self.setStatus(status) | |
692 | if version is not None: | |
693 | self.setVersion(version) | |
694 | if owned is not None: | |
695 | self.setOwned(owned) | |
696 | ||
697 | @updateLocalMetadata | |
698 | def addService(self, newService, update=False): | |
699 | res = self._addValue("_services", newService, update=update) | |
700 | if res: newService.addApplication(self) | |
701 | if self.getParent() is not None: | |
702 | self.getParent().addService(newService) | |
703 | return res | |
704 | ||
705 | @updateLocalMetadata | |
706 | def delService(self, srvID, checkFullDelete=True): | |
707 | srv = self.getService(srvID) | |
708 | res = self._delValue("_services", srvID) | |
709 | if res: | |
710 | if srv is not None: | |
711 | srv.delApplication(self.getID(), checkFullDelete) | |
712 | return res | |
713 | ||
714 | def getAllServices(self, mode = 0): | |
715 | """ | |
716 | return all services in this interface | |
717 | mode = 0 returns a list of service objects | |
718 | mode = 1 returns a dictionary of service objects with their id as key | |
719 | """ | |
720 | return self._getAllValues("_services", mode) | |
721 | ||
722 | def getService(self, ID): | |
723 | """ | |
724 | if name is found it returns the service object | |
725 | it returns None otherwise | |
726 | """ | |
727 | return self._getValueByID("_services", name) |
0 | ''' | |
1 | Faraday Penetration Test IDE | |
2 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
3 | See the file 'doc/LICENSE' for the license information | |
4 | ||
5 | ''' |
0 | ''' | |
1 | Faraday Penetration Test IDE | |
2 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
3 | See the file 'doc/LICENSE' for the license information | |
4 | ||
5 | ''' | |
6 | ||
7 | import model | |
8 | import threading | |
9 | import traceback | |
10 | from controllers.change import ChangeController | |
11 | ||
12 | ||
13 | class WorkspacePersister(object): | |
14 | _instance = None | |
15 | _persister = None | |
16 | _workspace = None | |
17 | _workspace_autoloader = None | |
18 | _pending_actions = None | |
19 | _change_controller = ChangeController() | |
20 | ||
21 | def __new__(cls, *args, **kargs): | |
22 | if cls._instance is None: | |
23 | cls._instance = object.__new__(cls, *args, **kargs) | |
24 | return cls._instance | |
25 | ||
26 | def setPersister(self, workspace, persister): | |
27 | WorkspacePersister._persister = persister | |
28 | WorkspacePersister._workspace = workspace | |
29 | WorkspacePersister._change_controller.setWorkspace(workspace) | |
30 | WorkspacePersister._workspace_autoloader = WorkspaceAutoSync(self.loadChanges, self.backendChangeListener) | |
31 | WorkspacePersister._workspace_autoloader.start() | |
32 | WorkspacePersister._pending_actions = PendingTransactionsQueue() | |
33 | ||
34 | @staticmethod | |
35 | def stopThreads(): | |
36 | WorkspacePersister._workspace_autoloader.stop() | |
37 | ||
38 | def loadChanges(self, changes): | |
39 | self._change_controller.loadChanges(changes) | |
40 | ||
41 | def reloadWorkspace(self): | |
42 | WorkspacePersister._workspace.load() | |
43 | ||
44 | @staticmethod | |
45 | def addPendingAction(obj, func, args, kwargs): | |
46 | if "wait" not in func.__name__: | |
47 | WorkspacePersister._pending_actions.pushPendingTransaction(obj, func, args, kwargs) | |
48 | ||
49 | @staticmethod | |
50 | def reExecutePendingActions(): | |
51 | for (obj, func, args, kwargs) in WorkspacePersister._pending_actions: | |
52 | func(obj, *args, **kwargs) | |
53 | ||
54 | model.api.devlog("Re executing") | |
55 | ||
56 | @staticmethod | |
57 | def notifyPersisterConnectionLost(): | |
58 | WorkspacePersister._workspace.notifyWorkspaceNoConnection() | |
59 | ||
60 | def backendChangeListener(self): | |
61 | changes = WorkspacePersister._persister.waitForDBChange(WorkspacePersister._workspace.name) | |
62 | return changes | |
63 | ||
64 | @staticmethod | |
65 | def save(obj): | |
66 | if WorkspacePersister._workspace is not None: | |
67 | WorkspacePersister._workspace.saveObj(obj) | |
68 | ||
69 | @staticmethod | |
70 | def delete(obj): | |
71 | if WorkspacePersister._workspace: | |
72 | WorkspacePersister._workspace.delObj(obj) | |
73 | ||
74 | class WorkspaceAutoSync(threading.Thread): | |
75 | def __init__(self, action_callback, listener): | |
76 | threading.Thread.__init__(self) | |
77 | self.setDaemon(True) | |
78 | self._stop = False | |
79 | self._listener = listener | |
80 | self._action = action_callback | |
81 | ||
82 | def run(self): | |
83 | while not self._stop: | |
84 | try: | |
85 | result = self._listener() | |
86 | if result: | |
87 | model.api.devlog("Changes found: %s" % result) | |
88 | self._action(result) | |
89 | except Exception, e: | |
90 | model.api.devlog("An exception was captured while saving workspaces\n%s" % traceback.format_exc()) | |
91 | ||
92 | def stop(self): | |
93 | self._stop = True | |
94 | ||
95 | def start(self): | |
96 | self._stop = False | |
97 | threading.Thread.start(self) | |
98 | ||
99 | ||
100 | class PendingTransactionsQueue(object): | |
101 | def __init__(self): | |
102 | self.pending = [] | |
103 | ||
104 | def pushPendingTransaction(self, obj, func, args, kwargs): | |
105 | self.pending.insert(0, (obj, func, args, kwargs)) | |
106 | ||
107 | def __iter__(self): | |
108 | return self | |
109 | ||
110 | def next(self): | |
111 | try: | |
112 | return self.pending.pop() | |
113 | except IndexError: | |
114 | raise StopIteration | |
115 |
6 | 6 | |
7 | 7 | ''' |
8 | 8 | import requests, json |
9 | from persistence.server.server_io_exceptions import ChangesStreamStoppedAbruptly | |
9 | 10 | |
10 | 11 | class CouchChangesStream(object): |
11 | def __init__(self, workspace_name, server_url, **params): | |
12 | def __init__(self, workspace_name, server_url, since=0, heartbeat='1000', feed='continuous', **params): | |
12 | 13 | self._base_url = server_url |
13 | 14 | self._change_url = "{0}/_changes".format(server_url) |
15 | self.since = since | |
16 | self.heartbeat = heartbeat | |
17 | self.feed = feed | |
14 | 18 | self._params = params |
15 | 19 | self._response = None |
16 | 20 | self._stop = False |
26 | 30 | |
27 | 31 | def __iter__(self): |
28 | 32 | try: |
29 | self._response = requests.get(self._change_url, self._params, stream=True) | |
30 | if self._response.status_code != 200: | |
31 | raise requests.exceptions.RequestException | |
33 | params = {'since' : self.since, 'heartbeat': self.heartbeat, 'feed': self.feed} | |
34 | self._response = requests.get(self._change_url, stream=True, params=params, **self._params) | |
32 | 35 | if self._response: |
33 | 36 | for raw_line in self._response.iter_lines(): |
34 | 37 | line = self._sanitize(raw_line) |
41 | 44 | object_type, object_name = self._get_object_type_and_name_from_change(change) |
42 | 45 | yield change, object_type, object_name |
43 | 46 | if not self._stop: # why did we stop if no one asked me to stop? |
44 | raise requests.exceptions.RequestException | |
45 | except requests.exceptions.RequestException: | |
47 | raise ChangesStreamStoppedAbruptly | |
48 | ||
49 | except (requests.exceptions.RequestException, ChangesStreamStoppedAbruptly): | |
46 | 50 | self.stop() |
47 | raise | |
51 | raise ChangesStreamStoppedAbruptly | |
48 | 52 | except Exception as e: |
49 | 53 | self.stop() |
50 | 54 | |
51 | 55 | def _get_object_type_and_name_from_change(self, change): |
52 | 56 | try: |
53 | 57 | id = change['id'] |
54 | response = requests.get("{0}/{1}".format(self._base_url, id)) | |
58 | response = requests.get("{0}/{1}".format(self._base_url, id), **self._params) | |
55 | 59 | object_json = response.json() |
56 | 60 | except Exception: |
57 | 61 | return None, None |
8 | 8 | import glob |
9 | 9 | import os |
10 | 10 | import sys |
11 | from time import time | |
12 | import traceback | |
11 | 13 | from threading import Lock |
12 | 14 | from persistence.server import server |
15 | from persistence.server.server_io_exceptions import (WrongObjectSignature, | |
16 | CantAccessConfigurationWithoutTheClient) | |
17 | ||
13 | 18 | from persistence.server.utils import (force_unique, |
19 | get_hash, | |
14 | 20 | get_host_properties, |
15 | 21 | get_interface_properties, |
16 | 22 | get_service_properties, |
18 | 24 | get_vuln_web_properties, |
19 | 25 | get_note_properties, |
20 | 26 | get_credential_properties, |
21 | get_command_properties, | |
22 | WrongObjectSignature) | |
27 | get_command_properties) | |
23 | 28 | |
24 | 29 | from model.diff import ModelObjectDiff, MergeSolver |
25 | 30 | from model.conflict import ConflictUpdate |
26 | 31 | from config.configuration import getInstanceConfiguration |
27 | 32 | from functools import wraps |
28 | ||
29 | CONF = getInstanceConfiguration() | |
33 | from difflib import Differ | |
34 | ||
35 | ||
36 | FARADAY_UP = True | |
37 | MERGE_STRATEGY = None # you may change it the string 'NEW' to prefer new objects | |
38 | # you may ask why this can be None type or 'New' as a string | |
39 | # the answer is: Faraday. | |
40 | ||
41 | def _conf(): | |
42 | if FARADAY_UP: | |
43 | from config.configuration import getInstanceConfiguration | |
44 | return getInstanceConfiguration() | |
45 | else: | |
46 | raise CantAccessConfigurationWithoutTheClient | |
47 | ||
48 | def _get_merge_strategy(): | |
49 | try: | |
50 | merge_strategy = _conf().getMergeStrategy() | |
51 | except CantAccessConfigurationWithoutTheClient: | |
52 | merge_strategy = MERGE_STRATEGY | |
53 | return merge_strategy | |
30 | 54 | |
31 | 55 | _CHANGES_LOCK = Lock() |
32 | 56 | def get_changes_lock(): |
46 | 70 | return json |
47 | 71 | return func_wrapper |
48 | 72 | |
73 | def _flatten_dictionary(dictionary): | |
74 | """Given a dictionary with dictionaries inside, create a new flattened | |
75 | dictionary from that one and return it. | |
76 | ||
77 | It's not as general as it sounds. Do not use without looking at the | |
78 | implementation. | |
79 | """ | |
80 | flattened_dict = {} | |
81 | if dictionary.get('_id'): | |
82 | flattened_dict[u'_id'] = dictionary['_id'] | |
83 | if dictionary.get('id'): | |
84 | flattened_dict[u'id'] = dictionary['id'] | |
85 | for k, v in dictionary.get('value', {}).items(): | |
86 | if k != '_id': # this is the couch id, which we have saved on 'id' | |
87 | flattened_dict[k] = v | |
88 | return flattened_dict | |
89 | ||
90 | # NOTE: what is a faraday_ready object? | |
91 | # it's an instance of the classes defined on this module | |
92 | # created from a dictionary of faraday_ready_dictionaries | |
93 | # faraday_ready_dictionaries are the dictionaries gotten from | |
94 | # the server's json response with adecuate transformations applied to them | |
95 | # so as to be able to create the needed objects | |
96 | ||
97 | # i called them 'faraday ready' because they are _ready_ for the faraday | |
98 | # client, even if they come from the server: they should have the same | |
99 | # interface as the old style objects, from when we kept them on memory | |
100 | ||
49 | 101 | def _get_faraday_ready_objects(workspace_name, faraday_ready_object_dictionaries, |
50 | 102 | faraday_object_name): |
51 | 103 | """Takes a workspace name, a faraday object ('hosts', 'vulns', |
53 | 105 | the information about the objects live) and an arbitray number |
54 | 106 | of params to customize to request. |
55 | 107 | |
56 | Return a list of faraday objects | |
57 | (_Host, _Interface, _Service, _Vuln, _WevVuln) which the same interface | |
58 | for getting attribuetes than those defined my the ModelController. | |
59 | """ | |
60 | object_to_class = {'hosts': _Host, | |
61 | 'vulns': _Vuln, | |
62 | 'vulns_web': _VulnWeb, | |
63 | 'interfaces': _Interface, | |
64 | 'services': _Service, | |
65 | 'notes': _Note, | |
66 | 'credentials': _Credential, | |
67 | 'commands': _Command} | |
108 | Return a list of faraday objects: either | |
109 | Host, Interface, Service, Vuln, VulnWeb, Credential or Command. | |
110 | """ | |
111 | object_to_class = {'hosts': Host, | |
112 | 'vulns': Vuln, | |
113 | 'vulns_web': VulnWeb, | |
114 | 'interfaces': Interface, | |
115 | 'services': Service, | |
116 | 'notes': Note, | |
117 | 'credentials': Credential, | |
118 | 'commands': Command} | |
68 | 119 | |
69 | 120 | appropiate_class = object_to_class[faraday_object_name] |
70 | 121 | faraday_objects = [] |
71 | 122 | if faraday_ready_object_dictionaries: |
72 | 123 | for object_dictionary in faraday_ready_object_dictionaries: |
73 | faraday_objects.append(appropiate_class(object_dictionary, workspace_name)) | |
124 | flattened_object_dictionary = _flatten_dictionary(object_dictionary) | |
125 | faraday_objects.append(appropiate_class(flattened_object_dictionary, workspace_name)) | |
74 | 126 | return faraday_objects |
75 | 127 | |
76 | 128 | def _get_faraday_ready_hosts(workspace_name, hosts_dictionaries): |
129 | """Return a list of Hosts created with the information found on hosts_dictionaries""" | |
77 | 130 | return _get_faraday_ready_objects(workspace_name, hosts_dictionaries, 'hosts') |
78 | 131 | |
79 | 132 | def _get_faraday_ready_vulns(workspace_name, vulns_dictionaries, vulns_type=None): |
133 | """Return a list of Vuln or VulnWeb objects created with the information found on | |
134 | vulns_dictionaries. | |
135 | ||
136 | If vulns_type is specified, the returned list will only contain vuln_type objects. | |
137 | Otherwise, vuln_type will be inferred for every vuln_dictionary. | |
138 | """ | |
80 | 139 | if vulns_type: |
81 | 140 | return _get_faraday_ready_objects(workspace_name, vulns_dictionaries, vulns_type) |
82 | 141 | |
87 | 146 | return faraday_ready_vulns + faraday_ready_web_vulns |
88 | 147 | |
89 | 148 | def _get_faraday_ready_services(workspace_name, services_dictionaries): |
149 | """Return a list of Services created with the information found on services_dictionaries""" | |
90 | 150 | return _get_faraday_ready_objects(workspace_name, services_dictionaries, 'services') |
91 | 151 | |
92 | 152 | def _get_faraday_ready_interfaces(workspace_name, interfaces_dictionaries): |
153 | """Return a list of Interfaces created with the information found on interfaces_dictionaries""" | |
93 | 154 | return _get_faraday_ready_objects(workspace_name, interfaces_dictionaries, 'interfaces') |
94 | 155 | |
95 | 156 | def _get_faraday_ready_credentials(workspace_name, credentials_dictionaries): |
157 | """Return a list of Credentials created with the information found on credentials_dictionaries""" | |
96 | 158 | return _get_faraday_ready_objects(workspace_name, credentials_dictionaries, 'credentials') |
97 | 159 | |
98 | 160 | def _get_faraday_ready_notes(workspace_name, notes_dictionaries): |
161 | """Return a list of Notes created with the information found on notes_dictionaries""" | |
99 | 162 | return _get_faraday_ready_objects(workspace_name, notes_dictionaries, 'notes') |
100 | 163 | |
101 | 164 | def _get_faraday_ready_commands(workspace_name, commands_dictionaries): |
165 | """Return a list of Commands created with the information found on commands_dictionaries""" | |
102 | 166 | return _get_faraday_ready_objects(workspace_name, commands_dictionaries, 'commands') |
103 | 167 | |
104 | def get_changes_stream(workspace_name, **params): | |
168 | def get_changes_stream(workspace_name): | |
169 | """Take a workspace_name as a string. | |
170 | Return a couchDB change_stream with the changes relevant to the workspace | |
171 | of name workspace_name. | |
172 | The change stream will have heartbeet set to 1000. | |
173 | """ | |
105 | 174 | since = server.get_workspace(workspace_name)['last_seq'] |
106 | 175 | return server.get_changes_stream(workspace_name, since=since, |
107 | 176 | heartbeat='1000') |
120 | 189 | return force_unique(get_hosts(workspace_name, couchid=host_id)) |
121 | 190 | |
122 | 191 | def get_all_vulns(workspace_name, **params): |
192 | """Take a workspace name and a arbitrary number of params to customize the | |
193 | request. | |
194 | ||
195 | Return a list with Vuln and VulnWeb objects. | |
196 | """ | |
123 | 197 | vulns_dictionaries = server.get_all_vulns(workspace_name, **params) |
124 | 198 | return _get_faraday_ready_vulns(workspace_name, vulns_dictionaries) |
125 | 199 | |
176 | 250 | return force_unique(get_services(workspace_name, couchid=service_id)) |
177 | 251 | |
178 | 252 | def get_credentials(workspace_name, **params): |
253 | """Take a workspace name and a arbitrary number of params to customize the | |
254 | request. | |
255 | ||
256 | Return a list of Credential objects | |
257 | """ | |
179 | 258 | credentials_dictionary = server.get_credentials(workspace_name, **params) |
180 | 259 | return _get_faraday_ready_credentials(workspace_name, credentials_dictionary) |
181 | 260 | |
182 | 261 | def get_credential(workspace_name, credential_id): |
262 | """Return the Credential of id credential_id. None if not found.""" | |
183 | 263 | return force_unique(get_credentials(workspace_name, couchid=credential_id)) |
184 | 264 | |
185 | 265 | def get_notes(workspace_name, **params): |
266 | """Take a workspace name and a arbitrary number of params to customize the | |
267 | request. | |
268 | ||
269 | Return a list of Note objects | |
270 | """ | |
186 | 271 | notes_dictionary = server.get_notes(workspace_name, **params) |
187 | 272 | return _get_faraday_ready_notes(workspace_name, notes_dictionary) |
188 | 273 | |
189 | 274 | def get_note(workspace_name, note_id): |
275 | """Return the Note of id note_id. None if not found.""" | |
190 | 276 | return force_unique(get_notes(workspace_name, couchid=note_id)) |
191 | 277 | |
192 | 278 | def get_workspace(workspace_name): |
195 | 281 | return _Workspace(workspace, workspace_name) if workspace else None |
196 | 282 | |
197 | 283 | def get_commands(workspace_name, **params): |
284 | """Take a workspace name and a arbitrary number of params to customize the | |
285 | request. | |
286 | ||
287 | Return a list of Command objects | |
288 | """ | |
198 | 289 | commands_dictionary = server.get_commands(workspace_name, **params) |
199 | 290 | return _get_faraday_ready_commands(workspace_name, commands_dictionary) |
200 | 291 | |
201 | 292 | def get_command(workspace_name, command_id): |
293 | """Return the Command of id command_id. None if not found.""" | |
202 | 294 | return force_unique(get_commands(workspace_name, couchid=command_id)) |
203 | 295 | |
204 | 296 | def get_object(workspace_name, object_signature, object_id): |
210 | 302 | 'Interface', 'Service', 'Cred', 'Note' or 'CommandRunInformation'. |
211 | 303 | Will raise an WrongObjectSignature error if this condition is not met. |
212 | 304 | """ |
213 | object_to_func = {_Host.class_signature: get_host, | |
214 | _Vuln.class_signature: get_vuln, | |
215 | _VulnWeb.class_signature: get_web_vuln, | |
216 | _Interface.class_signature: get_interface, | |
217 | _Service.class_signature: get_service, | |
218 | _Credential.class_signature: get_credential, | |
219 | _Note.class_signature: get_note, | |
220 | _Command.class_signature: get_command} | |
305 | object_to_func = {Host.class_signature: get_host, | |
306 | Vuln.class_signature: get_vuln, | |
307 | VulnWeb.class_signature: get_web_vuln, | |
308 | Interface.class_signature: get_interface, | |
309 | Service.class_signature: get_service, | |
310 | Credential.class_signature: get_credential, | |
311 | Note.class_signature: get_note, | |
312 | Command.class_signature: get_command} | |
221 | 313 | try: |
222 | 314 | appropiate_function = object_to_func[object_signature] |
223 | 315 | except KeyError: |
242 | 334 | |
243 | 335 | @_ignore_in_changes |
244 | 336 | def update_host(workspace_name, host): |
337 | """Take a workspace_name and a host object and update it in the sever. | |
338 | ||
339 | Return the server's json response as a dictionary. | |
340 | """ | |
245 | 341 | host_properties = get_host_properties(host) |
246 | 342 | return server.update_host(workspace_name, **host_properties) |
247 | 343 | |
255 | 351 | |
256 | 352 | @_ignore_in_changes |
257 | 353 | def update_interface(workspace_name, interface): |
354 | """Take a workspace_name and an interface object and update it in the sever. | |
355 | ||
356 | Return the server's json response as a dictionary. | |
357 | """ | |
258 | 358 | interface_properties = get_interface_properties(interface) |
259 | 359 | return server.update_interface(workspace_name, **interface_properties) |
260 | 360 | |
268 | 368 | |
269 | 369 | @_ignore_in_changes |
270 | 370 | def update_service(workspace_name, service): |
371 | """Take a workspace_name and an service object and update it in the sever. | |
372 | ||
373 | Return the server's json response as a dictionary. | |
374 | """ | |
271 | 375 | service_properties = get_service_properties(service) |
272 | 376 | return server.update_service(workspace_name, **service_properties) |
273 | 377 | |
282 | 386 | |
283 | 387 | @_ignore_in_changes |
284 | 388 | def update_vuln(workspace_name, vuln): |
389 | """Take a workspace_name and a Vuln object and update it in the sever. | |
390 | ||
391 | Return the server's json response as a dictionary. | |
392 | """ | |
285 | 393 | vuln_properties = get_vuln_properties(vuln) |
286 | 394 | return server.update_vuln(workspace_name, **vuln_properties) |
287 | 395 | |
296 | 404 | |
297 | 405 | @_ignore_in_changes |
298 | 406 | def update_vuln_web(workspace_name, vuln_web): |
407 | """Take a workspace_name and a VulnWeb object and update it in the sever. | |
408 | ||
409 | Return the server's json response as a dictionary. | |
410 | """ | |
299 | 411 | vuln_web_properties = get_vuln_web_properties(vuln_web) |
300 | 412 | return server.update_vuln_web(workspace_name, **vuln_web_properties) |
301 | 413 | |
309 | 421 | |
310 | 422 | @_ignore_in_changes |
311 | 423 | def update_note(workspace_name, note): |
424 | """Take a workspace_name and a Note object and update it in the sever. | |
425 | Return the server's json response as a dictionary. | |
426 | """ | |
312 | 427 | note_properties = get_note_properties(note) |
313 | 428 | return server.update_note(workspace_name, **note_properties) |
314 | 429 | |
322 | 437 | |
323 | 438 | @_ignore_in_changes |
324 | 439 | def update_credential(workspace_name, credential): |
440 | """Take a workspace_name and a Credential object and update it in the sever. | |
441 | Return the server's json response as a dictionary. | |
442 | """ | |
325 | 443 | credential_properties = get_credential_properties(credential) |
326 | 444 | return server.update_credential(workspace_name, **credential_properties) |
327 | 445 | |
332 | 450 | |
333 | 451 | @_ignore_in_changes |
334 | 452 | def update_command(workspace_name, command): |
453 | """Take a workspace_name and a Command object and update it in the sever. | |
454 | Return the server's json response as a dictionary. | |
455 | """ | |
335 | 456 | command_properties = get_command_properties(command) |
336 | 457 | return server.update_command(workspace_name, **command_properties) |
337 | 458 | |
338 | 459 | def create_object(workspace_name, object_signature, obj): |
339 | object_to_func = {_Host.class_signature: create_host, | |
340 | _Vuln.class_signature: create_vuln, | |
341 | _VulnWeb.class_signature: create_vuln_web, | |
342 | _Interface.class_signature: create_interface, | |
343 | _Service.class_signature: create_service, | |
344 | _Credential.class_signature: create_credential, | |
345 | _Note.class_signature: create_note, | |
346 | _Command.class_signature: create_command} | |
460 | """Given a workspace name, an object_signature as string and obj, a Faraday | |
461 | object, save that object on the server. | |
462 | ||
463 | object_signature must match the type of the object. | |
464 | ||
465 | object_signature must be either 'Host', 'Vulnerability', 'VulnerabilityWeb', | |
466 | 'Interface', 'Service', 'Cred', 'Note' or 'CommandRunInformation'. | |
467 | Will raise an WrongObjectSignature error if this condition is not met. | |
468 | """ | |
469 | object_to_func = {Host.class_signature: create_host, | |
470 | Vuln.class_signature: create_vuln, | |
471 | VulnWeb.class_signature: create_vuln_web, | |
472 | Interface.class_signature: create_interface, | |
473 | Service.class_signature: create_service, | |
474 | Credential.class_signature: create_credential, | |
475 | Note.class_signature: create_note, | |
476 | Command.class_signature: create_command} | |
347 | 477 | try: |
348 | 478 | appropiate_function = object_to_func[object_signature] |
349 | 479 | except KeyError: |
352 | 482 | return appropiate_function(workspace_name, obj) |
353 | 483 | |
354 | 484 | def update_object(workspace_name, object_signature, obj): |
355 | object_to_func = {_Host.class_signature: update_host, | |
356 | _Vuln.class_signature: update_vuln, | |
357 | _VulnWeb.class_signature: update_vuln_web, | |
358 | _Interface.class_signature: update_interface, | |
359 | _Service.class_signature: update_service, | |
360 | _Credential.class_signature: update_credential, | |
361 | _Note.class_signature: update_note, | |
362 | _Command.class_signature: update_command} | |
485 | """Given a workspace name, an object_signature as string and obj, a Faraday | |
486 | object, update that object on the server. | |
487 | ||
488 | object_signature must match the type of the object. | |
489 | ||
490 | object_signature must be either 'Host', 'Vulnerability', 'VulnerabilityWeb', | |
491 | 'Interface', 'Service', 'Cred', 'Note' or 'CommandRunInformation'. | |
492 | Will raise an WrongObjectSignature error if this condition is not met. | |
493 | ||
494 | """ | |
495 | object_to_func = {Host.class_signature: update_host, | |
496 | Vuln.class_signature: update_vuln, | |
497 | VulnWeb.class_signature: update_vuln_web, | |
498 | Interface.class_signature: update_interface, | |
499 | Service.class_signature: update_service, | |
500 | Credential.class_signature: update_credential, | |
501 | Note.class_signature: update_note, | |
502 | Command.class_signature: update_command} | |
363 | 503 | try: |
364 | 504 | appropiate_function = object_to_func[object_signature] |
365 | 505 | except KeyError: |
377 | 517 | but there was a problem creating its basic documents, it will delete |
378 | 518 | the document an raise the corresponding error. |
379 | 519 | """ |
380 | ||
381 | def upload_views(): | |
382 | """All wrongdoing is sin, but there is sin that does not lead to death. | |
383 | John 5:17 | |
384 | """ | |
385 | from managers.all import ViewsManager # Blessed are the merciful, for they shall receive mercy. | |
386 | import couchdbkit # for i have sinned and failed short of the glory of god | |
387 | s = couchdbkit.Server(uri=CONF.getCouchURI()) # if we confess our sins | |
388 | db = s[workspace_name] # he is faithful and just to forgive us | |
389 | views_manager = ViewsManager() # and to cleans us | |
390 | views_manager.addViews(db) # from all unrightousness | |
391 | ||
392 | db_creation = server.create_database(workspace_name) | |
393 | if db_creation.get('ok'): | |
394 | try: | |
395 | upload_views() | |
396 | return server.create_workspace(workspace_name, description, | |
397 | start_date, finish_date, customer) | |
398 | except: | |
399 | server.delete_workspace(workspace_name) | |
400 | raise | |
401 | else: | |
402 | return None | |
403 | ||
404 | def get_workspace_summary(workspace_number): | |
405 | return server.get_workspace_summary(workspace_number) | |
520 | return server.create_workspace(workspace_name, description, | |
521 | start_date, finish_date, customer) | |
522 | ||
523 | def get_workspace_summary(workspace_name): | |
524 | """Return the workspace summary as a dictionary | |
525 | """ | |
526 | return server.get_workspace_summary(workspace_name) | |
406 | 527 | |
407 | 528 | def get_workspace_numbers(workspace_name): |
529 | """Return a tuple with the number of hosts, interfaces, services and vulns | |
530 | on the workspace of name workspace_name. | |
531 | """ | |
408 | 532 | return server.get_workspace_numbers(workspace_name) |
409 | 533 | |
410 | 534 | def get_hosts_number(workspace_name, **params): |
535 | """Return the number of hosts found on the workspace of name workspace_name | |
536 | """ | |
411 | 537 | return server.get_hosts_number(workspace_name, **params) |
412 | 538 | |
413 | 539 | def get_services_number(workspace_name, **params): |
540 | """Return the number of services found on the workspace of name workspace_name | |
541 | """ | |
414 | 542 | return server.get_services_number(workspace_name, **params) |
415 | 543 | |
416 | 544 | def get_interfaces_number(workspace_name, **params): |
545 | """Return the number of interfaces found on the workspace of name workspace_name | |
546 | """ | |
417 | 547 | return server.get_interfaces_number(workspace_name, **params) |
418 | 548 | |
419 | 549 | def get_vulns_number(workspace_name, **params): |
550 | """Return the number of vulns found on the workspace of name workspace_name | |
551 | """ | |
420 | 552 | return server.get_vulns_number(workspace_name, **params) |
421 | 553 | |
554 | # NOTE: the delete functions are actually the same. | |
555 | # there's no difference between delete_host and delete_interface | |
556 | # except for their names. | |
557 | # maybe implement some kind of validation in the future? | |
558 | ||
422 | 559 | @_ignore_in_changes |
423 | 560 | def delete_host(workspace_name, host_id): |
561 | """Delete the host of id host_id on workspace workspace_name. | |
562 | Return the json response from the server. | |
563 | """ | |
424 | 564 | return server.delete_host(workspace_name, host_id) |
425 | 565 | |
426 | 566 | @_ignore_in_changes |
427 | 567 | def delete_interface(workspace_name, interface_id): |
568 | """Delete the interface of id interface_id on workspace workspace_name. | |
569 | Return the json response from the server. | |
570 | """ | |
428 | 571 | return server.delete_interface(workspace_name, interface_id) |
429 | 572 | |
430 | 573 | @_ignore_in_changes |
431 | 574 | def delete_service(workspace_name, service_id): |
575 | """Delete the service of id service_id on workspace workspace_name. | |
576 | Return the json response from the server. | |
577 | """ | |
432 | 578 | return server.delete_service(workspace_name, service_id) |
433 | 579 | |
434 | 580 | @_ignore_in_changes |
435 | 581 | def delete_vuln(workspace_name, vuln_id): |
582 | """Delete the vuln of id vuln_id on workspace workspace_name. | |
583 | Return the json response from the server. | |
584 | """ | |
436 | 585 | return server.delete_vuln(workspace_name, vuln_id) |
437 | 586 | |
438 | 587 | @_ignore_in_changes |
439 | 588 | def delete_note(workspace_name, note_id): |
589 | """Delete the note of id note_id on workspace workspace_name. | |
590 | Return the json response from the server. | |
591 | """ | |
440 | 592 | return server.delete_note(workspace_name, note_id) |
441 | 593 | |
442 | 594 | @_ignore_in_changes |
443 | 595 | def delete_credential(workspace_name, credential_id): |
596 | """Delete the credential of id credential_id on workspace workspace_name. | |
597 | Return the json response from the server. | |
598 | """ | |
444 | 599 | return server.delete_credential(workspace_name, credential_id) |
445 | 600 | |
446 | 601 | @_ignore_in_changes |
447 | 602 | def delete_vuln_web(workspace_name, vuln_id): |
603 | """Delete the vulnweb of id vulnweb_id on workspace workspace_name. | |
604 | Return the json response from the server. | |
605 | """ | |
448 | 606 | return server.delete_vuln(workspace_name, vuln_id) |
449 | 607 | |
450 | 608 | @_ignore_in_changes |
451 | 609 | def delete_command(workspace_name, command_id): |
610 | """Delete the command of id command_id on workspace workspace_name. | |
611 | Return the json response from the server. | |
612 | """ | |
452 | 613 | return server.delete_command(workspace_name, command_id) |
453 | 614 | |
454 | 615 | def delete_object(workspace_name, object_signature, obj_id): |
455 | object_to_func = {_Host.class_signature: delete_host, | |
456 | _Vuln.class_signature: delete_vuln, | |
457 | _VulnWeb.class_signature: delete_vuln_web, | |
458 | _Interface.class_signature: delete_interface, | |
459 | _Service.class_signature: delete_service, | |
460 | _Credential.class_signature: delete_credential, | |
461 | _Note.class_signature: delete_note, | |
462 | _Command.class_signature: delete_command} | |
616 | """Given a workspace name, an object_signature as string and an object id. | |
617 | ||
618 | object_signature must be either 'Host', 'Vulnerability', 'VulnerabilityWeb', | |
619 | 'Interface', 'Service', 'Cred', 'Note' or 'CommandRunInformation'. | |
620 | Will raise an WrongObjectSignature error if this condition is not met. | |
621 | """ | |
622 | object_to_func = {Host.class_signature: delete_host, | |
623 | Vuln.class_signature: delete_vuln, | |
624 | VulnWeb.class_signature: delete_vuln_web, | |
625 | Interface.class_signature: delete_interface, | |
626 | Service.class_signature: delete_service, | |
627 | Credential.class_signature: delete_credential, | |
628 | Note.class_signature: delete_note, | |
629 | Command.class_signature: delete_command} | |
463 | 630 | try: |
464 | 631 | appropiate_function = object_to_func[object_signature] |
465 | 632 | except KeyError: |
475 | 642 | return server.delete_workspace(workspace_name) |
476 | 643 | |
477 | 644 | def get_workspaces_names(): |
645 | """Return a list with all the workspace names available.""" | |
478 | 646 | return server.get_workspaces_names()['workspaces'] |
479 | 647 | |
480 | 648 | def is_server_up(): |
649 | """True if server is up, False otherwise.""" | |
481 | 650 | return server.is_server_up() |
482 | 651 | |
483 | 652 | def test_server_url(url_to_test): |
653 | """Return True if url_to_test/_api/info is accessible, False otherwise""" | |
484 | 654 | return server.test_server_url(url_to_test) |
485 | 655 | |
656 | ||
657 | # NOTE: the whole 'which arguments are mandatory and which type should they be" | |
658 | # should probably be reviewed in a nice developmet meeting where | |
659 | # I think there are several # discrepancies between the models here, | |
660 | # those on the server and the parameters the apis specify, | |
661 | # and this leads to potential dissaster. Remember params? | |
486 | 662 | class ModelBase(object): |
663 | """A model for all the Faraday Objects. | |
664 | There should be a one to one correspondance with the jsons the faraday | |
665 | server gives through apis and the classes inheriting from this one. | |
666 | That is: you can view this classes as an python-object representation | |
667 | of the server's json or viceversa. | |
668 | ||
669 | As all the classes take the obj dictionary as an mandatory parameter. | |
670 | The obj dictionary contains the information of the object we need to create | |
671 | an instance of. To specify a default argument for the objects attributes, | |
672 | use the .get method for dictionaries. Try to specifiy a default value that | |
673 | matches the type of the value you expect. | |
674 | ||
675 | All of the values used from the obj dictionary that are set to be | |
676 | non-nullable on the server's models (server/models.py) should be given a | |
677 | sane default argument, EXCEPT for those where we can't provide a one. | |
678 | For example, we can't provide a sane default argument for ID, that should be | |
679 | given to us and indeed raise an exception if it wasn't. We can provide | |
680 | a default argument for 'description': if nothing came, assume empty string, | |
681 | """ | |
487 | 682 | def __init__(self, obj, workspace_name): |
488 | 683 | self._workspace_name = workspace_name |
489 | self._server_id = obj['_id'] | |
490 | self.id = obj['id'] | |
491 | self.name = obj['value']['name'] | |
492 | self.description = obj['value']['description'] | |
493 | self.owned = obj['value']['owned'] | |
494 | self.owner = obj['value']['owner'] | |
495 | self.metadata = obj['value']['metadata'] | |
684 | self._server_id = obj.get('_id', '') | |
685 | self.id = obj.get('id', '') | |
686 | self.name = obj.get('name') | |
687 | self.description = obj.get('description', "") | |
688 | self.owned = obj.get('owned', False) | |
689 | self.owner = obj.get('owner', '') | |
690 | self._metadata = obj.get('metadata', Metadata(self.owner)) | |
496 | 691 | self.updates = [] |
692 | ||
693 | def setID(self, parent_id, *args): | |
694 | if self.id and self.id != -1: | |
695 | return None | |
696 | objid = get_hash(args) | |
697 | if parent_id: | |
698 | objid = '.'.join([parent_id, objid]) | |
699 | self.id = objid | |
497 | 700 | |
498 | 701 | @staticmethod |
499 | 702 | def publicattrsrefs(): |
506 | 709 | |
507 | 710 | def propertyTieBreaker(self, key, prop1, prop2): |
508 | 711 | """ Breakes the conflict between two properties. If either of them |
509 | is a default value returns the true and only. | |
712 | is a default value returns the good one. | |
510 | 713 | If neither returns the default value. |
511 | 714 | If conflicting returns a tuple with the values """ |
512 | 715 | if prop1 in self.defaultValues(): return prop2 |
515 | 718 | else: return (prop1, prop2) |
516 | 719 | |
517 | 720 | def tieBreakable(self, key): |
721 | """ | |
722 | Return true if we can auto resolve this conflict. | |
723 | """ | |
518 | 724 | return False |
519 | 725 | |
520 | 726 | def tieBreak(self, key, prop1, prop2): |
727 | """ | |
728 | Return the 'choosen one' | |
729 | Return a tuple with prop1, prop2 if we cant resolve conflict. | |
730 | """ | |
521 | 731 | return None |
522 | 732 | |
523 | 733 | def addUpdate(self, newModelObject): |
527 | 737 | attribute = self.publicattrsrefs().get(k) |
528 | 738 | prop_update = self.propertyTieBreaker(attribute, *v) |
529 | 739 | |
530 | if not isinstance(prop_update, tuple) or CONF.getMergeStrategy(): | |
740 | if not isinstance(prop_update, tuple) or _get_merge_strategy(): | |
531 | 741 | # if there's a strategy set by the user, apply it |
532 | 742 | if isinstance(prop_update, tuple): |
533 | prop_update = MergeSolver(CONF.getMergeStrategy()) | |
743 | prop_update = MergeSolver(_get_merge_strategy()) | |
534 | 744 | prop_update = prop_update.solve(prop_update[0], prop_update[1]) |
535 | 745 | |
536 | 746 | setattr(self, attribute, prop_update) |
549 | 759 | def needs_merge(self, new_obj): |
550 | 760 | return ModelObjectDiff(self, new_obj).existDiff() |
551 | 761 | |
552 | def updateMetadata(self): | |
553 | self.getMetadata().update(self.owner) | |
554 | ||
555 | 762 | def getOwner(self): return self.owner |
556 | 763 | def isOwned(self): return self.owned |
557 | 764 | def getName(self): return self.name |
558 | def getMetadata(self): return self.metadata | |
765 | def getMetadata(self): return self._metadata | |
559 | 766 | def getDescription(self): return self.description |
560 | 767 | |
561 | 768 | |
562 | class _Host(ModelBase): | |
769 | class Host(ModelBase): | |
563 | 770 | """A simple Host class. Should implement all the methods of the |
564 | 771 | Host object in Model.Host |
565 | 772 | Any method here more than a couple of lines long probably represent |
569 | 776 | |
570 | 777 | def __init__(self, host, workspace_name): |
571 | 778 | ModelBase.__init__(self, host, workspace_name) |
572 | self.default_gateway = host['value']['default_gateway'] | |
573 | self.os = host['value']['os'] | |
574 | self.vuln_amount = int(host['value']['vulns']) | |
779 | self.default_gateway = host.get('default_gateway') | |
780 | self.os = host.get('os') if host.get('os') else 'unknown' | |
781 | self.vuln_amount = int(host.get('vulns', 0)) | |
782 | ||
783 | def setID(self, _): | |
784 | # empty arg so as to share same interface as other classes' generateID | |
785 | ModelBase.setID(self, '', self.name) | |
575 | 786 | |
576 | 787 | @staticmethod |
577 | 788 | def publicattrsrefs(): |
581 | 792 | return publicattrs |
582 | 793 | |
583 | 794 | def updateAttributes(self, name=None, description=None, os=None, owned=None): |
584 | ||
585 | self.updateMetadata() | |
586 | 795 | if name is not None: |
587 | 796 | self.name = name |
588 | 797 | if description is not None: |
608 | 817 | return get_services(self._workspace_name, hostid=self._server_id) |
609 | 818 | |
610 | 819 | |
611 | class _Interface(ModelBase): | |
820 | class Interface(ModelBase): | |
612 | 821 | """A simple Interface class. Should implement all the methods of the |
613 | 822 | Interface object in Model.Host |
614 | 823 | Any method here more than a couple of lines long probably represent |
618 | 827 | |
619 | 828 | def __init__(self, interface, workspace_name): |
620 | 829 | ModelBase.__init__(self, interface, workspace_name) |
621 | self.hostnames = interface['value']['hostnames'] | |
622 | self.ipv4 = interface['value']['ipv4'] | |
623 | self.ipv6 = interface['value']['ipv6'] | |
624 | self.mac = interface['value']['mac'] | |
625 | self.network_segment = interface['value']['network_segment'] | |
626 | self.ports = interface['value']['ports'] | |
830 | self.hostnames = interface.get('hostnames', []) | |
831 | ||
832 | # NOTE. i don't know why this is like this | |
833 | # probably a remnant of the old faraday style classes | |
834 | try: | |
835 | self.ipv4 = interface['ipv4'] | |
836 | self.ipv6 = interface['ipv6'] | |
837 | except KeyError: | |
838 | self.ipv4 = {'address': interface['ipv4_address'], | |
839 | 'gateway': interface['ipv4_gateway'], | |
840 | 'mask': interface['ipv4_mask'], | |
841 | 'DNS': interface['ipv4_dns']} | |
842 | self.ipv6 = {'address': interface['ipv6_address'], | |
843 | 'gateway': interface['ipv6_gateway'], | |
844 | 'prefix': interface['ipv6_prefix'], | |
845 | 'DNS': interface['ipv6_dns']} | |
846 | self.mac = interface.get('mac') | |
847 | self.network_segment = interface.get('network_segment') | |
848 | self.ports = interface.get('ports') | |
627 | 849 | |
628 | 850 | self.amount_ports_opened = 0 |
629 | 851 | self.amount_ports_closed = 0 |
630 | 852 | self.amount_ports_filtered = 0 |
853 | ||
854 | def setID(self, parent_id): | |
855 | try: | |
856 | ipv4_address = self.ipv4_address | |
857 | ipv6_address = self.ipv6_address | |
858 | except AttributeError: | |
859 | ipv4_address = self.ipv4['address'] | |
860 | ipv6_address = self.ipv6['address'] | |
861 | ||
862 | ModelBase.setID(self, parent_id, self.network_segment, ipv4_address, ipv6_address) | |
631 | 863 | |
632 | 864 | @staticmethod |
633 | 865 | def publicattrsrefs(): |
641 | 873 | return publicattrs |
642 | 874 | |
643 | 875 | def tieBreakable(self, property_key): |
876 | """ | |
877 | Return true if we can auto resolve this conflict. | |
878 | """ | |
644 | 879 | if property_key in ["hostnames"]: |
645 | 880 | return True |
646 | 881 | return False |
647 | 882 | |
648 | 883 | def tieBreak(self, key, prop1, prop2): |
884 | """ | |
885 | Return the 'choosen one' | |
886 | Return a tuple with prop1, prop2 if we cant resolve conflict. | |
887 | """ | |
649 | 888 | if key == "hostnames": |
650 | 889 | prop1.extend(prop2) |
890 | # Remove duplicated with set... | |
651 | 891 | return list(set(prop1)) |
652 | return None | |
892 | ||
893 | return (prop1, prop2) | |
653 | 894 | |
654 | 895 | def updateAttributes(self, name=None, description=None, hostnames=None, mac=None, ipv4=None, ipv6=None, |
655 | 896 | network_segment=None, amount_ports_opened=None, amount_ports_closed=None, |
656 | 897 | amount_ports_filtered=None, owned=None): |
657 | 898 | |
658 | self.updateMetadata() | |
659 | 899 | if name is not None: |
660 | 900 | self.name = name |
661 | 901 | if description is not None: |
711 | 951 | return get_all_vulns(self._workspace_name, interfaceid=self._server_id) |
712 | 952 | |
713 | 953 | |
714 | class _Service(ModelBase): | |
954 | class Service(ModelBase): | |
715 | 955 | """A simple Service class. Should implement all the methods of the |
716 | 956 | Service object in Model.Host |
717 | 957 | Any method here more than a couple of lines long probably represent |
721 | 961 | |
722 | 962 | def __init__(self, service, workspace_name): |
723 | 963 | ModelBase.__init__(self, service, workspace_name) |
724 | self.protocol = service['value']['protocol'] | |
725 | self.ports = service['value']['ports'] | |
726 | self.version = service['value']['version'] | |
727 | self.status = service['value']['status'] | |
728 | self.vuln_amount = int(service['vulns']) | |
964 | self.protocol = service['protocol'] | |
965 | self.ports = [int(port) for port in service['ports']] | |
966 | self.version = service['version'] | |
967 | self.status = service['status'] | |
968 | self.vuln_amount = int(service.get('vulns', 0)) | |
969 | ||
970 | def setID(self, parent_id): | |
971 | # TODO: str from list? ERROR MIGRATION NEEDED | |
972 | ports = ':'.join(str(self.ports)) | |
973 | ModelBase.setID(self, parent_id, self.protocol, ports) | |
729 | 974 | |
730 | 975 | @staticmethod |
731 | 976 | def publicattrsrefs(): |
739 | 984 | |
740 | 985 | def updateAttributes(self, name=None, description=None, protocol=None, ports=None, |
741 | 986 | status=None, version=None, owned=None): |
742 | self.updateMetadata() | |
743 | 987 | if name is not None: |
744 | 988 | self.name = name |
745 | 989 | if description is not None: |
765 | 1009 | def getVulns(self): return get_all_vulns(self._workspace_name, serviceid=self._server_id) |
766 | 1010 | |
767 | 1011 | |
768 | class _Vuln(ModelBase): | |
1012 | class Vuln(ModelBase): | |
769 | 1013 | """A simple Vuln class. Should implement all the methods of the |
770 | 1014 | Vuln object in Model.Common |
771 | 1015 | Any method here more than a couple of lines long probably represent |
775 | 1019 | |
776 | 1020 | def __init__(self, vuln, workspace_name): |
777 | 1021 | ModelBase.__init__(self, vuln, workspace_name) |
778 | self.desc = vuln['value']['desc'] | |
779 | self.data = vuln['value']['data'] | |
780 | self.severity = vuln['value']['severity'] | |
781 | self.refs = vuln['value']['refs'] | |
782 | self.confirmed = vuln['value']['confirmed'] | |
783 | self.resolution = vuln['value']['resolution'] | |
1022 | # this next two lines are stupid but so is life so you should get used to it :) | |
1023 | self.description = vuln['desc'] | |
1024 | self.desc = vuln['desc'] | |
1025 | self.data = vuln.get('data') | |
1026 | self.severity = self.standarize(vuln['severity']) | |
1027 | self.refs = vuln.get('refs') or [] | |
1028 | self.confirmed = vuln.get('confirmed', False) | |
1029 | self.resolution = vuln.get('resolution') | |
1030 | self.status = vuln.get('status', "opened") | |
1031 | ||
1032 | def setID(self, parent_id): | |
1033 | ModelBase.setID(self, parent_id, self.name, self.description) | |
784 | 1034 | |
785 | 1035 | @staticmethod |
786 | 1036 | def publicattrsrefs(): |
788 | 1038 | 'Data' : 'data', |
789 | 1039 | 'Severity' : 'severity', |
790 | 1040 | 'Refs' : 'refs', |
791 | 'Resolution': 'resolution' | |
1041 | 'Resolution': 'resolution', | |
1042 | 'Status': 'status' | |
792 | 1043 | }) |
793 | 1044 | return publicattrs |
794 | 1045 | |
795 | 1046 | def tieBreakable(self, key): |
1047 | """ | |
1048 | Return true if we can auto resolve this conflict. | |
1049 | """ | |
796 | 1050 | if key == "confirmed": |
797 | 1051 | return True |
1052 | if key == "status": | |
1053 | return True | |
798 | 1054 | return False |
799 | 1055 | |
800 | 1056 | def tieBreak(self, key, prop1, prop2): |
1057 | """ | |
1058 | Return the 'choosen one' | |
1059 | Return a tuple with prop1, prop2 if we cant resolve conflict. | |
1060 | """ | |
1061 | ||
801 | 1062 | if key == "confirmed": |
802 | 1063 | return True |
1064 | ||
1065 | if key == "status": | |
1066 | if prop1 == "closed" or prop1 == "re-opened": | |
1067 | return "re-opened" | |
1068 | if prop1 == "risk-accepted": | |
1069 | return 'risk-accepted' | |
1070 | ||
803 | 1071 | return (prop1, prop2) |
804 | 1072 | |
805 | 1073 | def standarize(self, severity): |
831 | 1099 | return severity |
832 | 1100 | |
833 | 1101 | def updateAttributes(self, name=None, desc=None, data=None, |
834 | severity=None, resolution=None, refs=None): | |
835 | self.updateMetadata() | |
1102 | severity=None, resolution=None, refs=None, status=None): | |
836 | 1103 | if name is not None: |
837 | 1104 | self.name = name |
838 | 1105 | if desc is not None: |
845 | 1112 | self.severity = self.standarize(severity) |
846 | 1113 | if refs is not None: |
847 | 1114 | self.refs = refs |
1115 | if status is not None: | |
1116 | self.setStatus(status) | |
848 | 1117 | |
849 | 1118 | def getID(self): return self.id |
850 | 1119 | def getDesc(self): return self.desc |
853 | 1122 | def getRefs(self): return self.refs |
854 | 1123 | def getConfirmed(self): return self.confirmed |
855 | 1124 | def getResolution(self): return self.resolution |
856 | ||
857 | ||
858 | class _VulnWeb(_Vuln): | |
1125 | def getStatus(self): return self.status | |
1126 | ||
1127 | def setStatus(self, status): | |
1128 | self.status = status | |
1129 | ||
1130 | ||
1131 | class VulnWeb(Vuln): | |
859 | 1132 | """A simple VulnWeb class. Should implement all the methods of the |
860 | 1133 | VulnWeb object in Model.Common |
861 | 1134 | Any method here more than a couple of lines long probably represent |
864 | 1137 | class_signature = 'VulnerabilityWeb' |
865 | 1138 | |
866 | 1139 | def __init__(self, vuln_web, workspace_name): |
867 | _Vuln.__init__(self, vuln_web, workspace_name) | |
868 | self.path = vuln_web['value']['path'] | |
869 | self.website = vuln_web['value']['website'] | |
870 | self.request = vuln_web['value']['request'] | |
871 | self.response = vuln_web['value']['response'] | |
872 | self.method = vuln_web['value']['method'] | |
873 | self.pname = vuln_web['value']['pname'] | |
874 | self.params = vuln_web['value']['params'] | |
875 | self.query = vuln_web['value']['query'] | |
876 | self.resolution = vuln_web['value']['resolution'] | |
877 | self.attachments = vuln_web['value']['_attachments'] | |
878 | self.hostnames = vuln_web['value']['hostnames'] | |
879 | self.impact = vuln_web['value']['impact'] | |
880 | self.service = vuln_web['value']['service'] | |
881 | self.status = vuln_web['value']['status'] | |
882 | self.tags = vuln_web['value']['tags'] | |
883 | self.target = vuln_web['value']['target'] | |
884 | self.parent = vuln_web['value']['parent'] | |
1140 | Vuln.__init__(self, vuln_web, workspace_name) | |
1141 | self.path = vuln_web.get('path') | |
1142 | self.website = vuln_web.get('website') | |
1143 | self.request = vuln_web.get('request') | |
1144 | self.response = vuln_web.get('response') | |
1145 | self.method = vuln_web.get('method') | |
1146 | self.pname = vuln_web.get('pname') | |
1147 | self.params = vuln_web.get('params') | |
1148 | self.query = vuln_web.get('query') | |
1149 | self.resolution = vuln_web.get('resolution') | |
1150 | self.attachments = vuln_web.get('_attachments') | |
1151 | self.hostnames = vuln_web.get('hostnames') | |
1152 | self.impact = vuln_web.get('impact') | |
1153 | self.service = vuln_web.get('service') | |
1154 | self.tags = vuln_web.get('tags') | |
1155 | self.target = vuln_web.get('target') | |
1156 | self.parent = vuln_web.get('parent') | |
1157 | ||
1158 | def setID(self, parent_id): | |
1159 | ModelBase.setID(self, parent_id, self.name, self.website, self.path, self.description) | |
885 | 1160 | |
886 | 1161 | @staticmethod |
887 | 1162 | def publicattrsrefs(): |
896 | 1171 | 'Method' : 'method', |
897 | 1172 | 'Pname' : 'pname', |
898 | 1173 | 'Params' : 'params', |
899 | 'Query' : 'query'}) | |
1174 | 'Query' : 'query', | |
1175 | 'Status': 'status'}) | |
900 | 1176 | return publicattrs |
901 | 1177 | |
902 | 1178 | def updateAttributes(self, name=None, desc=None, data=None, website=None, path=None, refs=None, |
903 | 1179 | severity=None, resolution=None, request=None,response=None, method=None, |
904 | pname=None, params=None, query=None, category=None): | |
905 | ||
906 | super(_VulnWeb, self).updateAttributes(name, desc, data, severity, resolution, refs) | |
907 | self.updateMetadata() | |
1180 | pname=None, params=None, query=None, category=None, status=None): | |
1181 | ||
1182 | super(self.__class__, self).updateAttributes(name, desc, data, severity, resolution, refs, status) | |
908 | 1183 | |
909 | 1184 | if website is not None: |
910 | 1185 | self.website = website |
945 | 1220 | def getTarget(self): return self.target |
946 | 1221 | def getParent(self): return self.parent |
947 | 1222 | |
948 | class _Note(ModelBase): | |
1223 | def tieBreakable(self, key): | |
1224 | """ | |
1225 | Return true if we can auto resolve this conflict. | |
1226 | """ | |
1227 | if key == "response": | |
1228 | return True | |
1229 | if key == "confirmed": | |
1230 | return True | |
1231 | if key == "status": | |
1232 | return True | |
1233 | return False | |
1234 | ||
1235 | def tieBreak(self, key, prop1, prop2): | |
1236 | """ | |
1237 | Return the 'choosen one' | |
1238 | Return a tuple with prop1, prop2 if we cant resolve conflict. | |
1239 | """ | |
1240 | ||
1241 | if key == "response": | |
1242 | return self._resolve_response(prop1, prop2) | |
1243 | ||
1244 | if key == "status": | |
1245 | if prop1 == "closed" or prop1 == "re-opened": | |
1246 | return "re-opened" | |
1247 | if prop1 == "risk-accepted": | |
1248 | return 'risk-accepted' | |
1249 | ||
1250 | if key == "confirmed": | |
1251 | return True | |
1252 | ||
1253 | return (prop1, prop2) | |
1254 | ||
1255 | def _resolve_response(self ,res1, res2): | |
1256 | ||
1257 | differ = Differ() | |
1258 | result = list(differ.compare(res1.splitlines(), res2.splitlines())) | |
1259 | ||
1260 | counterNegative = 0 | |
1261 | counterPositive = 0 | |
1262 | ||
1263 | for i in result: | |
1264 | if i.startswith('-') and i.find('date:') != -1: | |
1265 | counterNegative += 1 | |
1266 | if i.startswith('+') and i.find('date:') != -1: | |
1267 | counterPositive += 1 | |
1268 | ||
1269 | if counterNegative == 1 and counterPositive == 1 and counterNegative == counterPositive: | |
1270 | return res2 | |
1271 | else: | |
1272 | return None | |
1273 | ||
1274 | class Note(ModelBase): | |
949 | 1275 | class_signature = 'Note' |
950 | 1276 | |
951 | 1277 | def __init__(self, note, workspace_name): |
952 | 1278 | ModelBase.__init__(self, note, workspace_name) |
953 | self.text = note['value']['text'] | |
1279 | self.text = note['text'] | |
1280 | ||
1281 | def setID(self, parent_id): | |
1282 | ModelBase.setID(self, parent_id, self.name, self.text) | |
954 | 1283 | |
955 | 1284 | def updateAttributes(self, name=None, text=None): |
956 | self.updateMetadata() | |
957 | 1285 | if name is not None: |
958 | 1286 | self.name = name |
959 | 1287 | if text is not None: |
963 | 1291 | def getDescription(self): return self.description |
964 | 1292 | def getText(self): return self.text |
965 | 1293 | |
966 | class _Credential(ModelBase): | |
1294 | class Credential(ModelBase): | |
967 | 1295 | class_signature = "Cred" |
968 | 1296 | |
969 | 1297 | def __init__(self, credential, workspace_name): |
970 | 1298 | ModelBase.__init__(self, credential, workspace_name) |
971 | self.username = credential['value']['username'] | |
972 | self.password = credential['value']['password'] | |
1299 | try: | |
1300 | self.username = credential['username'] | |
1301 | except KeyError: | |
1302 | self.username = credential['name'] | |
1303 | ||
1304 | self.password = credential['password'] | |
1305 | ||
1306 | def setID(self, parent_id): | |
1307 | ModelBase.setID(self, parent_id, self.username, self.password) | |
973 | 1308 | |
974 | 1309 | def updateAttributes(self, username=None, password=None): |
975 | self.updateMetadata() | |
976 | 1310 | if username is not None: |
977 | 1311 | self.username =username |
978 | 1312 | if password is not None: |
982 | 1316 | def getUsername(self): return self.username |
983 | 1317 | def getPassword(self): return self.password |
984 | 1318 | |
985 | class _Command: | |
1319 | class Command: | |
986 | 1320 | class_signature = 'CommandRunInformation' |
987 | 1321 | def __init__(self, command, workspace_name): |
988 | 1322 | self._workspace_name = workspace_name |
989 | 1323 | self.id = command['id'] |
990 | self.command = command['value']['command'] | |
991 | self.duration = command['value']['duration'] | |
992 | self.hostname = command['value']['hostname'] | |
993 | self.ip = command['value']['ip'] | |
994 | self.itime = command['value']['itime'] | |
995 | self.params = command['value']['params'] | |
996 | self.user = command['value']['user'] | |
997 | self.workspace = command['value']['workspace'] | |
1324 | self.command = command['command'] | |
1325 | self.duration = command['duration'] | |
1326 | self.hostname = command['hostname'] | |
1327 | self.ip = command['ip'] | |
1328 | self.itime = command['itime'] | |
1329 | self.params = command['params'] | |
1330 | self.user = command['user'] | |
1331 | self.workspace = command['workspace'] | |
998 | 1332 | |
999 | 1333 | def getID(self): return self.id |
1000 | 1334 | def getCommand(self): return self.command |
1023 | 1357 | def getCustomer(self): return self.customer |
1024 | 1358 | def getStartDate(self): return self.start_date |
1025 | 1359 | def getFinishDate(self): return self.finish_date |
1360 | ||
1361 | ||
1362 | class MetadataUpdateActions(object): | |
1363 | """Constants for the actions made on the update""" | |
1364 | UNDEFINED = -1 | |
1365 | CREATE = 0 | |
1366 | UPDATE = 1 | |
1367 | ||
1368 | ||
1369 | class Metadata(object): | |
1370 | """To save information about the modification of ModelObjects. | |
1371 | All members declared public as this is only a wrapper""" | |
1372 | ||
1373 | class_signature = "Metadata" | |
1374 | ||
1375 | def __init__(self, user): | |
1376 | self.creator = user | |
1377 | self.owner = user | |
1378 | self.create_time = time() | |
1379 | self.update_time = time() | |
1380 | self.update_user = user | |
1381 | self.update_action = MetadataUpdateActions.CREATE | |
1382 | self.update_controller_action = self.__getUpdateAction() | |
1383 | self.command_id = '' | |
1384 | ||
1385 | def toDict(self): | |
1386 | return self.__dict__ | |
1387 | ||
1388 | def fromDict(self, dictt): | |
1389 | for k, v in dictt.items(): | |
1390 | setattr(self, k, v) | |
1391 | return self | |
1392 | ||
1393 | def update(self, user, action = MetadataUpdateActions.UPDATE): | |
1394 | """Update the local metadata giving a user and an action. | |
1395 | Update time gets modified to the current system time""" | |
1396 | self.update_user = user | |
1397 | self.update_time = time() | |
1398 | self.update_action = action | |
1399 | ||
1400 | self.update_controller_action = self.__getUpdateAction() | |
1401 | ||
1402 | def __getUpdateAction(self): | |
1403 | """This private method grabs the stackframes in look for the controller | |
1404 | call that generated the update""" | |
1405 | ||
1406 | l_strace = traceback.extract_stack(limit = 10) | |
1407 | controller_funcallnames = [ x[2] for x in l_strace if "controller" in x[0] ] | |
1408 | ||
1409 | if controller_funcallnames: | |
1410 | return "ModelControler." + " ModelControler.".join(controller_funcallnames) | |
1411 | return "No model controller call" | |
1026 | 1412 | |
1027 | 1413 | # NOTE: uncomment for test |
1028 | 1414 | # class SillyHost(): |
8 | 8 | import requests |
9 | 9 | import json |
10 | 10 | from persistence.server.utils import force_unique |
11 | from persistence.server.utils import WrongObjectSignature | |
11 | from persistence.server.server_io_exceptions import (WrongObjectSignature, | |
12 | CantCommunicateWithServerError, | |
13 | ConflictInDatabase, | |
14 | ResourceDoesNotExist, | |
15 | Unauthorized, | |
16 | MoreThanOneObjectFoundByID) | |
17 | ||
12 | 18 | from persistence.server.changes_stream import CouchChangesStream |
13 | 19 | |
14 | 20 | # NOTE: Change is you want to use this module by itself. |
15 | 21 | # If FARADAY_UP is False, SERVER_URL must be a valid faraday server url |
16 | 22 | FARADAY_UP = True |
17 | SERVER_URL = "http://127.0.1:5984" | |
18 | ||
23 | SERVER_URL = "http://127.0.0.1:5984" | |
24 | ||
25 | def _conf(): | |
26 | from config.configuration import getInstanceConfiguration | |
27 | CONF = getInstanceConfiguration() | |
28 | return CONF | |
19 | 29 | |
20 | 30 | def _get_base_server_url(): |
21 | 31 | if FARADAY_UP: |
22 | from config.configuration import getInstanceConfiguration | |
23 | CONF = getInstanceConfiguration() | |
24 | server_url = CONF.getCouchURI() | |
32 | server_url = _conf().getCouchURI() | |
25 | 33 | else: |
26 | 34 | server_url = SERVER_URL |
27 | 35 | return server_url |
30 | 38 | def _create_server_api_url(): |
31 | 39 | """Return the server's api url.""" |
32 | 40 | return "{0}/_api".format(_get_base_server_url()) |
33 | ||
34 | 41 | |
35 | 42 | def _create_server_get_url(workspace_name, object_name=None): |
36 | 43 | """Creates a url to get from the server. Takes the workspace name |
57 | 64 | def _create_server_delete_url(workspace_name, object_id): |
58 | 65 | return _create_server_post_url(workspace_name, object_id) |
59 | 66 | |
60 | ||
61 | 67 | # XXX: COUCH IT! |
62 | 68 | def _create_couch_get_url(workspace_name, object_id): |
63 | 69 | server_url = _get_base_server_url() |
70 | 76 | |
71 | 77 | |
72 | 78 | # XXX: COUCH IT! |
73 | def _create_server_db_url(workspace_name): | |
79 | def _create_couch_db_url(workspace_name): | |
74 | 80 | server_base_url = _get_base_server_url() |
75 | 81 | db_url = '{0}/{1}'.format(server_base_url, workspace_name) |
76 | 82 | return db_url |
77 | 83 | |
84 | def _create_server_db_url(workspace_name): | |
85 | server_api_url = _create_server_api_url() | |
86 | db_url = '{0}/ws/{1}'.format(server_api_url, workspace_name) | |
87 | return db_url | |
78 | 88 | |
79 | 89 | def _unsafe_io_with_server(server_io_function, server_expected_response, |
80 | 90 | server_url, **payload): |
94 | 104 | if answer.status_code == 403 or answer.status_code == 401: |
95 | 105 | raise Unauthorized(answer) |
96 | 106 | if answer.status_code != server_expected_response: |
97 | raise requests.exceptions.ConnectionError() | |
98 | except requests.exceptions.ConnectionError: | |
107 | raise requests.exceptions.RequestException(response=answer) | |
108 | except requests.exceptions.RequestException: | |
99 | 109 | raise CantCommunicateWithServerError(server_io_function, server_url, payload) |
100 | 110 | return answer |
101 | 111 | |
150 | 160 | params = {} |
151 | 161 | if not database: |
152 | 162 | last_rev = _get(delete_url)['_rev'] |
153 | params = {'_rev': last_rev} | |
163 | params = {'rev': last_rev} | |
154 | 164 | return _parse_json(_unsafe_io_with_server(requests.delete, |
155 | 165 | 200, |
156 | 166 | delete_url, |
226 | 236 | post_url = _create_server_post_url(workspace_name, faraday_object_id) |
227 | 237 | return _put(post_url, update=True, expected_response=200, **params) |
228 | 238 | |
239 | def _save_db_to_server(db_name, **params): | |
240 | post_url = _create_server_db_url(db_name) | |
241 | return _put(post_url, expected_response=200, **params) | |
242 | ||
229 | 243 | # XXX: SEMI COUCH IT! |
230 | 244 | def _delete_from_couch(workspace_name, faraday_object_id): |
231 | 245 | delete_url = _create_server_delete_url(workspace_name, faraday_object_id) |
234 | 248 | # XXX: COUCH IT! |
235 | 249 | def _couch_changes(workspace_name, **params): |
236 | 250 | return CouchChangesStream(workspace_name, |
237 | _create_server_db_url(workspace_name), | |
251 | _create_couch_db_url(workspace_name), | |
238 | 252 | **params) |
239 | 253 | |
240 | 254 | |
370 | 384 | return appropiate_function(workspace_name, **params) |
371 | 385 | |
372 | 386 | # cha cha cha chaaaanges! |
373 | def get_changes_stream(workspace_name, since=0, heartbeat='1000', **params): | |
387 | def get_changes_stream(workspace_name, since=0, heartbeat='1000', **extra_params): | |
374 | 388 | return _couch_changes(workspace_name, since=since, feed='continuous', |
375 | heartbeat=heartbeat, **params) | |
389 | heartbeat=heartbeat, **extra_params) | |
376 | 390 | |
377 | 391 | def get_workspaces_names(): |
378 | 392 | """Return a json containing the list with the workspaces names.""" |
587 | 601 | description=description, |
588 | 602 | type="Host") |
589 | 603 | |
604 | ||
605 | # TODO: FIX. If you actually pass ipv4 or ipv6 as None, which are the defaults | |
606 | # values here, the server will complain. Review if this should be fixed on | |
607 | # the client or on the server. | |
590 | 608 | def create_interface(workspace_name, id, name, description, mac, |
591 | 609 | owned=False, owner="", hostnames=None, network_segment=None, |
592 | 610 | ipv4=None, ipv6=None, metadata=None): |
664 | 682 | |
665 | 683 | def create_vuln(workspace_name, id, name, description, owned=None, owner="", |
666 | 684 | confirmed=False, data="", refs=None, severity="info", resolution="", |
667 | desc="", metadata=None): | |
685 | desc="", metadata=None, status=None): | |
668 | 686 | """Save a vulnerability to the server. Return the json with the |
669 | 687 | server's response. |
670 | 688 | """ |
681 | 699 | resolution=resolution, |
682 | 700 | desc=desc, |
683 | 701 | type="Vulnerability", |
702 | status=status, | |
684 | 703 | metadata=metadata) |
685 | 704 | |
686 | 705 | def update_vuln(workspace_name, id, name, description, owned=None, owner="", |
687 | 706 | confirmed=False, data="", refs=None, severity="info", resolution="", |
688 | desc="", metadata=None): | |
707 | desc="", metadata=None, status=None): | |
689 | 708 | """Update a vulnerability in the server. Return the json with the |
690 | 709 | server's response. |
691 | 710 | """ |
702 | 721 | resolution=resolution, |
703 | 722 | desc=desc, |
704 | 723 | type="Vulnerability", |
724 | status=status, | |
705 | 725 | metadata=metadata) |
706 | 726 | |
707 | 727 | def create_vuln_web(workspace_name, id, name, description, owned=None, owner="", |
708 | 728 | confirmed=False, data="", refs=None, severity="info", resolution="", |
709 | 729 | desc="", metadata=None, method=None, params="", path=None, pname=None, |
710 | query=None, request=None, response=None, category="", website=None): | |
730 | query=None, request=None, response=None, category="", website=None, | |
731 | status=None): | |
711 | 732 | """Save a web vulnerability to the server. Return the json with the |
712 | 733 | server's response. |
713 | 734 | """ |
733 | 754 | response=response, |
734 | 755 | website=website, |
735 | 756 | category=category, |
757 | status=status, | |
736 | 758 | type='VulnerabilityWeb') |
737 | 759 | |
738 | 760 | def update_vuln_web(workspace_name, id, name, description, owned=None, owner="", |
739 | 761 | confirmed=False, data="", refs=None, severity="info", resolution="", |
740 | 762 | desc="", metadata=None, method=None, params="", path=None, pname=None, |
741 | query=None, request=None, response=None, category="", website=None): | |
763 | query=None, request=None, response=None, category="", website=None, | |
764 | status=None): | |
742 | 765 | """Update a web vulnerability in the server. Return the json with the |
743 | 766 | server's response. |
744 | 767 | """ |
764 | 787 | response=response, |
765 | 788 | website=website, |
766 | 789 | category=category, |
790 | status=status, | |
767 | 791 | type='VulnerabilityWeb') |
768 | 792 | |
769 | 793 | def create_note(workspace_name, id, name, text, owned=None, owner="", |
864 | 888 | type="CommandRunInformation") |
865 | 889 | |
866 | 890 | |
867 | # COUCH IT! | |
868 | def create_database(workspace_name): | |
869 | """Create a database in the server. Return the json with the | |
870 | server's response. Can throw an Unauthorized exception | |
871 | """ | |
872 | ||
873 | # NOTE: this function is still talking to couch directly, | |
874 | # that's why it is unable to use the _put function: | |
875 | # it returns s 201 response code if everything went ok | |
876 | db_url = _create_server_db_url(workspace_name) | |
877 | return _parse_json(_unsafe_io_with_server(requests.put, | |
878 | 201, | |
879 | db_url)) | |
880 | ||
881 | 891 | def create_workspace(workspace_name, description, start_date, finish_date, |
882 | 892 | customer=None): |
883 | 893 | """Create a workspace in the server. Return the json with the |
884 | 894 | server's response. |
885 | 895 | """ |
886 | return _save_to_couch(workspace_name, | |
887 | workspace_name, | |
888 | name=workspace_name, | |
889 | description=description, | |
890 | customer=customer, | |
891 | sdate=start_date, | |
892 | fdate=finish_date, | |
893 | type="Workspace") | |
896 | return _save_db_to_server(workspace_name, | |
897 | name=workspace_name, | |
898 | description=description, | |
899 | customer=customer, | |
900 | sdate=start_date, | |
901 | fdate=finish_date, | |
902 | type="Workspace") | |
894 | 903 | |
895 | 904 | def delete_host(workspace_name, host_id): |
896 | 905 | """Delete host of id host_id from the database.""" |
926 | 935 | return _delete(db_url, database=True) |
927 | 936 | |
928 | 937 | def is_server_up(): |
938 | """Return True if we can stablish a connection with the server, | |
939 | False otherwise. | |
940 | """ | |
929 | 941 | try: |
930 | 942 | _get("{0}/info".format(_create_server_api_url())) |
931 | 943 | is_server_up = True |
934 | 946 | return is_server_up |
935 | 947 | |
936 | 948 | def test_server_url(url_to_test): |
949 | """Return True if the url_to_test is indeed a valid Faraday Server URL. | |
950 | False otherwise. | |
951 | """ | |
937 | 952 | try: |
938 | 953 | _get("{0}/_api/info".format(url_to_test)) |
939 | 954 | test_okey = True |
940 | 955 | except: |
941 | 956 | test_okey = False |
942 | 957 | return test_okey |
943 | ||
944 | class ServerRequestException(Exception): | |
945 | def __init__(self): | |
946 | pass | |
947 | ||
948 | class CantCommunicateWithServerError(ServerRequestException): | |
949 | def __init__(self, function, server_url, payload): | |
950 | self.function = function | |
951 | self.server_url = server_url | |
952 | self.payload = payload | |
953 | ||
954 | def __str__(self): | |
955 | return ("Couldn't get a valid response from the server when requesting " | |
956 | "to URL {0} with parameters {1} and function {2}.".format(self.server_url, | |
957 | self.payload, | |
958 | self.function)) | |
959 | ||
960 | ||
961 | class ConflictInDatabase(ServerRequestException): | |
962 | def __init__(self, answer): | |
963 | self.answer = answer | |
964 | ||
965 | def __str__(self): | |
966 | return ("There was a conflict trying to save your document. " | |
967 | "Most probably the document already existed and you " | |
968 | "did not provided a _rev argument to your payload. " | |
969 | "The answer from the server was {0}".format(self.answer)) | |
970 | ||
971 | class ResourceDoesNotExist(ServerRequestException): | |
972 | def __init__(self, url): | |
973 | self.url = url | |
974 | ||
975 | def __str__(self): | |
976 | return ("Can't find anything on URL {0}".format(self.url)) | |
977 | ||
978 | class Unauthorized(ServerRequestException): | |
979 | def __init__(self, answer): | |
980 | self.answer = answer | |
981 | ||
982 | def __str__(self): | |
983 | return ("You're not authorized to make this request. " | |
984 | "The answer from the server was {0}".format(self.answer)) |
0 | #!/usr/bin/python2.7 | |
1 | # -*- coding: utf-8 -*- | |
2 | ''' | |
3 | Faraday Penetration Test IDE | |
4 | Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) | |
5 | See the file 'doc/LICENSE' for the license information | |
6 | ||
7 | ''' | |
8 | class ServerRequestException(Exception): | |
9 | def __init__(self): | |
10 | pass | |
11 | ||
12 | class MoreThanOneObjectFoundByID(ServerRequestException): | |
13 | def __init__(self, faulty_list): | |
14 | self.faulty_list = faulty_list | |
15 | ||
16 | def __str__(self): | |
17 | return ("More than one object has been found." | |
18 | "These are all the objects found with the same ID: {0}" | |
19 | .format(self.faulty_list)) | |
20 | ||
21 | ||
22 | class CantCommunicateWithServerError(ServerRequestException): | |
23 | def __init__(self, function, server_url, payload): | |
24 | self.function = function | |
25 | self.server_url = server_url | |
26 | self.payload = payload | |
27 | ||
28 | def __str__(self): | |
29 | return ("Couldn't get a valid response from the server when requesting " | |
30 | "to URL {0} and function {1}.".format(self.server_url, | |
31 | self.function)) | |
32 | ||
33 | class ConflictInDatabase(ServerRequestException): | |
34 | def __init__(self, answer): | |
35 | self.answer = answer | |
36 | ||
37 | def __str__(self): | |
38 | return ("There was a conflict trying to save your document. " | |
39 | "Most probably the document already existed and you " | |
40 | "did not provided a _rev argument to your payload. " | |
41 | "The answer from the server was {0}".format(self.answer)) | |
42 | ||
43 | class ResourceDoesNotExist(ServerRequestException): | |
44 | def __init__(self, url): | |
45 | self.url = url | |
46 | ||
47 | def __str__(self): | |
48 | return ("Can't find anything on URL {0}".format(self.url)) | |
49 | ||
50 | class Unauthorized(ServerRequestException): | |
51 | def __init__(self, answer): | |
52 | self.answer = answer | |
53 | ||
54 | def __str__(self): | |
55 | return ("You're not authorized to make this request. " | |
56 | "The answer from the server was {0}".format(self.answer)) | |
57 | ||
58 | class CouchDBException(Exception): | |
59 | def __init__(self): | |
60 | pass | |
61 | ||
62 | class ChangesStreamStoppedAbruptly(CouchDBException): | |
63 | def __str__(self): | |
64 | return ("The changes stream from CouchDB ended abruptly for some " | |
65 | "unkown reason.") | |
66 | ||
67 | ||
68 | class WrongObjectSignature(Exception): | |
69 | def __init__(self, param): | |
70 | self.param = param | |
71 | ||
72 | def __str__(self): | |
73 | return ("object_signature must be either 'host', 'vuln', 'vuln_web'," | |
74 | "'interface' 'service', 'credential' or 'note' and it was {0}" | |
75 | .format(self.param)) | |
76 | ||
77 | class CantAccessConfigurationWithoutTheClient(Exception): | |
78 | def __init__(self): | |
79 | pass | |
80 | ||
81 | def __str__(self): | |
82 | return ("You're tring to access to the Faraday Configuration without " | |
83 | "having the client up. This is not possible at the moment.") |
5 | 5 | See the file 'doc/LICENSE' for the license information |
6 | 6 | |
7 | 7 | ''' |
8 | ||
9 | class MoreThanOneObjectFoundByID(Exception): | |
10 | def __init__(self, faulty_list): | |
11 | self.faulty_list = faulty_list | |
12 | ||
13 | def __str__(self): | |
14 | return ("More than one object has been found." | |
15 | "These are all the objects found with the same ID: {0}" | |
16 | .format(self.faulty_list)) | |
17 | ||
18 | class WrongObjectSignature(Exception): | |
19 | def __init__(self, param): | |
20 | self.param = param | |
21 | ||
22 | def __str__(self): | |
23 | return ("object_signature must be either 'host', 'vuln', 'vuln_web'," | |
24 | "'interface' 'service', 'credential' or 'note' and it was {0}" | |
25 | .format(self.param)) | |
8 | import hashlib | |
9 | from persistence.server.server_io_exceptions import MoreThanOneObjectFoundByID | |
26 | 10 | |
27 | 11 | def force_unique(lst): |
28 | 12 | """Takes a list and return its only member if the list len is 1, |
36 | 20 | else: |
37 | 21 | raise MoreThanOneObjectFoundByID(lst) |
38 | 22 | |
23 | def get_hash(parts): | |
24 | return hashlib.sha1("._.".join(parts)).hexdigest() | |
25 | ||
39 | 26 | def get_object_properties(obj): |
27 | # this sometimes is the metadata object and sometimes its a dictionary | |
28 | # a better fix awaits in a brighter future | |
29 | metadata = obj.getMetadata() | |
30 | if not isinstance(obj.getMetadata(), dict): | |
31 | metadata = metadata.toDict() | |
32 | ||
40 | 33 | return {'id': obj.getID(), |
41 | 34 | 'name': obj.getName(), |
42 | 35 | 'description': obj.getDescription(), |
43 | 'metadata': obj.getMetadata(), | |
36 | 'metadata': metadata, | |
44 | 37 | 'owned': obj.isOwned(), |
45 | 38 | 'owner': obj.getOwner() |
46 | 39 | } |
76 | 69 | 'refs': vuln.getRefs(), |
77 | 70 | 'severity': vuln.getSeverity(), |
78 | 71 | 'resolution': vuln.getResolution(), |
79 | 'desc': vuln.getDesc()} | |
72 | 'desc': vuln.getDesc(), | |
73 | 'status': vuln.getStatus()} | |
80 | 74 | vuln_dict.update(get_object_properties(vuln)) |
81 | 75 | return vuln_dict |
82 | 76 | |
89 | 83 | 'path': vuln_web.getPath(), |
90 | 84 | 'pname': vuln_web.getPname(), |
91 | 85 | 'query': vuln_web.getQuery(), |
86 | 'status': vuln_web.getStatus() | |
92 | 87 | } |
93 | 88 | vuln_web_dict.update(get_object_properties(vuln_web)) |
94 | 89 | vuln_web_dict.update(get_vuln_properties(vuln_web)) |
91 | 91 | """ |
92 | 92 | return self._plugins |
93 | 93 | |
94 | def processOutput(self, plugin, output, isReport=False): | |
94 | def processOutput(self, plugin, output, command_id, isReport=False): | |
95 | 95 | output_queue = multiprocessing.JoinableQueue() |
96 | 96 | new_elem_queue = multiprocessing.Queue() |
97 | 97 | |
117 | 117 | break |
118 | 118 | action = current_action[0] |
119 | 119 | parameters = current_action[1:] |
120 | ||
121 | parameters[-1]._metadata.command_id = command_id | |
122 | ||
120 | 123 | getLogger(self).debug( |
121 | 124 | "Core: Processing a new '%s', parameters (%s)\n" % |
122 | 125 | (action, str(parameters))) |
154 | 157 | def _setupActionDispatcher(self): |
155 | 158 | self._actionDispatcher = { |
156 | 159 | modelactions.ADDHOST: model.api.addHost, |
157 | modelactions.CADDHOST: model.api.createAndAddHost, | |
158 | 160 | modelactions.ADDINTERFACE: model.api.addInterface, |
159 | modelactions.CADDINTERFACE: model.api.createAndAddInterface, | |
160 | 161 | modelactions.ADDSERVICEINT: model.api.addServiceToInterface, |
161 | modelactions.ADDSERVICEAPP: model.api.addServiceToApplication, | |
162 | modelactions.CADDSERVICEINT: model.api.createAndAddServiceToInterface, | |
163 | modelactions.CADDSERVICEAPP: model.api.createAndAddServiceToApplication, | |
164 | modelactions.ADDAPPLICATION: model.api.addApplication, | |
165 | modelactions.CADDAPPLICATION: model.api.createAndAddApplication, | |
166 | 162 | modelactions.DELSERVICEINT: model.api.delServiceFromInterface, |
167 | 163 | #Vulnerability |
168 | 164 | modelactions.ADDVULNINT: model.api.addVulnToInterface, |
169 | modelactions.CADDVULNINT: model.api.createAndAddVulnToInterface, | |
170 | modelactions.ADDVULNAPP: model.api.addVulnToApplication, | |
171 | modelactions.CADDVULNAPP: model.api.createAndAddVulnToApplication, | |
172 | 165 | modelactions.ADDVULNHOST: model.api.addVulnToHost, |
173 | modelactions.CADDVULNHOST: model.api.createAndAddVulnToHost, | |
174 | 166 | modelactions.ADDVULNSRV: model.api.addVulnToService, |
175 | modelactions.CADDVULNSRV: model.api.createAndAddVulnToService, | |
176 | 167 | #VulnWeb |
177 | 168 | modelactions.ADDVULNWEBSRV: model.api.addVulnWebToService, |
178 | modelactions.CADDVULNWEBSRV: model.api.createAndAddVulnWebToService, | |
179 | 169 | #Note |
180 | 170 | modelactions.ADDNOTEINT: model.api.addNoteToInterface, |
181 | modelactions.CADDNOTEINT: model.api.createAndAddNoteToInterface, | |
182 | modelactions.ADDNOTEAPP: model.api.addNoteToApplication, | |
183 | modelactions.CADDNOTEAPP: model.api.createAndAddNoteToApplication, | |
184 | 171 | modelactions.ADDNOTEHOST: model.api.addNoteToHost, |
185 | modelactions.CADDNOTEHOST: model.api.createAndAddNoteToHost, | |
186 | 172 | modelactions.ADDNOTESRV: model.api.addNoteToService, |
187 | modelactions.CADDNOTESRV: model.api.createAndAddNoteToService, | |
188 | 173 | modelactions.ADDNOTENOTE: model.api.addNoteToNote, |
189 | modelactions.CADDNOTENOTE: model.api.createAndAddNoteToNote, | |
190 | 174 | #Creds |
191 | modelactions.CADDCREDSRV: model.api.createAndAddCredToService, | |
192 | 175 | modelactions.ADDCREDSRV: model.api.addCredToService, |
193 | 176 | #LOG |
194 | 177 | modelactions.LOG: model.api.log, |
247 | 230 | cmd_info.duration = time.time() - cmd_info.itime |
248 | 231 | self._mapper_manager.update(cmd_info) |
249 | 232 | |
250 | self.processOutput(plugin, term_output) | |
233 | self.processOutput(plugin, term_output, cmd_info.getID() | |
234 | ) | |
251 | 235 | del self._active_plugins[pid] |
252 | 236 | return True |
253 | 237 | |
261 | 245 | self._mapper_manager.save(cmd_info) |
262 | 246 | |
263 | 247 | if plugin in self._plugins: |
264 | self.processOutput(self._plugins[plugin], filepath, True) | |
248 | self.processOutput(self._plugins[plugin], filepath, cmd_info.getID(), True ) | |
265 | 249 | cmd_info.duration = time.time() - cmd_info.itime |
266 | 250 | self._mapper_manager.update(cmd_info) |
267 | 251 | return True |
46 | 46 | ADDVULNWEBSRV = 2037 |
47 | 47 | CADDVULNWEBSRV = 2038 |
48 | 48 | ADDNOTENOTE = 2039 |
49 | CADDNOTENOTE = 2039 | |
50 | 49 | PLUGINSTART = 3000 |
51 | 50 | PLUGINEND = 3001 |
15 | 15 | |
16 | 16 | import model.api |
17 | 17 | import model.common |
18 | from model.common import ( | |
19 | factory, | |
20 | ModelObjectVuln, | |
21 | ModelObjectVulnWeb, | |
22 | ModelObjectNote, | |
23 | ModelObjectCred | |
24 | ) | |
25 | from model.hosts import Host, Interface, Service | |
18 | from model.common import factory | |
19 | from persistence.server.models import (Host, | |
20 | Interface, | |
21 | Service, | |
22 | Vuln, | |
23 | VulnWeb, | |
24 | Credential, | |
25 | Note, | |
26 | Command | |
27 | ) | |
26 | 28 | from plugins.modelactions import modelactions |
27 | 29 | |
28 | 30 | from config.configuration import getInstanceConfiguration |
139 | 141 | """ |
140 | 142 | self._pending_actions.put(args) |
141 | 143 | |
142 | def createAndAddHost(self, name, os="unknown", category=None, | |
143 | update=False, old_hostname=None): | |
144 | self.__addPendingAction(modelactions.CADDHOST, name, os, category, | |
145 | update, old_hostname) | |
146 | return factory.generateID(Host.class_signature, name=name, os=os) | |
144 | def createAndAddHost(self, name, os="unknown"): | |
145 | ||
146 | host_obj = factory.createModelObject( | |
147 | Host.class_signature, | |
148 | name, os=os, parent_id=None) | |
149 | ||
150 | host_obj._metadata.creator = self.id | |
151 | self.__addPendingAction(modelactions.ADDHOST, host_obj) | |
152 | return host_obj.getID() | |
147 | 153 | |
148 | 154 | def createAndAddInterface( |
149 | 155 | self, host_id, name="", mac="00:00:00:00:00:00", |
151 | 157 | ipv4_dns=[], ipv6_address="0000:0000:0000:0000:0000:0000:0000:0000", |
152 | 158 | ipv6_prefix="00", |
153 | 159 | ipv6_gateway="0000:0000:0000:0000:0000:0000:0000:0000", ipv6_dns=[], |
154 | network_segment="", hostname_resolution=[] | |
155 | ): | |
156 | self.__addPendingAction(modelactions.CADDINTERFACE, host_id, name, | |
157 | mac, ipv4_address, ipv4_mask, ipv4_gateway, | |
158 | ipv4_dns, ipv6_address, ipv6_prefix, | |
159 | ipv6_gateway, ipv6_dns, | |
160 | network_segment, hostname_resolution) | |
161 | return factory.generateID( | |
162 | Interface.class_signature, parent_id=host_id, name=name, mac=mac, | |
163 | ipv4_address=ipv4_address, ipv4_mask=ipv4_mask, | |
164 | ipv4_gateway=ipv4_gateway, ipv4_dns=ipv4_dns, | |
160 | network_segment="", hostname_resolution=[]): | |
161 | ||
162 | int_obj = model.common.factory.createModelObject( | |
163 | Interface.class_signature, | |
164 | name, mac=mac, ipv4_address=ipv4_address, | |
165 | ipv4_mask=ipv4_mask, ipv4_gateway=ipv4_gateway, ipv4_dns=ipv4_dns, | |
165 | 166 | ipv6_address=ipv6_address, ipv6_prefix=ipv6_prefix, |
166 | 167 | ipv6_gateway=ipv6_gateway, ipv6_dns=ipv6_dns, |
167 | 168 | network_segment=network_segment, |
168 | hostname_resolution=hostname_resolution) | |
169 | hostname_resolution=hostname_resolution, parent_id=host_id) | |
170 | ||
171 | int_obj._metadata.creator = self.id | |
172 | self.__addPendingAction(modelactions.ADDINTERFACE, host_id, int_obj) | |
173 | return int_obj.getID() | |
169 | 174 | |
170 | 175 | def createAndAddServiceToInterface(self, host_id, interface_id, name, |
171 | 176 | protocol="tcp?", ports=[], |
172 | 177 | status="running", version="unknown", |
173 | 178 | description=""): |
174 | self.__addPendingAction(modelactions.CADDSERVICEINT, host_id, | |
175 | interface_id, name, protocol, ports, status, | |
176 | version, description) | |
177 | return factory.generateID( | |
179 | ||
180 | serv_obj = model.common.factory.createModelObject( | |
178 | 181 | Service.class_signature, |
179 | name=name, protocol=protocol, ports=ports, | |
180 | status=status, version=version, description=description, | |
181 | parent_id=interface_id) | |
182 | name, protocol=protocol, ports=ports, status=status, | |
183 | version=version, description=description, parent_id=interface_id) | |
184 | ||
185 | serv_obj._metadata.creator = self.id | |
186 | self.__addPendingAction(modelactions.ADDSERVICEINT, host_id, interface_id, serv_obj) | |
187 | return serv_obj.getID() | |
182 | 188 | |
183 | 189 | def createAndAddVulnToHost(self, host_id, name, desc="", ref=[], |
184 | 190 | severity="", resolution=""): |
185 | self.__addPendingAction(modelactions.CADDVULNHOST, host_id, name, | |
186 | desc, ref, severity, resolution) | |
187 | return factory.generateID( | |
188 | ModelObjectVuln.class_signature, | |
189 | name=name, desc=desc, ref=ref, severity=severity, | |
190 | resolution=resolution, parent_id=host_id) | |
191 | ||
192 | vuln_obj = model.common.factory.createModelObject( | |
193 | Vuln.class_signature, | |
194 | name, desc=desc, ref=ref, severity=severity, resolution=resolution, | |
195 | confirmed=False, parent_id=host_id) | |
196 | ||
197 | vuln_obj._metadata.creator = self.id | |
198 | self.__addPendingAction(modelactions.ADDVULNHOST, host_id, vuln_obj) | |
199 | return vuln_obj.getID() | |
191 | 200 | |
192 | 201 | def createAndAddVulnToInterface(self, host_id, interface_id, name, |
193 | 202 | desc="", ref=[], severity="", |
194 | 203 | resolution=""): |
195 | self.__addPendingAction(modelactions.CADDVULNINT, host_id, | |
196 | interface_id, name, desc, ref, severity, | |
197 | resolution) | |
198 | return factory.generateID( | |
199 | ModelObjectVuln.class_signature, | |
200 | name=name, desc=desc, ref=ref, severity=severity, | |
201 | resolution=resolution, parent_id=interface_id) | |
204 | ||
205 | vuln_obj = model.common.factory.createModelObject( | |
206 | Vuln.class_signature, | |
207 | name, desc=desc, ref=ref, severity=severity, resolution=resolution, | |
208 | confirmed=False, parent_id=interface_id) | |
209 | ||
210 | vuln_obj._metadata.creator = self.id | |
211 | self.__addPendingAction(modelactions.ADDVULNINT, host_id, interface_id, vuln_obj) | |
212 | return vuln_obj.getID() | |
202 | 213 | |
203 | 214 | def createAndAddVulnToService(self, host_id, service_id, name, desc="", |
204 | 215 | ref=[], severity="", resolution=""): |
205 | self.__addPendingAction(modelactions.CADDVULNSRV, host_id, | |
206 | service_id, name, desc, ref, severity, | |
207 | resolution) | |
208 | return factory.generateID( | |
209 | ModelObjectVuln.class_signature, | |
210 | name=name, desc=desc, ref=ref, severity=severity, | |
211 | resolution=resolution, parent_id=service_id) | |
216 | ||
217 | vuln_obj = model.common.factory.createModelObject( | |
218 | Vuln.class_signature, | |
219 | name, desc=desc, ref=ref, severity=severity, resolution=resolution, | |
220 | confirmed=False, parent_id=service_id) | |
221 | ||
222 | vuln_obj._metadata.creator = self.id | |
223 | self.__addPendingAction(modelactions.ADDVULNSRV, host_id, service_id, vuln_obj) | |
224 | return vuln_obj.getID() | |
212 | 225 | |
213 | 226 | def createAndAddVulnWebToService(self, host_id, service_id, name, desc="", |
214 | 227 | ref=[], severity="", resolution="", |
215 | 228 | website="", path="", request="", |
216 | 229 | response="", method="", pname="", |
217 | 230 | params="", query="", category=""): |
218 | self.__addPendingAction(modelactions.CADDVULNWEBSRV, host_id, | |
219 | service_id, name, desc, ref, severity, | |
220 | resolution, website, path, request, response, | |
221 | method, pname, params, query, category) | |
222 | return factory.generateID( | |
223 | ModelObjectVulnWeb.class_signature, | |
224 | name=name, desc=desc, ref=ref, severity=severity, | |
225 | resolution=resolution, website=website, path=path, request=request, | |
226 | response=response, method=method, pname=pname, params=params, | |
227 | query=query, category=category, parent_id=service_id) | |
231 | ||
232 | vulnweb_obj = model.common.factory.createModelObject( | |
233 | VulnWeb.class_signature, | |
234 | name, desc=desc, ref=ref, severity=severity, resolution=resolution, | |
235 | website=website, path=path, request=request, response=response, | |
236 | method=method, pname=pname, params=params, query=query, | |
237 | category=category, confirmed=False, parent_id=service_id) | |
238 | ||
239 | vulnweb_obj._metadata.creator = self.id | |
240 | self.__addPendingAction(modelactions.ADDVULNWEBSRV, host_id, service_id, vulnweb_obj) | |
241 | return vulnweb_obj.getID() | |
228 | 242 | |
229 | 243 | def createAndAddNoteToHost(self, host_id, name, text): |
230 | self.__addPendingAction(modelactions.CADDNOTEHOST, host_id, name, text) | |
231 | return factory.generateID( | |
232 | ModelObjectNote.class_signature, | |
233 | name=name, text=text, parent_id=host_id) | |
244 | ||
245 | note_obj = model.common.factory.createModelObject( | |
246 | Note.class_signature, | |
247 | name, text=text, parent_id=host_id) | |
248 | ||
249 | note_obj._metadata.creator = self.id | |
250 | self.__addPendingAction(modelactions.ADDNOTEHOST, host_id, note_obj) | |
251 | return note_obj.getID() | |
234 | 252 | |
235 | 253 | def createAndAddNoteToInterface(self, host_id, interface_id, name, text): |
236 | self.__addPendingAction(modelactions.CADDNOTEINT, host_id, interface_id, | |
237 | name, text) | |
238 | return factory.generateID( | |
239 | ModelObjectNote.class_signature, | |
240 | name=name, text=text, parent_id=interface_id) | |
254 | ||
255 | note_obj = model.common.factory.createModelObject( | |
256 | Note.class_signature, | |
257 | name, text=text, parent_id=interface_id) | |
258 | ||
259 | note_obj._metadata.creator = self.id | |
260 | self.__addPendingAction(modelactions.ADDNOTEINT, host_id, interface_id, note_obj) | |
261 | return note_obj.getID() | |
241 | 262 | |
242 | 263 | def createAndAddNoteToService(self, host_id, service_id, name, text): |
243 | self.__addPendingAction(modelactions.CADDNOTESRV, host_id, service_id, | |
244 | name, text) | |
245 | return factory.generateID( | |
246 | ModelObjectNote.class_signature, | |
247 | name=name, text=text, parent_id=service_id) | |
264 | ||
265 | note_obj = model.common.factory.createModelObject( | |
266 | Note.class_signature, | |
267 | name, text=text, parent_id=service_id) | |
268 | ||
269 | note_obj._metadata.creator = self.id | |
270 | self.__addPendingAction(modelactions.ADDNOTESRV, host_id, service_id, note_obj) | |
271 | return note_obj.getID() | |
248 | 272 | |
249 | 273 | def createAndAddNoteToNote(self, host_id, service_id, note_id, name, text): |
250 | self.__addPendingAction(modelactions.CADDNOTENOTE, host_id, service_id, | |
251 | note_id, name, text) | |
252 | return factory.generateID( | |
253 | ModelObjectNote.class_signature, | |
254 | name=name, text=text, parent_id=note_id) | |
274 | ||
275 | note_obj = model.common.factory.createModelObject( | |
276 | Note.class_signature, | |
277 | name, text=text, parent_id=note_id) | |
278 | ||
279 | note_obj._metadata.creator = self.id | |
280 | self.__addPendingAction(modelactions.ADDNOTENOTE, host_id, service_id, note_id, note_obj) | |
281 | return note_obj.getID() | |
255 | 282 | |
256 | 283 | def createAndAddCredToService(self, host_id, service_id, username, |
257 | 284 | password): |
258 | self.__addPendingAction(modelactions.CADDCREDSRV, host_id, service_id, | |
259 | username, password) | |
260 | return factory.generateID( | |
261 | ModelObjectCred.class_signature, | |
262 | username=username, password=password, parent_id=service_id) | |
263 | ||
264 | def addHost(self, host, category=None, update=False, old_hostname=None): | |
265 | self.__addPendingAction(modelactions.ADDHOST, host, category, update, | |
266 | old_hostname) | |
267 | ||
268 | def addInterface(self, host_id, interface): | |
269 | self.__addPendingAction(modelactions.ADDINTERFACE, host_id, interface) | |
270 | ||
271 | def addApplication(self, host_id, application): | |
272 | self.__addPendingAction(modelactions.ADDAPPLICATION, host_id, | |
273 | application) | |
274 | ||
275 | def addServiceToApplication(self, host_id, application_id, service): | |
276 | self.__addPendingAction(modelactions.ADDSERVICEAPP, host_id, | |
277 | application_id, service) | |
278 | ||
279 | def addServiceToInterface(self, host_id, interface_id, service): | |
280 | self.__addPendingAction(modelactions.ADDSERVICEINT, host_id, | |
281 | interface_id, service) | |
282 | ||
283 | def addVulnToHost(self, host_id, vuln): | |
284 | self.__addPendingAction(modelactions.ADDVULNHOST, host_id, vuln) | |
285 | ||
286 | def addVulnToInterface(self, host_id, interface_id, vuln): | |
287 | self.__addPendingAction(modelactions.ADDVULNINT, host_id, interface_id, | |
288 | vuln) | |
289 | ||
290 | def addVulnToApplication(self, host_id, application_id, vuln): | |
291 | self.__addPendingAction(modelactions.ADDVULNAPP, host_id, | |
292 | application_id, vuln) | |
293 | ||
294 | def addVulnToService(self, host_id, service_id, vuln): | |
295 | self.__addPendingAction(modelactions.ADDVULNSRV, host_id, service_id, | |
296 | vuln) | |
297 | ||
298 | def addVulnWebToService(self, host_id, service_id, vuln): | |
299 | self.__addPendingAction(modelactions.ADDVULNWEBSRV, host_id, service_id, | |
300 | vuln) | |
301 | ||
302 | def addNoteToHost(self, host_id, note): | |
303 | self.__addPendingAction(modelactions.ADDNOTEHOST, host_id, note) | |
304 | ||
305 | def addNoteToInterface(self, host_id, interface_id, note): | |
306 | self.__addPendingAction(modelactions.ADDNOTEINT, host_id, interface_id, | |
307 | note) | |
308 | ||
309 | def addNoteToApplication(self, host_id, application_id, note): | |
310 | self.__addPendingAction(modelactions.ADDNOTEAPP, host_id, | |
311 | application_id, note) | |
312 | ||
313 | def addNoteToService(self, host_id, service_id, note): | |
314 | self.__addPendingAction(modelactions.ADDNOTESRV, host_id, service_id, | |
315 | note) | |
316 | ||
317 | def addNoteToNote(self, host_id, service_id, note_id, note): | |
318 | self.__addPendingAction(modelactions.ADDNOTENOTE, host_id, service_id, | |
319 | note_id, note) | |
320 | ||
321 | def addCredToService(self, host_id, service_id, cred): | |
322 | self.__addPendingAction(modelactions.ADDCREDSRV, host_id, service_id, | |
323 | cred) | |
324 | ||
325 | def delServiceFromInterface(self, service, hostname, | |
326 | intname, remote=True): | |
327 | self.__addPendingAction(modelactions.DELSERVICEINT, hostname, intname, | |
328 | service, remote) | |
285 | ||
286 | cred_obj = model.common.factory.createModelObject( | |
287 | Credential.class_signature, | |
288 | username, password=password, parent_id=service_id) | |
289 | ||
290 | cred_obj._metadata.creator = self.id | |
291 | self.__addPendingAction(modelactions.ADDCREDSRV, host_id, service_id, cred_obj) | |
292 | return cred_obj.getID() | |
329 | 293 | |
330 | 294 | def log(self, msg, level='INFO'): |
331 | 295 | self.__addPendingAction(modelactions.LOG, msg, level) |
0 | ''' | |
1 | Faraday Penetration Test IDE | |
2 | Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) | |
3 | See the file 'doc/LICENSE' for the license information | |
4 | ''' |
0 | #!/usr/bin/env python | |
1 | # -*- coding: utf-8 -*- | |
2 | ||
3 | ''' | |
4 | Faraday Penetration Test IDE | |
5 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
6 | See the file 'doc/LICENSE' for the license information | |
7 | ''' | |
8 | ||
9 | from plugins import core | |
10 | import re | |
11 | import socket | |
12 | ||
13 | __author__ = "Federico Fernandez - @q3rv0" | |
14 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
15 | __license__ = "" | |
16 | __version__ = "1.0.0" | |
17 | __maintainer__ = "Federico Fernandez" | |
18 | __email__ = "[email protected]" | |
19 | __status__ = "Development" | |
20 | ||
21 | ||
22 | class dirbPlugin(core.PluginBase): | |
23 | ||
24 | def __init__(self): | |
25 | core.PluginBase.__init__(self) | |
26 | self.id = "dirb" | |
27 | self.name = "Dirb" | |
28 | self.plugin_version = "0.0.1" | |
29 | self.version = "2.22" | |
30 | self.regexpUrl = r'((http[s]?)\:\/\/([\w\.]+)[.\S]+)' | |
31 | self._command_regex = re.compile(r'^(sudo dirb|dirb|\.\/dirb|sudo \.\/dirb).*?') | |
32 | self.text = [] | |
33 | ||
34 | ||
35 | def getPort(self, host, proto): | |
36 | p = re.search(r"\:([0-9]+)\/", host) | |
37 | if p is not None: | |
38 | return p.group(1) | |
39 | elif proto == 'https': | |
40 | return 443 | |
41 | else: | |
42 | return 80 | |
43 | ||
44 | ||
45 | def getIP(self, host): | |
46 | try: | |
47 | ip = socket.gethostbyname(host) | |
48 | except Exception: | |
49 | pass | |
50 | ||
51 | return ip | |
52 | ||
53 | ||
54 | def state(self, output): | |
55 | if output.find('COULDNT CONNECT') != -1: | |
56 | return "close" | |
57 | else: | |
58 | return "open" | |
59 | ||
60 | ||
61 | def pathsDirListing(self, output): | |
62 | data = [] | |
63 | r = re.findall(self.regexpUrl + r"[\-\._\w\*\s]+\s+\(!\) WARNING: Directory IS LISTABLE", | |
64 | output) | |
65 | for u in r: | |
66 | data.append(u[0]) | |
67 | ||
68 | paths = "\n".join(data) | |
69 | return paths | |
70 | ||
71 | ||
72 | def note(self, output): | |
73 | dirs = re.findall(r"==> DIRECTORY: "+self.regexpUrl, output) | |
74 | files = re.findall(r"\+ " + self.regexpUrl + r" \(.+\)", output) | |
75 | for d in dirs: | |
76 | self.text.append(d[0]) | |
77 | ||
78 | for f in files: | |
79 | self.text.append(f[0]) | |
80 | ||
81 | self.text = '\n'.join(self.text) | |
82 | ||
83 | ||
84 | def parseOutputString(self, output, debug=False): | |
85 | ||
86 | url = re.search(r"URL_BASE: " + self.regexpUrl, output) | |
87 | paths = self.pathsDirListing(output) | |
88 | status = self.state(output) | |
89 | self.note(output) | |
90 | ||
91 | if output.find('END_TIME') != -1 and url is not None: | |
92 | ||
93 | proto = url.group(2) | |
94 | domain = url.group(3) | |
95 | ip = self.getIP(domain) | |
96 | puerto = self.getPort(url.group(1), proto) | |
97 | ||
98 | host_id = self.createAndAddHost(ip) | |
99 | iface_id = self.createAndAddInterface(host_id, ip, ipv4_address = ip) | |
100 | serv_id = self.createAndAddServiceToInterface(host_id, iface_id, proto, protocol = proto, ports = puerto, status = status) | |
101 | ||
102 | if len(self.text) > 0: | |
103 | self.createAndAddNoteToService(host_id, serv_id, 'Url Fuzzing', self.text) | |
104 | ||
105 | if len(paths) > 0: | |
106 | self.createAndAddVulnWebToService(host_id, serv_id, "Directory Listing", severity = "med", website = domain, request = paths, method = "GET") | |
107 | ||
108 | return True | |
109 | ||
110 | ||
111 | def processCommandString(self, username, current_path, command_string): | |
112 | ||
113 | arg = "%s -w" % command_string | |
114 | return arg | |
115 | ||
116 | ||
117 | def createPlugin(): | |
118 | return dirbPlugin() |
0 | ''' | |
1 | Faraday Penetration Test IDE | |
2 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
3 | See the file 'doc/LICENSE' for the license information | |
4 | ||
5 | ''' |
0 | #!/usr/bin/env python | |
1 | # -*- coding: utf-8 -*- | |
2 | ||
3 | ''' | |
4 | Faraday Penetration Test IDE | |
5 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
6 | See the file 'doc/LICENSE' for the license information | |
7 | ||
8 | ''' | |
9 | from __future__ import with_statement | |
10 | from plugins import core | |
11 | from model import api | |
12 | import re | |
13 | import os | |
14 | import shlex | |
15 | import argparse | |
16 | import sys | |
17 | import random | |
18 | from StringIO import StringIO | |
19 | import traceback | |
20 | #current_path = os.path.abspath(os.getcwd()) | |
21 | ||
22 | ||
23 | class FPlugin(core.PluginBase): | |
24 | """ | |
25 | Example plugin to parse f output. | |
26 | """ | |
27 | ||
28 | def __init__(self): | |
29 | core.PluginBase.__init__(self) | |
30 | self.id = "faraday" | |
31 | self.name = "Faraday Output Plugin" | |
32 | self.plugin_version = "0.0.2" | |
33 | self.version = "1.0.0" | |
34 | self.options = None | |
35 | self._current_output = None | |
36 | self._command_regex = re.compile( | |
37 | r'^(sudo fplugin|sudo \./fplugin|\./fplugin).*?') | |
38 | self._hosts = [] | |
39 | self.args = None | |
40 | self._completition = { | |
41 | "": "f [i <Python Code>]", | |
42 | "-e": "execute model directly", | |
43 | "-o": "output command", | |
44 | } | |
45 | ||
46 | def parseOutputString(self, output, debug=False): | |
47 | pass | |
48 | ||
49 | file_arg_re = re.compile(r"^.*(-o\s*[^\s]+).*$") | |
50 | ||
51 | def processCommandString(self, username, current_path, command_string): | |
52 | """ | |
53 | Adds the -m parameter to get machine readable output. | |
54 | """ | |
55 | arg_match = self.file_arg_re.match(command_string) | |
56 | self._file_output_path = os.path.join( | |
57 | self.data_path, "f_output-%s.txt" % random.uniform(1, 10)) | |
58 | ||
59 | parser = argparse.ArgumentParser() | |
60 | ||
61 | parser.add_argument('-e') | |
62 | parser.add_argument('-f') | |
63 | parser.add_argument('-o') | |
64 | ||
65 | # NO support -h --help style parameters. | |
66 | # Need "" in all parameter. Example script.py -p "parameter1 | |
67 | # parameter2" | |
68 | parser.add_argument('-p') | |
69 | ||
70 | if arg_match is None: | |
71 | final = re.sub(r"(^.*?fplugin)", | |
72 | r"\1 -o %s" % self._file_output_path, | |
73 | command_string) | |
74 | else: | |
75 | final = re.sub(arg_match.group(1), | |
76 | r"-o %s" % self._file_output_path, | |
77 | command_string) | |
78 | ||
79 | cmd = shlex.split(re.sub(r'\-h|\-\-help', r'', final)) | |
80 | try: | |
81 | self.args, unknown = parser.parse_known_args(cmd) | |
82 | except SystemExit: | |
83 | pass | |
84 | ||
85 | codeEx = "" | |
86 | if self.args.e: | |
87 | codeEx = self.args.e | |
88 | elif self.args.f: | |
89 | with open(current_path + "/" + self.args.f) as f: | |
90 | codeEx = f.read() | |
91 | f.close() | |
92 | ||
93 | if codeEx: | |
94 | buffer = StringIO() | |
95 | sys.stdout = buffer | |
96 | ||
97 | try: | |
98 | locales = locals() | |
99 | locales.update({'script_parameters': self.args.p}) | |
100 | exec(codeEx, globals(), locales) | |
101 | ||
102 | except Exception: | |
103 | api.devlog("[Error] - Faraday plugin") | |
104 | api.devlog(traceback.format_exc()) | |
105 | ||
106 | sys.stdout = sys.__stdout__ | |
107 | ||
108 | try: | |
109 | f = open(self._file_output_path, "w") | |
110 | f.write(buffer.getvalue()) | |
111 | f.close() | |
112 | except: | |
113 | api.devlog("[Faraday] Can't save faraday plugin output file") | |
114 | return | |
115 | ||
116 | return final | |
117 | ||
118 | def setHost(self): | |
119 | pass | |
120 | ||
121 | ||
122 | def createPlugin(): | |
123 | return FPlugin() |
0 | ''' | |
1 | Faraday Penetration Test IDE | |
2 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
3 | See the file 'doc/LICENSE' for the license information | |
4 | ||
5 | ''' |
0 | #!/usr/bin/python | |
1 | ''' | |
2 | Copyright (C) 2016 xtr4nge [_AT_] gmail.com | |
3 | ||
4 | This program is free software: you can redistribute it and/or modify | |
5 | it under the terms of the GNU General Public License as published by | |
6 | the Free Software Foundation, either version 3 of the License, or | |
7 | (at your option) any later version. | |
8 | ||
9 | This program is distributed in the hope that it will be useful, | |
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | GNU General Public License for more details. | |
13 | ||
14 | You should have received a copy of the GNU General Public License | |
15 | along with this program. If not, see <http://www.gnu.org/licenses/>. | |
16 | ''' | |
17 | ||
18 | import os, sys, getopt | |
19 | import urllib2 | |
20 | import json | |
21 | import requests | |
22 | from requests import session | |
23 | ||
24 | requests.packages.urllib3.disable_warnings() # DISABLE SSL CHECK WARNINGS | |
25 | ||
26 | gVersion = "1.0" | |
27 | server = "http://127.0.0.1:8000"; | |
28 | token = "e5dab9a69988dd65e578041416773149ea57a054" | |
29 | ||
30 | def usage(): | |
31 | print "\nFruityWiFi API " + gVersion + " by @xtr4nge" | |
32 | ||
33 | print "Usage: ./client <options>\n" | |
34 | print "Options:" | |
35 | print "-x <command>, --execute=<commnd> exec the command passed as parameter." | |
36 | print "-t <token>, --token=<token> authentication token." | |
37 | print "-s <server>, --server=<server> FruityWiFi server [http{s}://ip:port]." | |
38 | print "-h Print this help message." | |
39 | print "" | |
40 | print "FruityWiFi: http://www.fruitywifi.com" | |
41 | print "" | |
42 | ||
43 | def parseOptions(argv): | |
44 | ||
45 | v_execute = "/log/dhcp" | |
46 | v_token = token | |
47 | v_server = server | |
48 | ||
49 | try: | |
50 | opts, args = getopt.getopt(argv, "hx:t:s:", | |
51 | ["help","execute=","token=","server="]) | |
52 | ||
53 | for opt, arg in opts: | |
54 | if opt in ("-h", "--help"): | |
55 | usage() | |
56 | sys.exit() | |
57 | elif opt in ("-x", "--execute"): | |
58 | v_execute = arg | |
59 | elif opt in ("-t", "--token"): | |
60 | v_token = arg | |
61 | elif opt in ("-s", "--server"): | |
62 | v_server = arg | |
63 | ||
64 | return (v_execute, v_token, v_server) | |
65 | ||
66 | except getopt.GetoptError: | |
67 | usage() | |
68 | sys.exit(2) | |
69 | ||
70 | (execute, token, server) = parseOptions(sys.argv[1:]) | |
71 | ||
72 | class webclient: | |
73 | ||
74 | def __init__(self, server, token): | |
75 | ||
76 | self.global_webserver = server | |
77 | self.path = "/modules/api/includes/ws_action.php" | |
78 | self.s = requests.session() | |
79 | self.token = token | |
80 | ||
81 | def login(self): | |
82 | ||
83 | payload = { | |
84 | 'action': 'login', | |
85 | 'token': self.token | |
86 | } | |
87 | ||
88 | self.s = requests.session() | |
89 | self.s.get(self.global_webserver, verify=False) # DISABLE SSL CHECK | |
90 | self.s.post(self.global_webserver + '/login.php', data=payload) | |
91 | ||
92 | def loginCheck(self): | |
93 | ||
94 | response = self.s.get(self.global_webserver + '/login_check.php') | |
95 | ||
96 | if response.text != "": | |
97 | self.login() | |
98 | ||
99 | if response.text != "": | |
100 | print json.dumps("[FruityWiFi]: Ah, Ah, Ah! You didn't say the magic word! (check API token and server)") | |
101 | sys.exit() | |
102 | ||
103 | return True | |
104 | ||
105 | def submitPost(self, data): | |
106 | response = self.s.post(self.global_webserver + data) | |
107 | return response.json | |
108 | ||
109 | if response.text == "": | |
110 | return True | |
111 | else: | |
112 | return False | |
113 | ||
114 | def submitGet(self, data): | |
115 | response = self.s.get(self.global_webserver + self.path + "?" + data) | |
116 | #print response.headers | |
117 | #print "debug: " + response.text | |
118 | #print response.json | |
119 | ||
120 | return response | |
121 | ||
122 | try: | |
123 | w = webclient(server, token) | |
124 | w.login() | |
125 | w.loginCheck() | |
126 | except Exception, e: | |
127 | print json.dumps("[FruityWiFi]: There is something wrong (%s)" % e) | |
128 | sys.exit(1) | |
129 | ||
130 | _exec = "/log/dhcp" | |
131 | _exec = execute | |
132 | if _exec != "": | |
133 | try: | |
134 | out = w.submitGet("api=" + str(_exec)) | |
135 | json_output = out.json() | |
136 | except Exception, e: | |
137 | print json.dumps("[FruityWiFi]: There is something wrong (%s)" % e) | |
138 | sys.exit(1) | |
139 | ||
140 | output = [] | |
141 | if _exec == "/log/dhcp": | |
142 | for item in json_output: | |
143 | if item.strip() != "": | |
144 | output = [item.split(" ")] | |
145 | else: | |
146 | output = json_output | |
147 | ||
148 | if len(output) > 0: | |
149 | print json.dumps(output) | |
150 | else: | |
151 | print json.dumps("No clients connected") |
0 | #!/usr/bin/env python | |
1 | # -*- coding: utf-8 -*- | |
2 | ||
3 | ''' | |
4 | Faraday Penetration Test IDE | |
5 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
6 | See the file 'doc/LICENSE' for the license information | |
7 | ||
8 | ''' | |
9 | from plugins import core | |
10 | import re | |
11 | import os, json | |
12 | import traceback | |
13 | ||
14 | __author__ = "xtr4nge" | |
15 | __copyright__ = "Copyright (c) 2016, FruityWiFi" | |
16 | __credits__ = ["xtr4nge"] | |
17 | __license__ = "" | |
18 | __version__ = "1.0.0" | |
19 | __maintainer__ = "xtr4nge" | |
20 | __email__ = "@xtr4nge" | |
21 | __status__ = "Development" | |
22 | ||
23 | class FruityWiFiPlugin(core.PluginBase): | |
24 | """ | |
25 | This plugin handles FruityWiFi clients. | |
26 | """ | |
27 | ||
28 | def __init__(self): | |
29 | core.PluginBase.__init__(self) | |
30 | self.id = "fruitywifi" | |
31 | self.name = "FruityWiFi" | |
32 | self.plugin_version = "0.0.1" | |
33 | self.version = "2.4" | |
34 | self.description = "http://www.fruitywifi.com" | |
35 | self.options = None | |
36 | self._current_output = None | |
37 | self.target = None | |
38 | ||
39 | self._command_regex = re.compile( | |
40 | r'^(fruitywifi).*?') | |
41 | ||
42 | self.addSetting("Token", str, "e5dab9a69988dd65e578041416773149ea57a054") | |
43 | self.addSetting("Server", str, "http://127.0.0.1:8000") | |
44 | self.addSetting("Severity", str, "high") | |
45 | ||
46 | def getSeverity(self, severity): | |
47 | if severity.lower() == "critical" or severity == "4": | |
48 | return 4 | |
49 | elif severity.lower() == "high" or severity == "3": | |
50 | return 3 | |
51 | elif severity.lower() == "med" or severity == "2": | |
52 | return 2 | |
53 | elif severity.lower() == "low" or severity == "1": | |
54 | return 1 | |
55 | elif severity.lower() == "info" or severity == "0": | |
56 | return 0 | |
57 | else: | |
58 | return 5 | |
59 | ||
60 | def createHostInterfaceVuln(self, ip_address, macaddress, hostname, desc, vuln_name, severity): | |
61 | h_id = self.createAndAddHost(ip_address) | |
62 | if self._isIPV4(ip_address): | |
63 | i_id = self.createAndAddInterface( | |
64 | h_id, | |
65 | ip_address, | |
66 | macaddress, | |
67 | ipv4_address=ip_address, | |
68 | hostname_resolution=[hostname] | |
69 | ) | |
70 | else: | |
71 | self.createAndAddInterface( | |
72 | h_id, ip_address, ipv6_address=ip_address, hostname_resolution=[hostname]) | |
73 | ||
74 | v_id = self.createAndAddVulnToHost( | |
75 | h_id, | |
76 | vuln_name, | |
77 | desc=desc, | |
78 | ref=["http://www.fruitywifi.com/"], | |
79 | severity=severity | |
80 | ) | |
81 | ||
82 | def parseOutputString(self, output, debug=False): | |
83 | ||
84 | try: | |
85 | output = json.loads(output) | |
86 | ||
87 | if len(output) > 0: | |
88 | ||
89 | if len(output[0]) == 3: | |
90 | ||
91 | severity = self.getSeverity(self.getSetting("Severity")) | |
92 | ||
93 | for item in output: | |
94 | ip_address = item[0] | |
95 | macaddress = item[1] | |
96 | hostname = item[2] | |
97 | vuln_name = "FruityWiFi" | |
98 | severity = severity | |
99 | ||
100 | desc = "Client ip: " + ip_address + \ | |
101 | " has been connected to FruityWiFi\n" | |
102 | desc += "More information:" | |
103 | desc += "\nname: " + hostname | |
104 | ||
105 | self.createHostInterfaceVuln(ip_address, macaddress, hostname, desc, vuln_name, severity) | |
106 | ||
107 | elif len(output[0]) == 5: | |
108 | for item in output: | |
109 | ip_address = item[0] | |
110 | macaddress = item[1] | |
111 | hostname = item[2] | |
112 | vuln_name = item[3] | |
113 | severity = item[4] | |
114 | ||
115 | desc = "Client ip: " + ip_address + \ | |
116 | " has been connected to FruityWiFi\n" | |
117 | desc += "More information:" | |
118 | desc += "\nname: " + hostname | |
119 | ||
120 | self.createHostInterfaceVuln(ip_address, macaddress, hostname, desc, vuln_name, severity) | |
121 | ||
122 | except: | |
123 | traceback.print_exc() | |
124 | ||
125 | return True | |
126 | ||
127 | def _isIPV4(self, ip): | |
128 | if len(ip.split(".")) == 4: | |
129 | return True | |
130 | else: | |
131 | return False | |
132 | ||
133 | def processCommandString(self, username, current_path, command_string, debug=False): | |
134 | """ | |
135 | """ | |
136 | #params = command_string.replace("fruitywifi","") | |
137 | params = "-t %s -s %s" % (self.getSetting("Token"), self.getSetting("Server")) | |
138 | ||
139 | return "python " + os.path.dirname(__file__) + "/fruitywifi.py " + params | |
140 | #return None | |
141 | ||
142 | def createPlugin(): | |
143 | return FruityWiFiPlugin() |
0 | ''' | |
1 | Faraday Penetration Test IDE | |
2 | Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) | |
3 | See the file 'doc/LICENSE' for the license information | |
4 | ''' |
0 | #!/usr/bin/env python | |
1 | # -*- coding: utf-8 -*- | |
2 | ||
3 | ''' | |
4 | Faraday Penetration Test IDE | |
5 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
6 | See the file 'doc/LICENSE' for the license information | |
7 | ''' | |
8 | ||
9 | from plugins import core | |
10 | import pprint | |
11 | import re | |
12 | ||
13 | __author__ = "Federico Fernandez - @q3rv0" | |
14 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
15 | __license__ = "" | |
16 | __version__ = "1.0.0" | |
17 | __maintainer__ = "Federico Fernandez" | |
18 | __email__ = "[email protected]" | |
19 | __status__ = "Development" | |
20 | ||
21 | class NetdiscoverPlugin(core.PluginBase): | |
22 | ||
23 | def __init__(self): | |
24 | core.PluginBase.__init__(self) | |
25 | self.id = "Netdiscover" | |
26 | self.name = "netdiscover" | |
27 | self.plugin_version = "0.0.1" | |
28 | self.version = "1.0.0" | |
29 | self._command_regex = re.compile(r'^(sudo netdiscover|netdiscover).*?') | |
30 | ||
31 | def parseOutputString(self, output, debug=False): | |
32 | #regexp get ip, mac and hostname | |
33 | reg = re.findall(r"(([0-9]+\.?){4})\s+(([0-9a-f]+\:?){6})((\s+[0-9]+){2})(.*)", output) | |
34 | ||
35 | if output.find('Finished!') != -1 and len(reg) > 0: | |
36 | ||
37 | for stdout in reg: | |
38 | ip_address = stdout[0] | |
39 | mac = stdout[2] | |
40 | hostname = stdout[6].strip() | |
41 | ||
42 | h_id = self.createAndAddHost(ip_address) | |
43 | self.createAndAddInterface(h_id, ip_address, ipv4_address=ip_address, mac=mac, hostname_resolution=[hostname]) | |
44 | ||
45 | return True | |
46 | ||
47 | def processCommandString(self, username, current_path, command_string): | |
48 | return None | |
49 | ||
50 | ||
51 | def createPlugin(): | |
52 | return NetdiscoverPlugin() |
60 | 60 | try: |
61 | 61 | return ET.fromstring(xml_output) |
62 | 62 | except SyntaxError, err: |
63 | print "SyntaxError: %s. %s" % (err, filepath) | |
63 | print "SyntaxError: %s." % (err) | |
64 | 64 | return None |
65 | 65 | |
66 | 66 | def get_hosts(self, tree): |
0 | #!/usr/bin/env python | |
1 | # -*- coding: utf-8 -*- | |
2 | ||
3 | ''' | |
4 | Faraday Penetration Test IDE | |
5 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
6 | See the file 'doc/LICENSE' for the license information | |
7 | ''' | |
8 | ||
9 | from plugins import core | |
10 | from config.configuration import getInstanceConfiguration | |
11 | from urlparse import urlparse | |
12 | import requests | |
13 | import xmlrpclib | |
14 | import json | |
15 | import uuid | |
16 | import re | |
17 | ||
18 | __author__ = "Alejandro Parodi" | |
19 | __copyright__ = "Copyright (c) 2016, Infobyte LLC" | |
20 | __credits__ = ["Parodi, Alejandro Julián"] | |
21 | __license__ = "" | |
22 | __version__ = "1.0.0" | |
23 | __maintainer__ = "Alejandro Parodi" | |
24 | __email__ = "[email protected]" | |
25 | __status__ = "Development" | |
26 | ||
27 | ||
28 | class SentinelPlugin(core.PluginBase): | |
29 | """ | |
30 | This plugin get information from Sentinel Tool. | |
31 | """ | |
32 | ||
33 | def __init__(self): | |
34 | core.PluginBase.__init__(self) | |
35 | self.id = "Sentinel" | |
36 | self.name = "Sentinel Online Plugin" | |
37 | self.plugin_version = "0.0.1" | |
38 | self.version = "1.0.0" | |
39 | self.baseURL = "https://sentinel.whitehatsec.com/api/" | |
40 | self.vulnURL = "https://source.whitehatsec.com/site_vuln_detail.html?site_id=" | |
41 | ||
42 | self.addSetting("Api_key", str, "") | |
43 | self.addSetting("Enable", str, "0") | |
44 | ||
45 | self.faraday_config = 'http://' + getInstanceConfiguration().getApiConInfoHost() + ':' + str(getInstanceConfiguration().getApiConInfoPort()) + '/' | |
46 | self.faraday_api = xmlrpclib.ServerProxy(self.faraday_config) | |
47 | self.format = "?format=json&display_all=1&key=" | |
48 | self._command_regex = re.compile( | |
49 | r'^(sudo sentinel|sentinel).*?') | |
50 | ||
51 | def parseOutputString(self, output, debug=False): | |
52 | ||
53 | if self.getSetting("Api_key") == "": | |
54 | self.log("Please set Sentinel API in plugin configuration", "ERROR") | |
55 | return True | |
56 | ||
57 | allVulns = self.getAllVulns() | |
58 | for element in allVulns['collection']: | |
59 | ||
60 | vulnClass = element.get('class', "Vuln_Without_Title") | |
61 | severity = element.get('severity', "INFO") | |
62 | host = element.get('url', 'Unknown Hostname') | |
63 | ||
64 | hostId = self.faraday_api.createAndAddHost(host, "", "", False, "") | |
65 | ||
66 | interfaceId = self.faraday_api.createAndAddInterface( | |
67 | hostId, | |
68 | host, | |
69 | '00:00:00:00:00:00', | |
70 | '0.0.0.0', | |
71 | '0.0.0.0', | |
72 | '0.0.0.0', | |
73 | [], | |
74 | host) | |
75 | ||
76 | serviceId = self.faraday_api.createAndAddServiceToInterface(hostId, interfaceId, "HTTP") | |
77 | vulnData = self.getAttackVector(element.get('href', 'unknown')) | |
78 | ||
79 | for vuln in vulnData['collection']: | |
80 | ||
81 | vuln_information = self.getVulnInformation(element.get('href', 'unknown')) | |
82 | ||
83 | desc = vuln_information.get("description", "").get("description_prepend", "") | |
84 | solution = vuln_information.get("solution", "").get("solution_prepend", "") | |
85 | siteId = vuln_information.get("site", "Unknown") | |
86 | id = vuln_information.get("id", uuid.uuid4()) | |
87 | ||
88 | vulnUrlComplete = self.vulnURL + siteId + "&vuln_id=" + id | |
89 | ||
90 | cvss = "CVSS: " + vuln_information.get("cvss_score", "") | |
91 | siteName = "Site-Name: " + vuln_information.get("site_name", "Unknown") | |
92 | ||
93 | found = vuln.get('found', '0000-00-00T00:00:00Z') | |
94 | tested = vuln.get('tested', '0000-00-00T00:00:00Z') | |
95 | request = vuln.get('request', {})#{} | |
96 | ||
97 | state = "State: " + vuln.get('state', 'Unknown') | |
98 | ||
99 | ||
100 | if(len(request)>0): | |
101 | ||
102 | url = request.get('url', "Unknown") | |
103 | method = request.get('method', "Unknown") | |
104 | headers = request.get("headers", []) | |
105 | reqHeader = "" | |
106 | ||
107 | if(headers == None): | |
108 | headers = [] | |
109 | ||
110 | for parts in headers: | |
111 | reqHeader += parts.get("name", "") + ":" + parts.get("value", "")+"\n" | |
112 | ||
113 | body = request.get("body", {})#{} | |
114 | ||
115 | if(len(body)>0): | |
116 | bodyContent = body.get('content', "") | |
117 | ||
118 | response = vuln.get('response', {})#{} | |
119 | ||
120 | if(len(response)>0): | |
121 | ||
122 | status = str(response.get("status", "")) | |
123 | headers = response.get("headers", []) | |
124 | resHeader = "" | |
125 | ||
126 | if (headers == None): | |
127 | headers = [] | |
128 | ||
129 | for parts in headers: | |
130 | resHeader += parts.get("name", "") + ":" + parts.get("value", "") + "\n" | |
131 | ||
132 | resBody = response.get("body", {})#{} | |
133 | if(len(resBody)>0): | |
134 | resBodyMatch = resBody.get("body_match", {})# | |
135 | resBodyContent = resBodyMatch.get("content", "") | |
136 | ||
137 | data = "\n\nFound: " + found + "\n" + "Tested: " + tested + "\n" + state | |
138 | req = "" | |
139 | res = "" | |
140 | ||
141 | if(len(request)>0): | |
142 | ||
143 | req = method+" "+url+"\n" | |
144 | req += reqHeader+"\n" | |
145 | req += bodyContent | |
146 | ||
147 | if (len(response)>0): | |
148 | ||
149 | res = "Status: "+status+"\n" | |
150 | res += resHeader+"\n" | |
151 | res += resBodyContent | |
152 | ||
153 | name = vulnClass+" ID: "+id | |
154 | ||
155 | self.faraday_api.createAndAddVulnWebToService(hostId, | |
156 | serviceId, name, | |
157 | desc + data, | |
158 | [cvss, state, siteName, vulnUrlComplete], | |
159 | severity, solution, url, "", req, res, | |
160 | method, "", "", "", "") | |
161 | return True | |
162 | ||
163 | def getAllVulns(self): | |
164 | req = self.baseURL+"vuln"+self.format+self.getSetting("Api_key") | |
165 | r = requests.get(req) | |
166 | return json.loads(r.text) | |
167 | ||
168 | def getAttackVector(self, path): | |
169 | if(path != "unknown"): | |
170 | req = self.baseURL + path[5:] + "/attack_vector" +self.format + self.getSetting("Api_key") | |
171 | r = requests.get(req) | |
172 | return json.loads(r.text) | |
173 | else: | |
174 | return json.loads("{'colection':[]}") | |
175 | ||
176 | def getVulnInformation(self, path): | |
177 | req = self.baseURL + path[5:] + self.format + self.getSetting("Api_key") + "&display_description=1&display_solution=1&display_cvss=1" | |
178 | r = requests.get(req) | |
179 | return json.loads(r.text) | |
180 | ||
181 | def processCommandString(self, username, current_path, command_string): | |
182 | return | |
183 | ||
184 | ||
185 | def createPlugin(): | |
186 | return SentinelPlugin() |
10 | 10 | from plugins import core |
11 | 11 | import re |
12 | 12 | import socket |
13 | ||
14 | __author__ = "Joaquin L. Pereyra" | |
13 | import json | |
14 | ||
15 | __author__ = "Joaquin L. Pereyra | Federico Fernandez" | |
15 | 16 | __copyright__ = "Copyright (c) 2016, Infobyte LLC" |
16 | 17 | __credits__ = ["Joaquin L. Pereyra"] |
17 | 18 | __license__ = "" |
38 | 39 | self.version = "2.9.1" |
39 | 40 | self._command_regex = re.compile( |
40 | 41 | r"^((sudo )?(ruby )?(\.\/)?(wpscan)(.rb)?)") |
42 | self.addSetting("WPscan path", str, "~/wpscan") | |
43 | self.wpPath = self.getSetting("WPscan path") | |
44 | self.themes = {} | |
45 | self.plugins = {} | |
46 | self.wpversion = '' | |
47 | self.risks = {'AUTHBYPASS' : 'high', | |
48 | 'BYPASS' : 'med', | |
49 | 'CSRF' : 'med', | |
50 | 'DOS' : 'med', | |
51 | 'FPD' : 'info', | |
52 | 'LFI' : 'high', | |
53 | 'MULTI' : 'unclassified', | |
54 | 'PRIVESC' : 'high', | |
55 | 'RCE' : 'critical', | |
56 | 'REDIRECT' : 'low', | |
57 | 'RFI' : 'critical', | |
58 | 'SQLI' : 'high', | |
59 | 'SSRF' : 'med', | |
60 | 'UNKNOWN' : 'unclassified', | |
61 | 'UPLOAD' : 'critical', | |
62 | 'XSS' : 'high', | |
63 | 'XXE' : 'high' | |
64 | } | |
65 | ||
66 | def getPort(self, host, proto): | |
67 | p = re.search(r"\:([0-9]+)\/", host) | |
68 | if p is not None: | |
69 | return p.group(1) | |
70 | elif proto == 'https': | |
71 | return 443 | |
72 | else: | |
73 | return 80 | |
74 | ||
75 | ||
76 | def parseOutputWpscan(self, output): | |
77 | sp = output.split('0m Name:') #cut by name | |
78 | for e in sp: | |
79 | if 'Title:' in e: | |
80 | if 'WordPress version' in e: | |
81 | r = re.search(r'WordPress version ([\d\.]+) identified', e) #get wordpress version | |
82 | self.wpversion = r.group(1) | |
83 | ||
84 | elif 'wp-content/themes/' in e: | |
85 | name = re.findall(r"Location: .+themes\/(.+)\/", e) # get theme name | |
86 | title = re.findall(r"Title: (.+)", e) # get vulnerabilities title | |
87 | self.themes[name[0]] = title #insert theme in dicc {'themeName' : ['titles', 'titles']} | |
88 | ||
89 | else: | |
90 | name = re.findall(r"Location: .+plugins\/(.+)\/", e) #get plugin name | |
91 | title = re.findall(r"Title: (.+)", e) #get vulnerabilities title | |
92 | self.plugins[name[0]] = title #insert plugin in dicc {'plugin' : ['titles', 'titles']} | |
93 | ||
94 | def addThemesOrPluginsVulns(self, db, dic, host_id, serv_id, domain, wp_url, name): | |
95 | with open(self.wpPath+'/data/'+db, "r") as data: | |
96 | j = json.load(data) | |
97 | for p in dic: | |
98 | for title in dic[p]: | |
99 | for vuln in j[p]['vulnerabilities']: #iter vulnerabilities | |
100 | if vuln['title'] == title: # if output title is equal | |
101 | title = vuln['title'] #title | |
102 | risk = self.risks[vuln['vuln_type']] #vuln type (xss,rce,lfi,etc) - risk | |
103 | location = wp_url+'wp-content/'+name+'/'+p+'/' | |
104 | if vuln['references'].has_key('url') == True: #if references | |
105 | refs = vuln['references']['url'] #references[] | |
106 | else: | |
107 | refs = [] #references null | |
108 | self.createAndAddVulnWebToService(host_id, serv_id, title, severity = risk, website = domain, ref = refs, path = location) | |
109 | ||
110 | ||
111 | def addWPVulns(self, db, version, host_id, serv_id, domain): | |
112 | with open(self.wpPath+'/data/'+db, "r") as data: | |
113 | j = json.load(data) | |
114 | for vuln in j[version]['vulnerabilities']: #iter vulnerabilities | |
115 | title = vuln['title'] #title | |
116 | risk = self.risks[vuln['vuln_type']] #vuln type (xss,rce,lfi,etc) - risk | |
117 | if vuln['references'].has_key('url') == True: #if references | |
118 | refs = vuln['references']['url'] #references[] | |
119 | else: | |
120 | refs = [] #references null | |
121 | self.createAndAddVulnWebToService(host_id, serv_id, title, severity = risk, website = domain, ref = refs) | |
122 | ||
41 | 123 | |
42 | 124 | def parseOutputString(self, output, debug=False): |
43 | 125 | """Parses the output given as a string by the wpscan tool and creates |
44 | 126 | the appropiate hosts, interface, service and vulnerabilites. Return |
45 | 127 | nothing. |
46 | 128 | """ |
129 | self.parseOutputWpscan(output) | |
130 | wp_url = re.search(r"URL: ((http[s]?)\:\/\/([\w\.]+)[.\S]+)", output) | |
47 | 131 | service, base_url = self.__get_service_and_url_from_output(output) |
132 | port = self.getPort(wp_url.group(1), service) | |
48 | 133 | host_ip = socket.gethostbyname_ex(base_url)[2][0] |
49 | 134 | host_id = self.createAndAddHost(host_ip) |
50 | 135 | interface_id = self.createAndAddInterface(host_id, host_ip, |
52 | 137 | hostname_resolution=base_url) |
53 | 138 | |
54 | 139 | service_id = self.createAndAddServiceToInterface(host_id, interface_id, |
55 | service, "tcp") | |
140 | service, "tcp", ports = port) | |
56 | 141 | |
57 | 142 | potential_vulns = re.findall(r"(\[\!\].*)", output) |
58 | 143 | for potential_vuln in potential_vulns: |
64 | 149 | name=vuln_name, |
65 | 150 | website=base_url, |
66 | 151 | path=path, severity=severity) |
67 | ||
152 | ||
153 | if len(self.plugins) > 0: | |
154 | self.addThemesOrPluginsVulns('plugins.json', self.plugins, host_id, service_id, base_url, wp_url.group(1), 'plugins') | |
155 | if len(self.wpversion) > 0: | |
156 | self.addWPVulns('wordpresses.json', self.wpversion, host_id, service_id, base_url) | |
157 | if len(self.themes) > 0: | |
158 | self.addThemesOrPluginsVulns('themes.json', self.themes, host_id, service_id, base_url, wp_url.group(1), 'themes') | |
159 | ||
160 | ||
68 | 161 | def __get_service_and_url_from_output(self, output): |
69 | 162 | """ Return the service (http or https) and the base URL (URL without |
70 | 163 | protocol) from a given string. In case more than one URL is found, |
6 | 6 | import server.utils.logger |
7 | 7 | |
8 | 8 | from server.app import app |
9 | from server.utils.web import validate_workspace | |
9 | from server.utils.web import validate_workspace, build_bad_request_response, get_basic_auth | |
10 | from server.couchdb import get_user_from_session | |
10 | 11 | from restkit.errors import RequestFailed, ResourceError |
11 | 12 | |
12 | 13 | logger = server.utils.logger.get_logger(__name__) |
13 | ||
14 | def build_bad_request_response(msg): | |
15 | response = flask.jsonify({'error': msg}) | |
16 | response.status_code = 400 | |
17 | return response | |
18 | 14 | |
19 | 15 | @app.route('/ws/<workspace>/doc/<doc_id>', methods=['GET']) |
20 | 16 | def get_document(workspace, doc_id): |
38 | 34 | couchdb_conn = ws.couchdb |
39 | 35 | is_update_request = bool(document.get('_rev', False)) |
40 | 36 | |
37 | # change user in metadata based on session information | |
38 | user = get_user_from_session(flask.request.cookies, get_basic_auth()) | |
39 | if document.get('metadata', {}).has_key('owner'): | |
40 | document['metadata']['owner'] = user | |
41 | if document.get('metadata', {}).has_key('update_user'): | |
42 | document['metadata']['update_user'] = user | |
43 | ||
41 | 44 | try: |
42 | 45 | response = couchdb_conn.save_doc(document) |
43 | 46 | except RequestFailed as e: |
59 | 62 | return flask.jsonify(response) |
60 | 63 | |
61 | 64 | @app.route('/ws/<workspace>/doc/<doc_id>', methods=['DELETE']) |
62 | def delete_document(workspace, doc_id): | |
65 | def delete_document_and_children(workspace, doc_id): | |
66 | ||
67 | def delete_document(doc_id, doc_rev): | |
68 | try: | |
69 | response = couchdb_conn.delete_doc({'_id': doc_id, '_rev': doc_rev}) | |
70 | ||
71 | except RequestFailed as e: | |
72 | response = flask.jsonify(json.loads(e.msg)) | |
73 | response.status_code = e.status_int | |
74 | return response | |
75 | except ResourceError as e: | |
76 | response = flask.jsonify({'error': e.message}) | |
77 | response.status_code = e.status_int | |
78 | return response | |
79 | if response.get('ok', False): | |
80 | doc_importer = server.database.DocumentImporter(ws.connector) | |
81 | doc_importer.delete_entity_from_doc_id(doc_id) | |
82 | ||
83 | return flask.jsonify(response) | |
84 | ||
63 | 85 | validate_workspace(workspace) |
64 | ||
65 | 86 | ws = server.database.get(workspace) |
66 | 87 | couchdb_conn = ws.couchdb |
67 | doc_rev = flask.request.args.get('rev', '') | |
88 | docs_to_delete = couchdb_conn.get_documents_starting_with_id(doc_id) | |
89 | docs_ids_to_delete = filter(lambda x: x is not None, (map(lambda d: d.get('id'), docs_to_delete))) | |
90 | docs_revs_to_delete = map(lambda d: d['doc']['_rev'], docs_to_delete) | |
91 | responses = map(delete_document, docs_ids_to_delete, docs_revs_to_delete) | |
68 | 92 | |
69 | try: | |
70 | response = couchdb_conn.delete_doc({'_id': doc_id, '_rev': doc_rev}) | |
71 | ||
72 | except RequestFailed as e: | |
73 | response = flask.jsonify(json.loads(e.msg)) | |
74 | response.status_code = e.status_int | |
75 | return response | |
76 | ||
77 | except ResourceError as e: | |
78 | response = flask.jsonify({'error': e.message}) | |
79 | response.status_code = e.status_int | |
80 | return response | |
81 | ||
82 | if response.get('ok', False): | |
83 | doc_importer = server.database.DocumentImporter(ws.connector) | |
84 | doc_importer.delete_entity_from_doc_id(doc_id) | |
85 | ||
86 | return flask.jsonify(response) | |
93 | return responses[0] |
2 | 2 | # See the file 'doc/LICENSE' for the license information |
3 | 3 | |
4 | 4 | import flask |
5 | import json | |
5 | 6 | |
6 | 7 | from server.app import app |
7 | 8 | from server.dao.host import HostDAO |
9 | 10 | from server.dao.service import ServiceDAO |
10 | 11 | from server.dao.interface import InterfaceDAO |
11 | 12 | from server.dao.note import NoteDAO |
12 | from server.utils.web import gzipped, validate_workspace, get_basic_auth | |
13 | from server.utils.web import gzipped, validate_workspace, get_basic_auth, validate_admin_perm, validate_database, build_bad_request_response | |
13 | 14 | from server.couchdb import list_workspaces_as_user, get_workspace, get_auth_info |
15 | from server.database import get_manager | |
14 | 16 | |
15 | 17 | |
16 | 18 | @app.route('/ws', methods=['GET']) |
57 | 59 | if not ws.get('description'): ws['description'] = '' |
58 | 60 | return flask.jsonify(ws) |
59 | 61 | |
62 | @app.route('/ws/<workspace>', methods=['PUT']) | |
63 | @gzipped | |
64 | def workspace_create_or_update(workspace): | |
65 | # only admins can create workspaces | |
66 | validate_admin_perm() | |
67 | ||
68 | try: | |
69 | document = json.loads(flask.request.data) | |
70 | except ValueError: | |
71 | return build_bad_request_response('invalid json') | |
72 | if not document.get('name', None): | |
73 | return build_bad_request_response('workspace name needed') | |
74 | if document.get('name') != workspace: | |
75 | return build_bad_request_response('workspace name and route parameter don\'t match') | |
76 | if document.get('name').startswith('_'): | |
77 | return build_bad_request_response('database cannot start with an underscore') | |
78 | document['_id'] = document.get('name') # document dictionary does not have id, add it | |
79 | ||
80 | is_update_request = bool(document.get('_rev', False)) | |
81 | ||
82 | db_manager = get_manager() | |
83 | ||
84 | if workspace in db_manager and is_update_request: | |
85 | res = db_manager.update_workspace(document) | |
86 | elif workspace not in db_manager and not is_update_request: | |
87 | res = db_manager.create_workspace(document) | |
88 | else: | |
89 | abort(400) | |
90 | ||
91 | if not res: | |
92 | response = flask.jsonify({'error': "There was an error {0} the workspace".format("updating" if is_update_request else "creating")}) | |
93 | response.status_code = 500 | |
94 | return response | |
95 | ||
96 | return flask.jsonify({'ok': True}) | |
97 | ||
98 | @app.route('/ws/<workspace>', methods=['DELETE']) | |
99 | @gzipped | |
100 | def workspace_delete(workspace): | |
101 | # only admins can delete workspaces | |
102 | validate_admin_perm() | |
103 | validate_workspace(workspace) | |
104 | ||
105 | db_manager = get_manager() | |
106 | ||
107 | if not db_manager.delete_workspace(workspace): | |
108 | response = flask.jsonify({'error': "There was an error deleting the workspace"}) | |
109 | response.status_code = 500 | |
110 | return response | |
111 | ||
112 | return flask.jsonify({'ok': True}) |
1 | 1 | # Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) |
2 | 2 | # See the file 'doc/LICENSE' for the license information |
3 | 3 | |
4 | import sys, time, json | |
5 | import couchdbkit | |
6 | 4 | import restkit |
7 | import threading, thread | |
8 | 5 | import server.utils.logger |
9 | 6 | import requests |
10 | 7 | |
11 | 8 | from couchdbkit import Server |
12 | 9 | from couchdbkit.exceptions import ResourceNotFound |
13 | 10 | from couchdbkit.resource import CouchdbResource |
11 | from restkit.errors import RequestFailed, ResourceError | |
14 | 12 | from managers.all import ViewsManager |
15 | 13 | from server import config |
16 | 14 | |
47 | 45 | def get_or_create_db(self, ws_name): |
48 | 46 | return self.__server.get_or_create_db(ws_name) |
49 | 47 | |
48 | def create_db(self, ws_name): | |
49 | return self.__server.create_db(ws_name) | |
50 | ||
51 | def delete_db(self, ws_name): | |
52 | return self.__server.delete_db(ws_name) | |
53 | ||
50 | 54 | |
51 | 55 | class Workspace(object): |
52 | 56 | def __init__(self, ws_name, couchdb_server_conn=None): |
53 | 57 | self.__ws_name = ws_name |
54 | 58 | self.__server = couchdb_server_conn or CouchDBServer() |
55 | self.__changes_monitor_thread = None | |
56 | 59 | self.__get_workspace() |
57 | 60 | |
58 | 61 | def __get_workspace(self): |
86 | 89 | yield doc |
87 | 90 | offset += per_request |
88 | 91 | |
92 | def get_documents_starting_with_id(self, starting_id): | |
93 | startkey = '"{0}"'.format(starting_id) | |
94 | endkey = '"{0}.z"'.format(starting_id) | |
95 | return self.__workspace.all_docs(include_docs=True, start_key=startkey, end_key=endkey) | |
96 | ||
89 | 97 | def __get_all_docs(self, limit, offset=0): |
90 | 98 | return self.__workspace.all_docs(include_docs=True, limit=limit, skip=offset) |
91 | 99 | |
92 | def start_changes_monitor(self, changes_callback, last_seq=0): | |
93 | logger.debug(u'Starting changes monitor for workspace {} since {}'.format(self.__ws_name, last_seq)) | |
94 | ws_stream = ChangesStream(self.__ws_name, feed='continuous', | |
95 | since=last_seq, include_docs='true', heartbeat='true') | |
96 | self.__changes_monitor_thread = ChangesMonitorThread(ws_stream, changes_callback) | |
97 | self.__changes_monitor_thread.daemon = True | |
98 | self.__changes_monitor_thread.start() | |
99 | ||
100 | def close(self): | |
101 | if self.__changes_monitor_thread: | |
102 | self.__changes_monitor_thread.stop() | |
103 | self.__changes_monitor_thread = None | |
104 | ||
105 | 100 | def save_doc(self, document): |
106 | return self.__workspace.save_doc(document) | |
101 | return self.__workspace.save_doc(document, encode_attachments=False) | |
107 | 102 | |
108 | 103 | def delete_doc(self, document): |
109 | 104 | return self.__workspace.delete_doc(document) |
119 | 114 | def fetch_attachment(self, doc, name): |
120 | 115 | return self.__workspace.fetch_attachment(doc, name) |
121 | 116 | |
122 | class ChangesStream(object): | |
123 | ALL_DBS = "__ALL_WORKSPACES__" | |
124 | ||
125 | def __init__(self, db, **params): | |
126 | self.__db = db | |
127 | self.__url = self.__build_url() | |
128 | self.__params = params | |
129 | self.__response = None | |
130 | self.__stop = False | |
131 | ||
132 | def __build_url(self): | |
133 | if self.__db == ChangesStream.ALL_DBS: | |
134 | return get_couchdb_url() + '/_db_updates' | |
135 | else: | |
136 | return get_couchdb_url() + ('/%s/_changes' % self.__db) | |
137 | ||
138 | def __enter__(self): | |
139 | return self | |
140 | ||
141 | def __exit__(self, *args): | |
142 | return False | |
143 | ||
144 | def __next__(self): | |
145 | return self | |
146 | ||
147 | def __iter__(self): | |
148 | while not self.__stop: | |
149 | try: | |
150 | # TODO: Connection timeout is too long. | |
151 | self.__response = requests.get( | |
152 | self.__url, params=self.__params, | |
153 | stream=True, auth=get_auth_info()) | |
154 | ||
155 | for raw_line in self.__response.iter_lines(): | |
156 | if self.__stop: | |
157 | break | |
158 | ||
159 | line = self.__sanitize(raw_line) | |
160 | if not line: | |
161 | continue | |
162 | ||
163 | change = self.__parse_change(line) | |
164 | if not change: | |
165 | continue | |
166 | ||
167 | yield change | |
168 | ||
169 | except Exception, e: | |
170 | # On workspace deletion, requests will probably | |
171 | # fail to perform the request or the connection | |
172 | # will be closed. Check if this was intentional | |
173 | # by checking on the __stop flag. | |
174 | if self.__stop: | |
175 | break | |
176 | ||
177 | import traceback | |
178 | logger.debug(traceback.format_exc()) | |
179 | ||
180 | # Close everything but keep retrying | |
181 | self.stop() | |
182 | self.__stop = False | |
183 | ||
184 | logger.warning(u"Lost connection to CouchDB. Retrying in 3 seconds...") | |
185 | time.sleep(3) | |
186 | logger.info(u"Retrying...") | |
187 | ||
188 | def __sanitize(self, raw_line): | |
189 | if not isinstance(raw_line, basestring): | |
190 | return None | |
191 | ||
192 | line = raw_line.strip() | |
193 | ||
194 | # Ignore line cases | |
195 | if not line: | |
196 | return None | |
197 | if line in ('{"results":', '],'): | |
198 | return None | |
199 | ||
200 | # Modify line cases | |
201 | if line.startswith('"last_seq"'): | |
202 | line = '{' + line | |
203 | if line.endswith(","): | |
204 | line = line[:-1] | |
205 | ||
206 | return line | |
207 | ||
208 | def __parse_change(self, line): | |
209 | try: | |
210 | obj = json.loads(line) | |
211 | return obj | |
212 | except ValueError: | |
213 | return None | |
214 | ||
215 | def stop(self): | |
216 | self.__stop = True | |
217 | if self.__response is not None: | |
218 | self.__response.close() | |
219 | self.__response = None | |
220 | ||
221 | class Change(object): | |
222 | REQUIRED_FIELDS = ('doc', 'changes', 'id', 'seq') | |
223 | ||
224 | @staticmethod | |
225 | def validate(change_doc): | |
226 | return all(map(lambda prop: prop in change_doc, Change.REQUIRED_FIELDS)) | |
227 | ||
228 | def __init__(self, change_doc): | |
229 | self.change_doc = change_doc | |
230 | self.doc = change_doc.get('doc') | |
231 | self.revision = change_doc.get('changes')[-1].get('rev') | |
232 | self.doc_id = change_doc.get('id') | |
233 | self.seq = change_doc.get('seq') | |
234 | ||
235 | self.deleted = bool(change_doc.get('deleted', False)) | |
236 | self.updated = (int(self.revision.split('-')[0]) > 1) | |
237 | self.added = (not self.deleted and not self.updated) | |
238 | ||
239 | ||
240 | class DBChange(object): | |
241 | @staticmethod | |
242 | def validate(change_doc): | |
243 | return True | |
244 | ||
245 | def __init__(self, change_doc): | |
246 | self.change_doc = change_doc | |
247 | self.type = change_doc.get('type', None) | |
248 | self.deleted = (self.type == 'deleted') | |
249 | self.created = (self.type == 'created') | |
250 | self.db_name = change_doc.get('db_name', None) | |
251 | ||
252 | class MonitorThread(threading.Thread): | |
253 | CHANGE_CLS = None | |
254 | ||
255 | def __init__(self, stream, changes_callback): | |
256 | super(MonitorThread, self).__init__() | |
257 | self.__stream = stream | |
258 | self.__changes_callback = changes_callback | |
259 | ||
260 | def run(self): | |
261 | for change_doc in self.__stream: | |
262 | try: | |
263 | if self.CHANGE_CLS.validate(change_doc): | |
264 | self.__changes_callback(self.CHANGE_CLS(change_doc)) | |
265 | else: | |
266 | logger.debug(u'Ignoring change: {}'.format(change_doc)) | |
267 | ||
268 | except Exception, e: | |
269 | import traceback | |
270 | logger.debug(traceback.format_exc()) | |
271 | logger.warning(u"Error while processing change. Ignoring. Offending change: {}".format(change_doc)) | |
272 | ||
273 | if change_doc.get('error', None): | |
274 | if change_doc.get('error') == 'unauthorized': | |
275 | logger.error(u"Unauthorized access to CouchDB. Make sure faraday-server's"\ | |
276 | " configuration file has CouchDB admin's credentials set") | |
277 | thread.interrupt_main() | |
278 | ||
279 | # TODO: A proper fix is needed here | |
280 | elif change_doc.get('reason') == 'no_db_file': | |
281 | self.__stream.stop() | |
282 | break | |
283 | ||
284 | def stop(self): | |
285 | self.__stream.stop() | |
286 | ||
287 | class ChangesMonitorThread(MonitorThread): | |
288 | CHANGE_CLS = Change | |
289 | ||
290 | class DBsMonitorThread(MonitorThread): | |
291 | CHANGE_CLS = DBChange | |
292 | 117 | |
293 | 118 | def get_couchdb_url(): |
294 | 119 | couchdb_port = config.couchdb.port if config.couchdb.protocol == 'http' else config.couchdb.ssl_port |
339 | 164 | # respond 401 if it doesn't have access to it |
340 | 165 | return (response.status_code != requests.codes.unauthorized) |
341 | 166 | |
342 | def start_dbs_monitor(changes_callback): | |
343 | logger.debug(u'Starting global workspaces monitor') | |
344 | dbs_stream = ChangesStream(ChangesStream.ALL_DBS, feed='continuous', heartbeat='true') | |
345 | monitor_thread = DBsMonitorThread(dbs_stream, changes_callback) | |
346 | monitor_thread.daemon = True | |
347 | monitor_thread.start() | |
348 | return monitor_thread | |
167 | def get_user_from_session(cookies=None, credentials=None): | |
168 | session_url = "%s/_session" % get_couchdb_url() | |
169 | res = requests.get(session_url, cookies=cookies, auth=credentials) | |
170 | if res.ok: | |
171 | user = res.json()['userCtx']['name'] | |
172 | return user if user else '' | |
349 | 173 | |
350 | 174 | def push_reports(): |
351 | 175 | vmanager = ViewsManager() |
359 | 183 | logger.debug(traceback.format_exc()) |
360 | 184 | logger.warning("Reports database couldn't be uploaded. You need to be an admin to do it") |
361 | 185 | |
186 | def upload_views(workspace): | |
187 | """ Upload views with couchdb behind of ViewsManager """ | |
188 | vmanager = ViewsManager() | |
189 | try: | |
190 | vmanager.addViews(workspace) | |
191 | except: | |
192 | import traceback | |
193 | logger.debug(traceback.format_exc()) | |
194 | logger.warning("Views documents couldn't be uploaded. You need to be an admin to do it") | |
195 | ||
196 | def create_workspace(workspace): | |
197 | ||
198 | couch_server = CouchDBServer() | |
199 | couch_server.create_db(workspace.get('name')) | |
200 | ||
201 | ws = couch_server.get_workspace_handler(workspace.get('name')) | |
202 | upload_views(ws) | |
203 | ||
204 | try: | |
205 | response = ws.save_doc(workspace) | |
206 | except (RequestFailed, ResourceError): | |
207 | # create an error | |
208 | response = {'ok': False} | |
209 | ||
210 | success = response.get('ok', False) | |
211 | if not success: | |
212 | # if the document was not create, delete db | |
213 | couch_server.delete_db(workspace.get('name')) | |
214 | ||
215 | return success | |
216 | ||
217 | def update_workspace(workspace): | |
218 | couch_server = CouchDBServer() | |
219 | ws = couch_server.get_workspace_handler(workspace.get('name')) | |
220 | ||
221 | try: | |
222 | response = ws.save_doc(workspace) | |
223 | except (RequestFailed, ResourceError): | |
224 | # create an error | |
225 | response = {'ok': False} | |
226 | return response.get('ok', False) | |
227 | ||
228 | def delete_workspace(ws_name): | |
229 | couch_server = CouchDBServer() | |
230 | try: | |
231 | couch_server.delete_db(ws_name) | |
232 | except: | |
233 | return False | |
234 | return True |
35 | 35 | Credential.description, Credential.owned, EntityMetadata.couchdb_id,\ |
36 | 36 | EntityMetadata.revision, EntityMetadata.update_time, EntityMetadata.update_user,\ |
37 | 37 | EntityMetadata.update_action, EntityMetadata.creator, EntityMetadata.create_time,\ |
38 | EntityMetadata.update_controller_action, EntityMetadata.owner) | |
38 | EntityMetadata.update_controller_action, EntityMetadata.owner, EntityMetadata.command_id) | |
39 | 39 | |
40 | 40 | query = self._session.query(creds_bundle)\ |
41 | 41 | .outerjoin(EntityMetadata, EntityMetadata.id == Credential.entity_metadata_id) |
65 | 65 | 'creator': cred.creator, |
66 | 66 | 'create_time': cred.create_time, |
67 | 67 | 'update_controller_action': cred.update_controller_action, |
68 | 'owner': cred.owner | |
68 | 'owner': cred.owner, | |
69 | 'command_id': cred.command_id | |
69 | 70 | }, |
70 | 71 | 'couchid': cred.couchdb_id }} |
71 | 72 |
40 | 40 | Host.default_gateway_ip, Host.default_gateway_mac, EntityMetadata.couchdb_id,\ |
41 | 41 | EntityMetadata.revision, EntityMetadata.update_time, EntityMetadata.update_user,\ |
42 | 42 | EntityMetadata.update_action, EntityMetadata.creator, EntityMetadata.create_time,\ |
43 | EntityMetadata.update_controller_action, EntityMetadata.owner, | |
43 | EntityMetadata.update_controller_action, EntityMetadata.owner, EntityMetadata.command_id,\ | |
44 | 44 | func.group_concat(distinct(Interface.id)).label('interfaces'),\ |
45 | 45 | func.count(distinct(Vulnerability.id)).label('vuln_count'),\ |
46 | 46 | func.count(distinct(Service.id)).label('open_services_count')) |
85 | 85 | 'creator': host.creator, |
86 | 86 | 'create_time': host.create_time, |
87 | 87 | 'update_controller_action': host.update_controller_action, |
88 | 'owner': host.owner | |
88 | 'owner': host.owner, | |
89 | 'command_id': host.command_id | |
89 | 90 | }, |
90 | 91 | 'vulns': host.vuln_count, |
91 | 92 | 'services': host.open_services_count, |
28 | 28 | Interface.ports_opened, Interface.ports_closed, Interface.host_id, EntityMetadata.couchdb_id,\ |
29 | 29 | EntityMetadata.revision, EntityMetadata.update_time, EntityMetadata.update_user,\ |
30 | 30 | EntityMetadata.update_action, EntityMetadata.creator, EntityMetadata.create_time,\ |
31 | EntityMetadata.update_controller_action, EntityMetadata.owner) | |
31 | EntityMetadata.update_controller_action, EntityMetadata.owner, EntityMetadata.command_id) | |
32 | 32 | |
33 | 33 | query = self._session.query(interface_bundle).\ |
34 | 34 | outerjoin(EntityMetadata, EntityMetadata.id == Interface.entity_metadata_id) |
74 | 74 | 'creator': interface.creator, |
75 | 75 | 'create_time': interface.create_time, |
76 | 76 | 'update_controller_action': interface.update_controller_action, |
77 | 'owner': interface.owner | |
77 | 'owner': interface.owner, | |
78 | 'command_id': interface.command_id | |
78 | 79 | }, |
79 | 80 | 'host_id': interface.host_id} |
80 | 81 | } |
33 | 33 | note_bundle = Bundle('note', Note.id, Note.name, Note.text, Note.description, Note.owned, EntityMetadata.couchdb_id,\ |
34 | 34 | EntityMetadata.revision, EntityMetadata.update_time, EntityMetadata.update_user,\ |
35 | 35 | EntityMetadata.update_action, EntityMetadata.creator, EntityMetadata.create_time,\ |
36 | EntityMetadata.update_controller_action, EntityMetadata.owner) | |
36 | EntityMetadata.update_controller_action, EntityMetadata.owner, EntityMetadata.command_id) | |
37 | 37 | |
38 | 38 | query = self._session.query(note_bundle)\ |
39 | 39 | .outerjoin(EntityMetadata, EntityMetadata.id == Note.entity_metadata_id) |
64 | 64 | 'creator': note.creator, |
65 | 65 | 'create_time': note.create_time, |
66 | 66 | 'update_controller_action': note.update_controller_action, |
67 | 'owner': note.owner | |
67 | 'owner': note.owner, | |
68 | 'command_id': note.command_id | |
68 | 69 | }, |
69 | 70 | 'couchid': note.couchdb_id }} |
70 | 71 |
32 | 32 | func.count(distinct(Vulnerability.id)).label('vuln_count'), EntityMetadata.couchdb_id,\ |
33 | 33 | EntityMetadata.revision, EntityMetadata.update_time, EntityMetadata.update_user,\ |
34 | 34 | EntityMetadata.update_action, EntityMetadata.creator, EntityMetadata.create_time,\ |
35 | EntityMetadata.update_controller_action, EntityMetadata.owner) | |
35 | EntityMetadata.update_controller_action, EntityMetadata.owner, EntityMetadata.command_id) | |
36 | 36 | |
37 | 37 | query = self._session.query(service_bundle).\ |
38 | 38 | group_by(Service.id).\ |
65 | 65 | 'creator': service.creator, |
66 | 66 | 'create_time': service.create_time, |
67 | 67 | 'update_controller_action': service.update_controller_action, |
68 | 'owner': service.owner | |
68 | 'owner': service.owner, | |
69 | 'command_id': service.command_id | |
69 | 70 | }, |
70 | 71 | 'protocol': service.protocol, |
71 | 72 | 'status': service.status, |
30 | 30 | "owned": [Vulnerability.owned], |
31 | 31 | "easeofresolution": [Vulnerability.easeofresolution], |
32 | 32 | "type": [EntityMetadata.document_type], |
33 | "status": [], | |
33 | "status": [Vulnerability.status], | |
34 | 34 | "website": [Vulnerability.website], |
35 | 35 | "path": [Vulnerability.path], |
36 | 36 | "request": [Vulnerability.request], |
48 | 48 | "serviceid": [Service.id], |
49 | 49 | "interfaceid": [Interface.id], |
50 | 50 | "web": [], |
51 | "issuetracker": [] | |
51 | "issuetracker": [], | |
52 | "creator": [EntityMetadata.creator] | |
52 | 53 | } |
53 | 54 | |
54 | STRICT_FILTERING = ["type", "service", "couchid", "hostid", "serviceid", 'interfaceid', 'id'] | |
55 | STRICT_FILTERING = ["type", "service", "couchid", "hostid", "serviceid", 'interfaceid', 'id', 'status'] | |
55 | 56 | |
56 | 57 | def list(self, search=None, page=0, page_size=0, order_by=None, order_dir=None, vuln_filter={}): |
57 | 58 | results, count = self.__query_database(search, page, page_size, order_by, order_dir, vuln_filter) |
72 | 73 | Vulnerability.confirmed, Vulnerability.data,\ |
73 | 74 | Vulnerability.description, Vulnerability.easeofresolution, Vulnerability.impact_accountability,\ |
74 | 75 | Vulnerability.impact_availability, Vulnerability.impact_confidentiality, Vulnerability.impact_integrity,\ |
75 | Vulnerability.refs, Vulnerability.resolution, Vulnerability.severity, Vulnerability.owned,\ | |
76 | Vulnerability.refs, Vulnerability.resolution, Vulnerability.severity, Vulnerability.owned, Vulnerability.status,\ | |
76 | 77 | Vulnerability.website, Vulnerability.path, Vulnerability.request, Vulnerability.response,\ |
77 | 78 | Vulnerability.method, Vulnerability.params, Vulnerability.pname, Vulnerability.query,\ |
78 | 79 | EntityMetadata.couchdb_id, EntityMetadata.revision, EntityMetadata.create_time, EntityMetadata.creator,\ |
79 | 80 | EntityMetadata.owner, EntityMetadata.update_action, EntityMetadata.update_controller_action,\ |
80 | EntityMetadata.update_time, EntityMetadata.update_user, EntityMetadata.document_type, Vulnerability.attachments) | |
81 | EntityMetadata.update_time, EntityMetadata.update_user, EntityMetadata.document_type, EntityMetadata.command_id, Vulnerability.attachments) | |
81 | 82 | service_bundle = Bundle('service', Service.name.label('s_name'), Service.ports, Service.protocol, Service.id) |
82 | 83 | host_bundle = Bundle('host', Host.name) |
83 | 84 | |
160 | 161 | 'update_action': vuln.update_action, |
161 | 162 | 'update_controller_action': vuln.update_controller_action, |
162 | 163 | 'update_time': vuln.update_time, |
163 | 'update_user': vuln.update_user | |
164 | 'update_user': vuln.update_user, | |
165 | 'command_id': vuln.command_id | |
164 | 166 | }, |
165 | 167 | '_attachments': json.loads(vuln.attachments), |
166 | 168 | 'name': vuln.v_name, |
169 | 171 | 'owner': vuln.owner, |
170 | 172 | 'parent': get_parent_id(vuln.couchdb_id), |
171 | 173 | 'refs': json.loads(vuln.refs), |
172 | 'status': '', | |
174 | 'status': vuln.status, | |
173 | 175 | 'website': vuln.website, |
174 | 176 | 'path': vuln.path, |
175 | 177 | 'request': vuln.request, |
1 | 1 | # Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) |
2 | 2 | # See the file 'doc/LICENSE' for the license information |
3 | 3 | |
4 | import os, sys, re | |
4 | import os | |
5 | import re | |
5 | 6 | import atexit |
6 | import logging | |
7 | import threading | |
8 | 7 | import server.models |
9 | 8 | import server.config |
10 | 9 | import server.couchdb |
15 | 14 | from sqlalchemy.exc import IntegrityError |
16 | 15 | from sqlalchemy.orm import scoped_session, sessionmaker |
17 | 16 | from sqlalchemy.orm.exc import MultipleResultsFound |
18 | from restkit.errors import RequestError, Unauthorized | |
19 | 17 | |
20 | 18 | logger = server.utils.logger.get_logger(__name__) |
21 | 19 | |
36 | 34 | """ This is called by Flask to cleanup sessions created in the context of a request """ |
37 | 35 | _db_manager.close_sessions() |
38 | 36 | |
37 | def get_manager(): | |
38 | return _db_manager | |
39 | ||
39 | 40 | |
40 | 41 | class Manager(object): |
41 | 42 | def __init__(self): |
43 | 44 | |
44 | 45 | # Open all existent databases on workspaces path |
45 | 46 | self.__init_sessions() |
46 | ||
47 | # Start CouchDB database monitor | |
48 | server.couchdb.start_dbs_monitor(self.__process_workspace_change) | |
49 | 47 | |
50 | 48 | # Register database closing to be executed when process goes down |
51 | 49 | atexit.register(self.close_databases) |
79 | 77 | def __init_workspace(self, ws_name, db_conn=None): |
80 | 78 | if ws_name not in self.__workspaces: |
81 | 79 | new_workspace = Workspace(ws_name, db_conn=db_conn) |
82 | new_workspace.start_sync_job() | |
83 | 80 | self.__workspaces[ws_name] = new_workspace |
84 | 81 | |
85 | 82 | def get_workspace(self, ws_name): |
88 | 85 | except KeyError: |
89 | 86 | raise WorkspaceNotFound(ws_name) |
90 | 87 | |
91 | def __process_workspace_change(self, change): | |
92 | if change.created: | |
93 | logger.info(u'Workspace {} was created'.format(change.db_name)) | |
94 | self.__process_new_workspace(change.db_name) | |
95 | ||
96 | elif change.deleted: | |
97 | logger.info(u'Workspace {} was deleted'.format(change.db_name)) | |
98 | self.__process_delete_workspace(change.db_name) | |
88 | def create_workspace(self, workspace): | |
89 | # create the couch database first | |
90 | ok = server.couchdb.create_workspace(workspace) | |
91 | if ok: | |
92 | self.__process_new_workspace(workspace.get('name')) | |
93 | return ok | |
94 | ||
95 | def update_workspace(self, workspace): | |
96 | # update the couch database | |
97 | return server.couchdb.update_workspace(workspace) | |
98 | ||
99 | def delete_workspace(self, ws_name): | |
100 | # create the couch database first | |
101 | ok = server.couchdb.delete_workspace(ws_name) | |
102 | if ok: | |
103 | self.__process_delete_workspace(ws_name) | |
104 | return ok | |
99 | 105 | |
100 | 106 | def __process_new_workspace(self, ws_name): |
101 | 107 | if ws_name in self.__workspaces: |
132 | 138 | def is_valid_workspace(self, ws_name): |
133 | 139 | return ws_name in self.__workspaces |
134 | 140 | |
141 | def __contains__(self, ws_name): | |
142 | return self.is_valid_workspace(ws_name) | |
143 | ||
135 | 144 | def close_sessions(self): |
136 | 145 | for workspace in self.__workspaces.values(): |
137 | 146 | workspace.close_session() |
145 | 154 | def __init__(self, db_name, db_conn=None, couchdb_conn=None, couchdb_server_conn=None): |
146 | 155 | self.__db_conn = db_conn or Connector(db_name) |
147 | 156 | self.__couchdb_conn = couchdb_conn or server.couchdb.Workspace(db_name, couchdb_server_conn) |
148 | self.__sync = Synchronizer(self.__db_conn, self.__couchdb_conn) | |
149 | 157 | |
150 | 158 | @property |
151 | 159 | def connector(self): |
160 | 168 | def couchdb(self): |
161 | 169 | return self.__couchdb_conn |
162 | 170 | |
163 | def start_sync_job(self): | |
164 | self.__sync.start() | |
165 | ||
166 | def wait_until_sync(self, timeout): | |
167 | self.__sync.wait_until_sync(timeout) | |
168 | ||
169 | 171 | def close_session(self): |
170 | 172 | self.__db_conn.close() |
171 | 173 | |
172 | 174 | def close(self): |
173 | self.__sync.close() | |
174 | self.__couchdb_conn.close() | |
175 | 175 | self.close_session() |
176 | 176 | |
177 | 177 | def delete(self): |
239 | 239 | |
240 | 240 | return True |
241 | 241 | |
242 | ||
243 | class Synchronizer(object): | |
244 | def __init__(self, db_conn, couchdb_conn): | |
245 | self.__db_conn = db_conn | |
246 | self.__db_conf = Configuration(db_conn) | |
247 | self.__doc_importer = self.__build_doc_importer() | |
248 | self.__couchdb_conn = couchdb_conn | |
249 | self.__sync_seq_milestone = 0 | |
250 | ||
251 | # As far as we know, before the changes monitor is | |
252 | # running, the data is synchronized with CouchDB | |
253 | self.__data_sync_lock = threading.Lock() | |
254 | self.__data_sync_event = threading.Event() | |
255 | self.__data_sync_event.set() | |
256 | ||
257 | def __build_doc_importer(self): | |
258 | def post_change_cbk(change): | |
259 | self.__last_seq = change.seq | |
260 | # Set sync event when the database is updated relative | |
261 | # to the milestone set | |
262 | if self.__last_seq >= self.__sync_seq_milestone: | |
263 | self.__data_sync_event.set() | |
264 | ||
265 | return DocumentImporter(self.__db_conn, post_processing_change_cbk=post_change_cbk) | |
266 | ||
267 | def start(self): | |
268 | self.__last_seq = self.__db_conf.get_last_seq() | |
269 | logger.debug(u'Workspace {} last update: {}'.format(self.__db_conn.db_name, self.__last_seq)) | |
270 | self.__couchdb_conn.start_changes_monitor(self.__doc_importer.process_change, last_seq=self.__last_seq) | |
271 | ||
272 | def close(self): | |
273 | self.__couchdb_conn.close() | |
274 | ||
275 | def wait_until_sync(self, timeout): | |
276 | """ | |
277 | Wait a maximum of <timeout> seconds for Faraday server to | |
278 | synchronize its database with CouchDB. This is intended to | |
279 | provide a solution to data inconsistencies between CouchDB | |
280 | and the server on short windows of time between an update | |
281 | and its importation into Faraday server's database. | |
282 | Currently, this case is commonly seen when entities are | |
283 | updated or added and, immediately afterwards, a query for | |
284 | them is made. | |
285 | """ | |
286 | # Synchronize access to data synchronization to avoid race | |
287 | # conditions on heavy workload (mainly because multiple | |
288 | # threads could set different milestones and clear the sync | |
289 | # event on unexpected moments, rendering the possibility that | |
290 | # this function returns False when data is in fact synchronized | |
291 | # or to wait <timeout> seconds without need). (PS: maybe this | |
292 | # not necessary given its current use and cause overhead for a | |
293 | # situation that may not be troublesome) | |
294 | # This may cause performance issues if processing CouchDB | |
295 | # changes is taking a lot of time. If that is a persistent | |
296 | # issue adjust the timeout to a lower value to minimize its | |
297 | # impact | |
298 | with self.__data_sync_lock: | |
299 | self.__wait_until_database_is_sync(timeout) | |
300 | ||
301 | def __wait_until_database_is_sync(self, timeout): | |
302 | """ | |
303 | This function will establish a milestone by asking CouchDB's last | |
304 | update sequence number to then wait for an event signal from the | |
305 | changes monitor when its last procesed change is newer or as new | |
306 | as this milestone. | |
307 | If synchronization isn't achieved in <timeout> seconds it will | |
308 | return False, communicating that data consistency can be ensured | |
309 | after this call. | |
310 | """ | |
311 | self.__set_sync_milestone() | |
312 | ||
313 | logger.debug(u"Waiting until synchronization with CouchDB (ws: {}, couchdb: {})".format( | |
314 | self.__last_seq, self.__sync_seq_milestone)) | |
315 | ||
316 | self.__data_sync_event.wait(timeout) | |
317 | is_sync = self.__data_sync_event.is_set() | |
318 | ||
319 | if is_sync: | |
320 | logger.debug(u"Synchronized with CouchDB to seq {}".format(self.__last_seq)) | |
321 | else: | |
322 | logger.debug(u"Synchronization timed out. Working with outdated database") | |
323 | ||
324 | return is_sync | |
325 | ||
326 | def __set_sync_milestone(self): | |
327 | """ | |
328 | Set a milestone from where we can check if data is synchronized | |
329 | between the server and CouchDB | |
330 | """ | |
331 | self.__sync_seq_milestone = self.__couchdb_conn.get_last_seq() | |
332 | # Clear event if last database seq version is outdated | |
333 | # relative to CouchDB | |
334 | if self.__last_seq < self.__sync_seq_milestone: | |
335 | self.__data_sync_event.clear() | |
336 | ||
337 | ||
338 | 242 | class DocumentImporter(object): |
339 | 243 | def __init__(self, db_conn, post_processing_change_cbk=None): |
340 | 244 | self.__db_conn = db_conn |
341 | 245 | self.__db_conf = Configuration(db_conn) |
342 | 246 | self.__post_processing_change_cbk = post_processing_change_cbk |
343 | ||
344 | # CHA, CHA, CHA, CHANGESSSS | |
345 | def process_change(self, change): | |
346 | logger.debug(u'New change for {}: {}'.format(self.__db_conn.db_name, change.change_doc)) | |
347 | ||
348 | if change.deleted: | |
349 | logger.debug(u'Doc {} was deleted'.format(change.doc_id)) | |
350 | self.delete_entity_from_doc_id(change.doc['_id']) | |
351 | ||
352 | elif change.updated: | |
353 | logger.debug(u'Doc {} was updated'.format(change.doc_id)) | |
354 | self.update_entity_from_doc(change.doc) | |
355 | ||
356 | elif change.added: | |
357 | if self.add_entity_from_doc(change.doc): | |
358 | logger.debug(u'Doc {} was added'.format(change.doc_id)) | |
359 | else: | |
360 | logger.debug(u"Doc {} was not added".format(change.doc_id)) | |
361 | ||
362 | if change.seq is not None: | |
363 | self.__db_conf.set_last_seq(change.seq) | |
364 | if self.__post_processing_change_cbk: | |
365 | self.__post_processing_change_cbk(change) | |
366 | 247 | |
367 | 248 | def add_entity_from_doc(self, document): |
368 | 249 | """ |
8 | 8 | from sqlalchemy.ext.declarative import declarative_base |
9 | 9 | |
10 | 10 | |
11 | SCHEMA_VERSION = 'W.2.1.0' | |
11 | SCHEMA_VERSION = 'W.2.2.0' | |
12 | 12 | |
13 | 13 | Base = declarative_base() |
14 | 14 | |
79 | 79 | update_controller_action = Column(String(250), nullable=True) |
80 | 80 | creator = Column(String(250), nullable=True) |
81 | 81 | owner = Column(String(250), nullable=True) |
82 | command_id = Column(String(250), nullable=True) | |
82 | 83 | |
83 | 84 | couchdb_id = Column(String(250)) |
84 | 85 | revision = Column(String(250)) |
99 | 100 | self.couchdb_id=document.get('_id') |
100 | 101 | self.revision=document.get('_rev') |
101 | 102 | self.document_type=document.get('type') |
103 | self.command_id = metadata.get('command_id', None) | |
102 | 104 | |
103 | 105 | if self.create_time is not None: |
104 | 106 | self.create_time = self.__truncate_to_epoch_in_seconds(self.create_time) |
137 | 139 | credentials = relationship('Credential') |
138 | 140 | |
139 | 141 | def update_from_document(self, document): |
142 | # Ticket #3387: if the 'os' field is None, we default to 'unknown' | |
143 | if not document.get('os'): document['os']='unknown' | |
144 | ||
140 | 145 | default_gateway = self.__get_default_gateway(document) |
141 | 146 | |
142 | 147 | self.name=document.get('name') |
318 | 323 | response = Column(Text()) |
319 | 324 | website = Column(String(250)) |
320 | 325 | |
326 | status = Column(String(250)) | |
327 | ||
321 | 328 | entity_metadata = relationship(EntityMetadata, uselist=False, cascade="all, delete-orphan", single_parent=True) |
322 | 329 | entity_metadata_id = Column(Integer, ForeignKey(EntityMetadata.id), index=True) |
323 | 330 | |
350 | 357 | self.request=document.get('request') |
351 | 358 | self.response=document.get('response') |
352 | 359 | self.website=document.get('website') |
360 | self.status=document.get('status', 'opened') | |
353 | 361 | |
354 | 362 | params = document.get('params', u'') |
355 | 363 | if isinstance(params, (list, tuple)): |
3 | 3 | |
4 | 4 | import gzip |
5 | 5 | import functools |
6 | import requests | |
6 | 7 | import server.database |
7 | 8 | import server.couchdb |
9 | from server import config | |
8 | 10 | |
9 | from flask import after_this_request, request, abort | |
11 | from flask import after_this_request, request, abort, jsonify | |
10 | 12 | from cStringIO import StringIO as IO |
11 | 13 | |
12 | 14 | |
75 | 77 | return (user, passwd) |
76 | 78 | return None |
77 | 79 | |
80 | def build_bad_request_response(msg): | |
81 | response = jsonify({'error': msg}) | |
82 | response.status_code = 400 | |
83 | return response | |
84 | ||
78 | 85 | def validate_workspace(workspace_name, timeout_sync=0.1): |
79 | 86 | if not server.database.is_valid_workspace(workspace_name): |
80 | 87 | abort(404) |
82 | 89 | if not server.couchdb.has_permissions_for(workspace_name, request.cookies, get_basic_auth()): |
83 | 90 | abort(401) |
84 | 91 | |
85 | wait_for_ws_sync_with_couchdb(workspace_name, timeout_sync) | |
92 | def validate_database(workspace_name): | |
93 | if server.database.is_valid_workspace(workspace_name): | |
94 | # 412: Precondition failed, since database already exists | |
95 | abort(412) | |
86 | 96 | |
87 | def wait_for_ws_sync_with_couchdb(workspace_name, timeout_sync): | |
88 | workspace = server.database.get(workspace_name) | |
89 | workspace.wait_until_sync(timeout_sync) | |
97 | def validate_admin_perm(): | |
98 | def __get_server_sessions_uri(): | |
99 | couchdb_port = config.couchdb.port if config.couchdb.protocol == 'http' else config.couchdb.ssl_port | |
100 | return "%s://%s:%s/_session" % (config.couchdb.protocol, config.couchdb.host, couchdb_port) | |
101 | def __check_response(response): | |
102 | response = response.json() | |
103 | if response.get('ok', False) and '_admin' in response.get('userCtx', {}).get('roles', []): | |
104 | return True | |
105 | return False | |
90 | 106 | |
107 | try: | |
108 | res = __check_response(requests.get(__get_server_sessions_uri(), cookies=request.cookies)) or \ | |
109 | __check_response(requests.get(__get_server_sessions_uri(), auth=get_basic_auth())) | |
110 | except requests.RequestException: | |
111 | res = False | |
112 | if not res: | |
113 | abort(401) |
195 | 195 | |
196 | 196 | /* ==== General COLORS ==== */ |
197 | 197 | /* Backgrounds Legacy */ |
198 | .fondo-rojo, .fondo-high, .fondo-error {background-color: #DF3936 !important;} | |
198 | .fondo-rojo, .fondo-high, .fondo-error, .fondo-opened {background-color: #DF3936 !important;} | |
199 | 199 | .fondo-negro {background-color: #000 !important;} |
200 | 200 | .fondo-gris1, .fondo-critical {background-color: #932ebe !important;} |
201 | 201 | .fondo-gris1, .fondo-unclassified {background-color: #999 !important;} |
202 | 202 | .fondo-blanco {background-color: #fff !important;} |
203 | .fondo-info {background-color: #2e97bd !important;} | |
204 | .fondo-naranja, .fondo-med {background-color: #DFBF35 !important;} | |
205 | .fondo-amarillo, .fondo-low, .fondo-created{background-color: #A1CE31 !important;} | |
203 | .fondo-info, .fondo-risk-accepted {background-color: #2e97bd !important;} | |
204 | .fondo-naranja, .fondo-medium, .fondo-med, .fondo-re-opened {background-color: #DFBF35 !important;} | |
205 | .fondo-amarillo, .fondo-low, .fondo-closed , .fondo-created{background-color: #A1CE31 !important;} | |
206 | 206 | .fondo-violeta {background-color: #932ebe !important;} |
207 | 207 | /* Backgrounds New */ |
208 | 208 | .background-unclassified { |
1106 | 1106 | border-radius: .25em; |
1107 | 1107 | } |
1108 | 1108 | |
1109 | .ui-notification.info{ | |
1110 | color: #31708f; | |
1111 | background-color: #d9edf7; | |
1112 | } | |
1113 | ||
1114 | .ui-notification a { | |
1115 | color: #31708f; | |
1116 | } | |
1117 | ||
1118 | .ui-notification>h3{ | |
1119 | font-size:14px; | |
1120 | font-weight:700; | |
1121 | display:block; | |
1122 | margin:0px 10px 0; | |
1123 | padding:0 0 5px; | |
1124 | text-align:right; | |
1125 | border-bottom:1px solid rgba(255,255,255,.3) | |
1126 | }⏎ |
16 | 16 | <meta name="author" content=""/> |
17 | 17 | |
18 | 18 | <!-- CSS --> |
19 | <link rel="stylesheet" type="text/css" href="script/anguilar-ui-notification.min.css" /> | |
19 | 20 | <link rel="stylesheet" type="text/css" href="script/font.opensans.css" /> |
20 | 21 | <link rel="stylesheet" type="text/css" href="normalize.css" /> |
21 | 22 | <link rel="stylesheet" type="text/css" href="estilos.css" /> |
47 | 48 | <script type="text/javascript" src="script/cryptojs-sha1.js"></script> |
48 | 49 | <script type="text/javascript" src="script/ZeroClipboard.min.js"></script> |
49 | 50 | <script type="text/javascript" src="script/sanitize.js"></script> |
51 | <script type="text/javascript" src="script/angular-ui-notification.min.js"></script> | |
50 | 52 | <!-- angular chart --> |
51 | 53 | <script type="text/javascript" src="script/Chart.js"></script> |
52 | 54 | <script type="text/javascript" src="script/angular-chart.min.js"></script> |
76 | 78 | <script type="text/javascript" src="scripts/commons/controllers/modal.js"></script> |
77 | 79 | <script type="text/javascript" src="scripts/commons/controllers/commercialCtrl.js"></script> |
78 | 80 | <script type="text/javascript" src="scripts/commons/providers/commons.js"></script> |
81 | <script type="text/javascript" src="scripts/commons/providers/server.js"></script> | |
79 | 82 | <script type="text/javascript" src="scripts/commons/filters/decodeURIComponent.js"></script> |
80 | 83 | <script type="text/javascript" src="scripts/commons/filters/encodeURIComponent.js"></script> |
81 | 84 | <script type="text/javascript" src="scripts/commons/filters/getByProperty.js"></script> |
0 | /** | |
1 | * angular-ui-notification - Angular.js service providing simple notifications using Bootstrap 3 styles with css transitions for animating | |
2 | * @author Alex_Crack | |
3 | * @version v0.2.0 | |
4 | * @link https://github.com/alexcrack/angular-ui-notification | |
5 | * @license MIT | |
6 | */ | |
7 | .ui-notification{position:fixed;z-index:9999;width:300px;-webkit-transition:all ease .5s;-o-transition:all ease .5s;transition:all ease .5s;color:#fff;border-radius:0;background:#337ab7;box-shadow:5px 5px 10px rgba(0,0,0,.3)}.ui-notification.clickable{cursor:pointer}.ui-notification.clickable:hover{opacity:.7}.ui-notification.killed{-webkit-transition:opacity ease 1s;-o-transition:opacity ease 1s;transition:opacity ease 1s;opacity:0}.ui-notification>h3{font-size:14px;font-weight:700;display:block;margin:10px 10px 0;padding:0 0 5px;text-align:left;border-bottom:1px solid rgba(255,255,255,.3)}.ui-notification a{color:#fff}.ui-notification a:hover{text-decoration:underline}.ui-notification>.message{margin:10px}.ui-notification.warning{color:#fff;background:#f0ad4e}.ui-notification.error{color:#fff;background:#d9534f}.ui-notification.success{color:#fff;background:#5cb85c}.ui-notification.info{color:#fff;background:#5bc0de}⏎ |
0 | /** | |
1 | * angular-ui-notification - Angular.js service providing simple notifications using Bootstrap 3 styles with css transitions for animating | |
2 | * @author Alex_Crack | |
3 | * @version v0.2.0 | |
4 | * @link https://github.com/alexcrack/angular-ui-notification | |
5 | * @license MIT | |
6 | */ | |
7 | angular.module("ui-notification",[]),angular.module("ui-notification").provider("Notification",function(){this.options={delay:5e3,startTop:10,startRight:10,verticalSpacing:10,horizontalSpacing:10,positionX:"right",positionY:"top",replaceMessage:!1,templateUrl:"angular-ui-notification.html",onClose:void 0,closeOnClick:!0,maxCount:0,container:"body"},this.setOptions=function(e){if(!angular.isObject(e))throw new Error("Options should be an object!");this.options=angular.extend({},this.options,e)},this.$get=["$timeout","$http","$compile","$templateCache","$rootScope","$injector","$sce","$q","$window",function(e,t,n,i,o,s,a,l,r){var c=this.options,p=c.startTop,d=c.startRight,u=c.verticalSpacing,f=c.horizontalSpacing,m=c.delay,g=[],h=!1,C=function(s,C){var y=l.defer();return"object"!=typeof s&&(s={message:s}),s.scope=s.scope?s.scope:o,s.template=s.templateUrl?s.templateUrl:c.templateUrl,s.delay=angular.isUndefined(s.delay)?m:s.delay,s.type=C||c.type||"",s.positionY=s.positionY?s.positionY:c.positionY,s.positionX=s.positionX?s.positionX:c.positionX,s.replaceMessage=s.replaceMessage?s.replaceMessage:c.replaceMessage,s.onClose=s.onClose?s.onClose:c.onClose,s.closeOnClick=null!==s.closeOnClick&&void 0!==s.closeOnClick?s.closeOnClick:c.closeOnClick,s.container=s.container?s.container:c.container,t.get(s.template,{cache:i}).success(function(t){function i(e){["-webkit-transition","-o-transition","transition"].forEach(function(t){m.css(t,e)})}var o=s.scope.$new();o.message=a.trustAsHtml(s.message),o.title=a.trustAsHtml(s.title),o.t=s.type.substr(0,1),o.delay=s.delay,o.onClose=s.onClose;var l=function(){for(var e=0,t=0,n=p,i=d,o=[],a=g.length-1;a>=0;a--){var l=g[a];if(s.replaceMessage&&a<g.length-1)l.addClass("killed");else{var r=parseInt(l[0].offsetHeight),m=parseInt(l[0].offsetWidth),h=o[l._positionY+l._positionX];C+r>window.innerHeight&&(h=p,t++,e=0);var C=n=h?0===e?h:h+u:p,y=i+t*(f+m);l.css(l._positionY,C+"px"),"center"==l._positionX?l.css("left",parseInt(window.innerWidth/2-m/2)+"px"):l.css(l._positionX,y+"px"),o[l._positionY+l._positionX]=C+r,c.maxCount>0&&g.length>c.maxCount&&0===a&&l.scope().kill(!0),e++}}},m=n(t)(o);m._positionY=s.positionY,m._positionX=s.positionX,m.addClass(s.type);var C=function(e){e=e.originalEvent||e,("click"===e.type||"opacity"===e.propertyName&&e.elapsedTime>=1)&&(o.onClose&&o.$apply(o.onClose(m)),m.remove(),g.splice(g.indexOf(m),1),o.$destroy(),l())};s.closeOnClick&&(m.addClass("clickable"),m.bind("click",C)),m.bind("webkitTransitionEnd oTransitionEnd otransitionend transitionend msTransitionEnd",C),angular.isNumber(s.delay)&&e(function(){m.addClass("killed")},s.delay),i("none"),angular.element(document.querySelector(s.container)).append(m);var v=-(parseInt(m[0].offsetHeight)+50);if(m.css(m._positionY,v+"px"),g.push(m),"center"==s.positionX){var k=parseInt(m[0].offsetWidth);m.css("left",parseInt(window.innerWidth/2-k/2)+"px")}e(function(){i("")}),o._templateElement=m,o.kill=function(t){t?(o.onClose&&o.$apply(o.onClose(o._templateElement)),g.splice(g.indexOf(o._templateElement),1),o._templateElement.remove(),o.$destroy(),e(l)):o._templateElement.addClass("killed")},e(l),h||(angular.element(r).bind("resize",function(t){e(l)}),h=!0),y.resolve(o)}).error(function(e){throw new Error("Template ("+s.template+") could not be loaded. "+e)}),y.promise};return C.primary=function(e){return this(e,"primary")},C.error=function(e){return this(e,"error")},C.success=function(e){return this(e,"success")},C.info=function(e){return this(e,"info")},C.warning=function(e){return this(e,"warning")},C.clearAll=function(){angular.forEach(g,function(e){e.addClass("killed")})},C}]}),angular.module("ui-notification").run(["$templateCache",function(e){e.put("angular-ui-notification.html",'<div class="ui-notification"><h3 ng-show="title" ng-bind-html="title"></h3><div class="message" ng-bind-html="message"></div></div>')}]);⏎ |
10 | 10 | var faradayApp = angular.module('faradayApp', ['ngRoute', 'selectionModel', 'ui.bootstrap', 'angularFileUpload', |
11 | 11 | 'filter', 'ngClipboard', 'ngCookies', 'cfp.hotkeys', 'chart.js', |
12 | 12 | 'ui.grid', 'ui.grid.selection', 'ui.grid.grouping', 'ngSanitize', |
13 | 'ui.grid.pagination', 'ui.grid.pinning', 'angularMoment']) | |
13 | 'ui.grid.pagination', 'ui.grid.pinning', 'angularMoment', 'ui-notification']) | |
14 | 14 | .constant("BASEURL", (function() { |
15 | 15 | var url = window.location.origin + "/"; |
16 | 16 | return url; |
35 | 35 | "unclassified" |
36 | 36 | ]; |
37 | 37 | return severities; |
38 | })()) | |
39 | .constant("STATUSES", (function() { | |
40 | var statuses = [ | |
41 | "opened", | |
42 | "closed", | |
43 | "re-opened", | |
44 | "risk-accepted" | |
45 | ]; | |
46 | return statuses; | |
38 | 47 | })()); |
39 | 48 | |
40 | 49 | faradayApp.config(['$routeProvider', 'ngClipProvider', function($routeProvider, ngClipProvider) { |
193 | 202 | title: 'Users | ' |
194 | 203 | }). |
195 | 204 | otherwise({ |
196 | templateUrl: 'scripts/commons/partials/home.html', | |
197 | controller: 'statusReportCtrl' | |
205 | templateUrl: 'scripts/commons/partials/home.html' | |
198 | 206 | }); |
199 | 207 | }]); |
200 | 208 |
45 | 45 | return deferred.promise; |
46 | 46 | }; |
47 | 47 | |
48 | attachmentsFact.getStubs = function(ws, vid, names) { | |
49 | var url = BASEURL + ws + "/" + vid, | |
50 | stubs = {}, | |
51 | deferred = $q.defer(); | |
52 | ||
53 | $http.get(url).success(function(result) { | |
54 | for(var attachment in result._attachments) { | |
55 | if(names.indexOf(attachment) >= 0) { | |
56 | stubs[attachment] = result._attachments[attachment]; | |
57 | } | |
58 | } | |
59 | deferred.resolve(stubs); | |
60 | }); | |
61 | ||
62 | return deferred.promise; | |
63 | }; | |
64 | ||
65 | 48 | return attachmentsFact; |
66 | 49 | }]); |
90 | 90 | <strong>Manage your licenses</strong> |
91 | 91 | </small> |
92 | 92 | </a> |
93 | <a href="#/help" class="ws-link item animated flipInX"> | |
94 | <i class="fa fa-question fa-4x icons-color-home"></i> | |
95 | <span class="ws-name">Help</span> | |
96 | <small> | |
97 | Having some questions?<br/> | |
98 | <strong>Get some help</strong> | |
99 | </small> | |
100 | </a> | |
93 | 101 | </div><!-- .ws-list --> |
94 | 102 | </div><!-- .reports --> |
95 | 103 | </div><!-- #reports-main --></div><!-- .right-main --> |
0 | // Faraday Penetration Test IDE | |
1 | // Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
2 | // See the file "doc/LICENSE" for the license information | |
3 | ||
4 | // "use strict"; // Elm? Where we"re going we don"t need Elm. | |
5 | ||
6 | // TODO: handle errors | |
7 | angular.module("faradayApp") | |
8 | .factory("ServerAPI", ["BASEURL", "$http", "$q", | |
9 | function(BASEURL, $http, $q) { | |
10 | var ServerAPI = {}; | |
11 | var APIURL = BASEURL + "_api/"; | |
12 | ||
13 | var createGetUrl = function(wsName, objectName) { | |
14 | var objectName = ((objectName) ? "/" + objectName : ""); | |
15 | var get_url = APIURL + "ws/" + wsName + objectName; | |
16 | return get_url; | |
17 | }; | |
18 | ||
19 | var createNewGetUrl = function(wsName, objId) { | |
20 | return APIURL + "ws/" + wsName + "/doc/" + objId; | |
21 | } | |
22 | ||
23 | var createPostUrl = function(wsName, objectId, rev) { | |
24 | if (rev === undefined) { | |
25 | return APIURL + "ws/" + wsName + "/doc/" + objectId; | |
26 | } | |
27 | else { | |
28 | return APIURL + "ws/" + wsName + "/doc/" + objectId + "?rev=" + rev; | |
29 | } | |
30 | }; | |
31 | ||
32 | var createDbUrl = function(wsName) { | |
33 | return APIURL + "ws/" + wsName; | |
34 | } | |
35 | ||
36 | var createDeleteUrl = createPostUrl; | |
37 | ||
38 | var serverComm = function(method, url, data) { | |
39 | var success = function (response) { | |
40 | return response; | |
41 | }; | |
42 | var error = function(err) { | |
43 | return $q.reject(err); | |
44 | }; | |
45 | ||
46 | // return a promise :) | |
47 | if (method === 'GET' || method === 'DELETE') { | |
48 | return $http({method: method, url: url, params: data}).then(success).catch(error); | |
49 | } else { | |
50 | return $http({method: method, url: url, data: data}).then(success).catch(error); | |
51 | } | |
52 | }; | |
53 | ||
54 | var get = function(url, data) { | |
55 | return serverComm("GET", url, data); | |
56 | }; | |
57 | ||
58 | var put = function(url, data, is_update) { | |
59 | // undefined is just evil... | |
60 | if (typeof is_update === "undefined") {var is_update = false;} | |
61 | if (is_update && !data._rev) { | |
62 | // ok, undefined, you win | |
63 | return get(url).then(function s(r) { | |
64 | data._rev = r.data._rev; | |
65 | return serverComm("PUT", url, data); | |
66 | }).catch(function e(r) {$q.reject(r)}); | |
67 | } | |
68 | return serverComm("PUT", url, data); | |
69 | }; | |
70 | ||
71 | ||
72 | // delete is a reserved keyword | |
73 | // just set rev_provided to false if you're deleting a database :) | |
74 | var _delete = function(url, rev_provided) { | |
75 | // never let undefined win | |
76 | ||
77 | if (typeof rev_provided === "undefined") {var rev_provided = false;} | |
78 | var deferred = $q.defer(); | |
79 | var data = {}; | |
80 | ||
81 | if (rev_provided === false) { | |
82 | get(url).then( | |
83 | function s(r) { | |
84 | data.rev = r.data._rev; | |
85 | return serverComm("DELETE", url, data).then( | |
86 | function(res) { | |
87 | deferred.resolve(res); | |
88 | }, function(err) { | |
89 | deferred.reject(err); | |
90 | } | |
91 | ); | |
92 | }, | |
93 | function e(r) { | |
94 | deferred.reject(r); | |
95 | }) | |
96 | } | |
97 | else{ | |
98 | return serverComm("DELETE", url, data); | |
99 | } | |
100 | ||
101 | return deferred.promise; | |
102 | }; | |
103 | ||
104 | var modHost = function(createOrUpdate, wsName, host) { | |
105 | if (typeof host.description === "undefined") {host.description = ""}; | |
106 | if (typeof host.owner === "undefined") {host.owner = ""}; | |
107 | if (typeof host.owned === "undefined") {host.owned = false}; | |
108 | if (typeof host.os === "undefined") {host.os = ""}; | |
109 | return createOrUpdate(wsName, host._id, host); | |
110 | } | |
111 | ||
112 | var modInterface = function(createOrUpdate, wsName, _interface) { | |
113 | if (typeof _interface.owned === "undefined") {_interface.owned = false}; | |
114 | if (typeof _interface.owner === "undefined") {_interface.owner = ""}; | |
115 | if (typeof _interface.os === "undefined") {_interface.os = ""}; return createOrUpdate(wsName, _interface._id, _interface); | |
116 | } | |
117 | ||
118 | var modService = function(createOrUpdate, wsName, service) { | |
119 | if (typeof service.owned === "undefined") {service.owned = false}; | |
120 | if (typeof service.owner === "undefined") {service.owner = ""}; | |
121 | if (typeof service.protocol === "undefined") {service.protocol = ""}; | |
122 | if (typeof service.status === "undefined") {service.status = ""}; | |
123 | if (typeof service.version === "undefined") {service.version = ""}; | |
124 | return createOrUpdate(wsName, service._id, service); | |
125 | } | |
126 | ||
127 | var modVuln = function(createOrUpdate, wsName, vuln) { | |
128 | if (typeof vuln.owner === "undefined") {vuln.owner = ""}; | |
129 | if (typeof vuln.description === "undefined") {vuln.description = ""}; | |
130 | if (typeof vuln.protocol === "undefined") {vuln.protocol = ""}; | |
131 | if (typeof vuln.status === "undefined") {vuln.status = ""}; | |
132 | if (typeof vuln.version === "undefined") {vuln.version = ""}; | |
133 | if (typeof vuln.confirmed === "undefined") {vuln.confirmed = false}; | |
134 | if (typeof vuln.data === "undefined") {vuln.data = ""}; | |
135 | if (typeof vuln.severity === "undefined") {vuln.severity = "info"}; | |
136 | if (typeof vuln.resolution === "undefined") {vuln.resolution = ""}; | |
137 | return createOrUpdate(wsName, vuln._id, vuln); | |
138 | } | |
139 | ||
140 | var modVulnWeb = function(createOrUpdate, wsName, vulnWeb) { | |
141 | if (typeof vulnWeb.owner === "undefined") {vuln.owner = ""}; | |
142 | if (typeof vuln.description === "undefined") {vuln.description = ""}; | |
143 | if (typeof vulnWeb.protocol === "undefined") {vuln.protocol = ""}; | |
144 | if (typeof vulnWeb.status === "undefined") {vuln.status = ""}; | |
145 | if (typeof vulnWeb.version === "undefined") {vuln.version = ""}; | |
146 | if (typeof vulnWeb.confirmed === "undefined") {vuln.confirmed = false}; | |
147 | if (typeof vulnWeb.data === "undefined") {vuln.data = ""}; | |
148 | if (typeof vulnWeb.severity === "undefined") {vuln.severity = "info"}; | |
149 | if (typeof vulnWeb.resolution === "undefined") {vuln.resolution = ""}; | |
150 | if (typeof vulnWeb.params === "undefined") {vuln.parmas = ""}; | |
151 | return createOrUpdate(wsName, vulnWeb._id, vulnWeb); | |
152 | } | |
153 | ||
154 | var modNote = function(createOrUpdate, wsName, note) { | |
155 | if (typeof note.owner === "undefined") {note.owner = ""}; | |
156 | if (typeof note.description === "undefined") {note.description = ""}; | |
157 | return createOrUpdate(weName, note._id, note); | |
158 | } | |
159 | ||
160 | var modCredential = function(createOrUpdate, wsName, credential) { | |
161 | if (typeof credential.owner === "undefined") {credential.owner = ""}; | |
162 | if (typeof credential.description === "undefined") {credential.description = ""}; | |
163 | return createOrUpdate(wsName, credential._id, credential); | |
164 | } | |
165 | ||
166 | var modCommand = function(createOrUpdate, wsName, command) { | |
167 | return createOrUpdate(wsName, command._id, command); | |
168 | } | |
169 | ||
170 | var createObject = function(wsName, id, data) { | |
171 | var postUrl = createPostUrl(wsName, id); | |
172 | return put(postUrl, data, false); | |
173 | } | |
174 | ||
175 | var updateObject = function(wsName, id, data) { | |
176 | var postUrl = createPostUrl(wsName, id); | |
177 | return put(postUrl, data, true); | |
178 | } | |
179 | ||
180 | var saveInServer = function(wsName, objectId, data) { | |
181 | var postUrl = createPostUrl(wsName, objectId); | |
182 | return put(postUrl, data, false); | |
183 | } | |
184 | ||
185 | var updateInServer = function(wsName, objectId, data) { | |
186 | var postUrl = createPostUrl(wsName, objectId); | |
187 | return put(postUrl, objectId, true); | |
188 | } | |
189 | ||
190 | ServerAPI.getHosts = function(wsName, data) { | |
191 | var url = createGetUrl(wsName, 'hosts'); | |
192 | return get(url, data); | |
193 | } | |
194 | ||
195 | ServerAPI.getVulns = function(wsName, data) { | |
196 | var getUrl = createGetUrl(wsName, 'vulns'); | |
197 | return get(getUrl, data); | |
198 | } | |
199 | ||
200 | ServerAPI.getInterfaces = function(wsName, data) { | |
201 | var getUrl = createGetUrl(wsName, 'interfaces'); | |
202 | return get(getUrl, data); | |
203 | } | |
204 | ||
205 | ServerAPI.getServices = function(wsName, data) { | |
206 | var getUrl = createGetUrl(wsName, 'services'); | |
207 | return get(getUrl, data); | |
208 | } | |
209 | ||
210 | ServerAPI.getNotes = function(wsName, data) { | |
211 | var getUrl = createGetUrl(wsName, 'notes'); | |
212 | return get(getUrl, data); | |
213 | } | |
214 | ||
215 | ServerAPI.getCredentials = function(wsName, data) { | |
216 | var getUrl = createGetUrl(wsName, 'credentials'); | |
217 | return get(getUrl, data); | |
218 | } | |
219 | ||
220 | ServerAPI.getCommands = function(wsName, data) { | |
221 | var getUrl = createGetUrl(wsName, 'commands'); | |
222 | return get(getUrl, data); | |
223 | } | |
224 | ||
225 | ServerAPI.getWorkspacesNames = function() { | |
226 | return get(APIURL + "ws"); | |
227 | } | |
228 | ||
229 | ServerAPI.getWorkspace = function(wsName) { | |
230 | var getUrl = createDbUrl(wsName); | |
231 | return get(getUrl); | |
232 | } | |
233 | ||
234 | ServerAPI.getWorkspaceSummary = function(wsName, confirmed) { | |
235 | ||
236 | var getUrl = createGetUrl(wsName, "summary"); | |
237 | var payload = {}; | |
238 | ||
239 | if (confirmed !== undefined) { | |
240 | payload.confirmed = confirmed; | |
241 | } | |
242 | ||
243 | return get(getUrl, payload); | |
244 | } | |
245 | ||
246 | ServerAPI.getObj = function(wsName, objID) { | |
247 | var getUrl = createNewGetUrl(wsName, objID) | |
248 | return get(getUrl); | |
249 | } | |
250 | ||
251 | var getCount = function(wsName, object) { | |
252 | var deferred = $q.defer(); | |
253 | ServerAPI.getWorkspaceSummary(wsName).then( | |
254 | function(response) { | |
255 | deferred.resolve(response.data.stats[object]); | |
256 | }, function(error) { | |
257 | deferred.reject(error); | |
258 | }) | |
259 | return deferred.promise; | |
260 | } | |
261 | ||
262 | ServerAPI.getHostCount = function(wsName) { | |
263 | return getCount(wsName, 'hosts'); | |
264 | } | |
265 | ||
266 | ServerAPI.getServiceCount = function(wsName) { | |
267 | return getCount(wsName, 'services'); | |
268 | } | |
269 | ||
270 | ServerAPI.getServicesBy = function(wsName, what) { | |
271 | var url = createGetUrl(wsName, 'services') + '/count'; | |
272 | return get(url, {"group_by": what}) | |
273 | } | |
274 | ||
275 | ServerAPI.getServicesByName = function(wsName) { | |
276 | return ServerAPI.getServicesBy(wsName, 'name'); | |
277 | } | |
278 | ||
279 | ServerAPI.getServicesByHost = function(wsName) { | |
280 | return ServerAPI.getServicesBy(wsName, 'hostid'); | |
281 | } | |
282 | ||
283 | ServerAPI.getVulnsBySeverity = function(wsName, confirmed) { | |
284 | ||
285 | var url = createGetUrl(wsName, 'vulns') + '/count'; | |
286 | var payload = {'group_by': 'severity'} | |
287 | ||
288 | if (confirmed !== undefined) { | |
289 | payload.confirmed = confirmed; | |
290 | } | |
291 | ||
292 | return get(url, payload) | |
293 | } | |
294 | ||
295 | ServerAPI.createHost = function(wsName, host) { | |
296 | return modHost(createObject, wsName, host); | |
297 | } | |
298 | ||
299 | ServerAPI.updateHost = function(wsName, host) { | |
300 | return modHost(updateObject, wsName, host); | |
301 | } | |
302 | ||
303 | ServerAPI.createInterface = function(wsName, _interface) { | |
304 | return modInterface(createObject, wsName, _interface); | |
305 | } | |
306 | ||
307 | ServerAPI.updateInterface = function(wsName, _interface) { | |
308 | return modInterface(updateObject, wsName, _interface); | |
309 | } | |
310 | ||
311 | ServerAPI.createService = function(wsName, service) { | |
312 | return modService(createObject, wsName, service); | |
313 | } | |
314 | ||
315 | ServerAPI.updateService = function(wsName, service) { | |
316 | return modService(updateObject, wsName, service); | |
317 | } | |
318 | ||
319 | ServerAPI.createVuln = function(wsName, vuln) { | |
320 | return modVuln(createObject, wsName, vuln) | |
321 | } | |
322 | ||
323 | ServerAPI.updateVuln = function(wsName, vuln) { | |
324 | return modVuln(updateObject, wsName, vuln); | |
325 | } | |
326 | ||
327 | ServerAPI.createVulnWeb = function(wsName, vulnWeb) { | |
328 | return modVulnWeb(createObject, wsName, vulnWeb); | |
329 | } | |
330 | ||
331 | ServerAPI.updateVulnWeb = function(wsName, vulnWeb) { | |
332 | return modVulnWeb(updateObject, wsName, vulnWeb); | |
333 | } | |
334 | ||
335 | ServerAPI.createNote = function(wsName, note) { | |
336 | return modNote(createObject, wsName, note); | |
337 | } | |
338 | ||
339 | ServerAPI.updateNote = function(wsName, note) { | |
340 | return modNote(updateObject, wsName, note); | |
341 | } | |
342 | ||
343 | ServerAPI.createCredential = function(wsName, credential) { | |
344 | return modCredential(createObject, wsName, credential); | |
345 | } | |
346 | ||
347 | ServerAPI.updateCredential = function(wsName, credential) { | |
348 | return modCredential(updateObject, wsName, credential); | |
349 | } | |
350 | ||
351 | ServerAPI.createCommand = function(wsName, command) { | |
352 | return modCommand(createObject, wsName, command); | |
353 | } | |
354 | ||
355 | ServerAPI.updateCommand = function(wsName, command) { | |
356 | return modCommand(updateObject, wsName, command); | |
357 | } | |
358 | ||
359 | ServerAPI.createDB = function(wsName) { | |
360 | var dbUrl = BASEURL + wsName; | |
361 | return put(dbUrl); | |
362 | } | |
363 | ||
364 | ServerAPI.deleteHost = function(wsName, hostId, rev) { | |
365 | var deleteUrl = createDeleteUrl(wsName, hostId, rev); | |
366 | if (typeof rev === "undefined") { | |
367 | return _delete(deleteUrl, false) | |
368 | } | |
369 | else { | |
370 | return _delete(deleteUrl, true); | |
371 | } | |
372 | } | |
373 | ||
374 | ServerAPI.deleteInterface = function(wsName, interfaceId, rev) { | |
375 | var deleteUrl = createDeleteUrl(wsName, interfaceId, rev); | |
376 | if (typeof rev === "undefined") { | |
377 | return _delete(deleteUrl, false) | |
378 | } | |
379 | else { | |
380 | return _delete(deleteUrl, true); | |
381 | } | |
382 | } | |
383 | ||
384 | ServerAPI.deleteService = function(wsName, serviceId, rev) { | |
385 | var deleteUrl = createDeleteUrl(wsName, serviceId, rev); | |
386 | if (typeof rev === "undefined") { | |
387 | return _delete(deleteUrl, false) | |
388 | } | |
389 | else { | |
390 | return _delete(deleteUrl, true); | |
391 | } | |
392 | } | |
393 | ||
394 | ServerAPI.deleteVuln = function(wsName, vulnId, rev) { | |
395 | var deleteUrl = createDeleteUrl(wsName, vulnId, rev); | |
396 | if (typeof rev === "undefined") { | |
397 | return _delete(deleteUrl, false) | |
398 | } | |
399 | else { | |
400 | return _delete(deleteUrl, true); | |
401 | } | |
402 | } | |
403 | ||
404 | ServerAPI.deleteNote = function(wsName, noteId, rev) { | |
405 | var deleteUrl = createDeleteUrl(wsName, noteId, rev); | |
406 | if (typeof rev === "undefined") { | |
407 | return _delete(deleteUrl, false) | |
408 | } | |
409 | else { | |
410 | return _delete(deleteUrl, true); | |
411 | } | |
412 | } | |
413 | ||
414 | ServerAPI.deleteCredential = function(wsName, credentialId, rev) { | |
415 | var deleteUrl = createDeleteUrl(wsName, credentialid, rev); | |
416 | if (typeof rev === "undefined") { | |
417 | return _delete(deleteUrl, false) | |
418 | } | |
419 | else { | |
420 | return _delete(deleteUrl, true); | |
421 | } | |
422 | } | |
423 | ||
424 | ServerAPI.deleteCommand = function(wsName, commandId, rev) { | |
425 | var deleteUrl = createDeleteUrl(wsName, commandId, rev); | |
426 | if (typeof rev === "undefined") { | |
427 | return _delete(deleteUrl, false) | |
428 | } | |
429 | else { | |
430 | return _delete(deleteUrl, true); | |
431 | } | |
432 | } | |
433 | ||
434 | ServerAPI.createWorkspace = function(wsName, data) { | |
435 | var dbUrl = createDbUrl(wsName); | |
436 | return put(dbUrl, data, false) | |
437 | } | |
438 | ||
439 | ServerAPI.updateWorkspace = function(workspace) { | |
440 | var putUrl = createDbUrl(workspace.name); | |
441 | return put(putUrl, workspace, true) | |
442 | } | |
443 | ||
444 | ServerAPI.deleteWorkspace = function(wsName) { | |
445 | var dbUrl = createDbUrl(wsName); | |
446 | return _delete(dbUrl, false); | |
447 | } | |
448 | ||
449 | return ServerAPI; | |
450 | }]); |
27 | 27 | object[prop] = ""; |
28 | 28 | } |
29 | 29 | if(prop === "date") object[prop] = parseDate(v["metadata"]["create_time"] * 1000); |
30 | if(prop === "creator") object[prop] = v["metadata"]["creator"]; | |
30 | 31 | if(prop === "web") { |
31 | 32 | if(v.type === "Vulnerability") { |
32 | 33 | object[prop] = false; |
55 | 56 | }; |
56 | 57 | |
57 | 58 | parseObject = function(object) { |
59 | if (object === null || object === undefined) return ""; | |
58 | 60 | var parsedData = ""; |
59 | 61 | if(object.length === undefined) { |
60 | 62 | for(key in object){ |
6 | 6 | var cweFact = {}; |
7 | 7 | cweFact.cweList = []; |
8 | 8 | |
9 | // XXX: this is still not using the server | |
9 | 10 | cweFact.get = function() { |
10 | 11 | var deferred = $q.defer(); |
11 | 12 | var cwe_url = BASEURL + 'cwe/_all_docs?include_docs=true'; |
0 | 0 | <article id='list' class='panel panel-default' ng-controller="commandsCtrl"> |
1 | 1 | <header> |
2 | 2 | <h2>Commands History |
3 | <span class="glyphicon glyphicon-info-sign" uib-tooltip="Shows current WS' executed commands"></span> | |
3 | <span class="glyphicon glyphicon-info-sign" uib-tooltip="Shows current WS executed commands"></span> | |
4 | 4 | </h2> |
5 | 5 | </header> |
6 | 6 | <div ng-if="commands.length == 0" class="alert alert-info alert-dismissible no-margin-bottom"> |
1 | 1 | <header> |
2 | 2 | <h2> |
3 | 3 | Workspace's worth |
4 | <span class="glyphicon glyphicon-info-sign" uib-tooltip="Total net worth of Workspace, according to current vulnerabilities' prices"></span> | |
4 | <span class="glyphicon glyphicon-info-sign" uib-tooltip="Total net worth of Workspace, according to current vulnerabilities prices"></span> | |
5 | 5 | </h2> |
6 | 6 | </header> |
7 | 7 | <div ng-if="workspaceWorth == 0" class="alert alert-info alert-dismissible no-margin-bottom"> |
2 | 2 | // See the file 'doc/LICENSE' for the license information |
3 | 3 | |
4 | 4 | angular.module('faradayApp') |
5 | .factory('dashboardSrv', ['BASEURL', 'SEVERITIES', '$cookies', '$q', '$http', '$interval', 'hostsManager', | |
6 | function(BASEURL, SEVERITIES, $cookies, $q, $http, $interval, hostsManager) { | |
5 | .factory('dashboardSrv', ['BASEURL', 'SEVERITIES', '$cookies', '$q', '$http', '$interval', 'hostsManager', 'ServerAPI', | |
6 | function(BASEURL, SEVERITIES, $cookies, $q, $http, $interval, hostsManager, ServerAPI) { | |
7 | 7 | var dashboardSrv = {}; |
8 | 8 | |
9 | 9 | dashboardSrv._getView = function(url) { |
86 | 86 | return deferred.promise; |
87 | 87 | }; |
88 | 88 | |
89 | // this is really not the count | |
90 | // does some weird grouping too | |
89 | 91 | dashboardSrv.getServicesCount = function(ws) { |
90 | var deferred = $q.defer(), | |
91 | url = BASEURL + "_api/ws/" + ws + "/services/count?group_by=name"; | |
92 | ||
93 | $http.get(url) | |
92 | var deferred = $q.defer(); | |
93 | ServerAPI.getServicesByName(ws) | |
94 | 94 | .then(function(res) { |
95 | 95 | var tmp =[]; |
96 | 96 | res.data.groups.sort(function(a, b) { |
159 | 159 | }; |
160 | 160 | |
161 | 161 | dashboardSrv.getVulnerabilitiesCount = function(ws) { |
162 | var deferred = $q.defer(), | |
163 | url = BASEURL + "_api/ws/" + ws + "/vulns/count?group_by=severity"; | |
164 | ||
165 | if(dashboardSrv.props['confirmed']) { | |
166 | url += "&confirmed=true"; | |
167 | } | |
168 | ||
169 | $http.get(url) | |
162 | var deferred = $q.defer(); | |
163 | ||
164 | var confirmed = undefined; | |
165 | ||
166 | if (dashboardSrv.props['confirmed']) { | |
167 | confirmed = true; | |
168 | } | |
169 | ||
170 | ServerAPI.getVulnsBySeverity(ws, confirmed) | |
170 | 171 | .then(function(res) { |
171 | 172 | var vs = {}; |
172 | 173 | res.data.groups.forEach(function(vuln) { |
182 | 183 | }; |
183 | 184 | |
184 | 185 | dashboardSrv.getObjectsCount = function(ws) { |
185 | var deferred = $q.defer(), | |
186 | url = BASEURL + "_api/ws/" + ws + "/summary"; | |
187 | ||
188 | if(dashboardSrv.props['confirmed']) { | |
189 | url += "?confirmed=true"; | |
190 | } | |
191 | ||
192 | $http.get(url) | |
186 | var deferred = $q.defer(); | |
187 | // Confirmed empty = All vulns | |
188 | var confirmed = undefined; | |
189 | ||
190 | if (dashboardSrv.props['confirmed']) { | |
191 | confirmed = true; | |
192 | } | |
193 | ||
194 | ServerAPI.getWorkspaceSummary(ws, confirmed) | |
193 | 195 | .then(function(res) { |
194 | 196 | delete res.data.stats["interfaces"]; |
195 | 197 | deferred.resolve(res.data.stats); |
202 | 204 | |
203 | 205 | dashboardSrv.getCommands = function(ws) { |
204 | 206 | var deferred = $q.defer(); |
205 | var url = BASEURL + "/" + ws + "/_design/commands/_view/list"; | |
206 | ||
207 | dashboardSrv._getView(url) | |
207 | ||
208 | ServerAPI.getCommands(ws) | |
208 | 209 | .then(function(res) { |
209 | 210 | var tmp = []; |
210 | res.forEach(function(cmd) { | |
211 | res.data.commands.forEach(function(cmd) { | |
211 | 212 | var _cmd = cmd.value; |
212 | _cmd["command"] = cmd.key; | |
213 | 213 | _cmd.user = _cmd.user || "unknown"; |
214 | 214 | _cmd.hostname = _cmd.hostname || "unknown"; |
215 | 215 | _cmd.ip = _cmd.ip || "0.0.0.0"; |
218 | 218 | } else if(_cmd.duration != undefined) { |
219 | 219 | _cmd.duration = _cmd.duration.toFixed(2) + "s"; |
220 | 220 | } |
221 | _cmd.date = _cmd.startdate * 1000; | |
221 | _cmd.date = _cmd.itime * 1000; | |
222 | _cmd.command = _cmd.command + ' ' + _cmd.params; | |
222 | 223 | tmp.push(_cmd); |
223 | 224 | }); |
224 | 225 | |
232 | 233 | |
233 | 234 | dashboardSrv.getHosts = function(ws) { |
234 | 235 | var deferred = $q.defer(); |
235 | var url = BASEURL + "_api/ws/" + ws + "/hosts"; | |
236 | $http.get(url) | |
236 | ServerAPI.getHosts(ws) | |
237 | 237 | .then(function(res) { |
238 | 238 | var tmp = []; |
239 | 239 | res.data.rows.forEach(function(host) { |
250 | 250 | |
251 | 251 | dashboardSrv.getHost = function(ws, host_id) { |
252 | 252 | var deferred = $q.defer(); |
253 | var url = BASEURL + "/" + ws + "/" + host_id; | |
254 | $http.get(url) | |
255 | .then(function(res) { | |
256 | deferred.resolve(res.data); | |
253 | ServerAPI.getHosts(ws, {'couchid': host_id}) | |
254 | .then(function(res) { | |
255 | if (res.rows == 1) { | |
256 | deferred.resolve(res.data.rows[0]); | |
257 | } else { | |
258 | deferred.reject("More than one object found by ID"); | |
259 | } | |
257 | 260 | }, function() { |
258 | 261 | deferred.reject(); |
259 | 262 | }); |
260 | 263 | return deferred.promise; |
261 | 264 | }; |
262 | 265 | |
266 | // XXX: still uses a CouchDB view | |
267 | // server hasn't implemented services/count?group_by=host | |
263 | 268 | dashboardSrv.getServicesByHost = function(ws, host_id) { |
264 | 269 | var deferred = $q.defer(); |
265 | 270 | var url = BASEURL + "/" + ws + "/_design/services/_view/byhost?key=\"" + host_id + "\""; |
291 | 296 | |
292 | 297 | dashboardSrv.getName = function(ws, id) { |
293 | 298 | var deferred = $q.defer(); |
294 | url = BASEURL + "/" + ws + "/" + id; | |
295 | ||
296 | $http.get(url).then(function(response){ | |
297 | res = response.data.name; | |
298 | deferred.resolve(res); | |
299 | }, function(){ | |
300 | deferred.reject(); | |
301 | }); | |
299 | ||
300 | ServerAPI.getObj(ws, id) | |
301 | .then(function(response){ | |
302 | res = response.data.name; | |
303 | deferred.resolve(res); | |
304 | }, function(){ | |
305 | deferred.reject(); | |
306 | }); | |
302 | 307 | |
303 | 308 | return deferred.promise; |
304 | 309 | }; |
242 | 242 | resolve: { |
243 | 243 | service: function() { |
244 | 244 | return $scope.selectedServices(); |
245 | }, | |
246 | services: function() { | |
247 | return $scope.services; | |
248 | } | |
245 | } | |
249 | 246 | } |
250 | 247 | }); |
251 | 248 |
6 | 6 | ['$scope', '$cookies', '$filter', '$location', '$route', '$routeParams', '$uibModal', 'hostsManager', 'workspacesFact', 'commonsFact', |
7 | 7 | function($scope, $cookies, $filter, $location, $route, $routeParams, $uibModal, hostsManager, workspacesFact, commonsFact) { |
8 | 8 | |
9 | init = function() { | |
9 | var init = function() { | |
10 | 10 | $scope.selectall_hosts = false; |
11 | 11 | // hosts list |
12 | 12 | $scope.hosts = []; |
68 | 68 | // load icons into object for HTML |
69 | 69 | // maybe this part should be directly in the view somehow |
70 | 70 | // or, even better, in a CSS file |
71 | oss = ["windows", "cisco", "router", "osx", "apple","linux", "unix"]; | |
71 | var oss = ["windows", "cisco", "router", "osx", "apple","linux", "unix"]; | |
72 | 72 | oss.forEach(function(os){ |
73 | 73 | if(host.os.toLowerCase().indexOf(os) != -1) { |
74 | 74 | host.icon = os; |
131 | 131 | }); |
132 | 132 | |
133 | 133 | if(selected.length == 0) { |
134 | $uibModal.open(config = { | |
134 | $uibModal.open({ | |
135 | 135 | templateUrl: 'scripts/commons/partials/modalKO.html', |
136 | 136 | controller: 'commonsModalKoCtrl', |
137 | 137 | size: 'sm', |
147 | 147 | message = selected.length + " hosts will be deleted"; |
148 | 148 | } |
149 | 149 | message = message.concat(" along with all of its children. This operation cannot be undone. Are you sure you want to proceed?"); |
150 | $uibModal.open(config = { | |
150 | $uibModal.open({ | |
151 | 151 | templateUrl: 'scripts/commons/partials/modalDelete.html', |
152 | 152 | controller: 'commonsModalDelete', |
153 | 153 | size: 'lg', |
170 | 170 | $scope.hosts.push(host); |
171 | 171 | $scope.loadIcons(); |
172 | 172 | }, function(message) { |
173 | $uibModal.open(config = { | |
173 | $uibModal.open({ | |
174 | 174 | templateUrl: 'scripts/commons/partials/modalKO.html', |
175 | 175 | controller: 'commonsModalKoCtrl', |
176 | 176 | size: 'sm', |
192 | 192 | }); |
193 | 193 | |
194 | 194 | modal.result.then(function(data) { |
195 | hostdata = data[0]; | |
196 | interfaceData = data[1]; | |
195 | var hostdata = data[0]; | |
196 | var interfaceData = data[1]; | |
197 | 197 | $scope.insert(hostdata, interfaceData); |
198 | 198 | }); |
199 | 199 | }; |
227 | 227 | $scope.update($scope.selectedHosts()[0], hostdata, interfaceData); |
228 | 228 | }); |
229 | 229 | } else { |
230 | $uibModal.open(config = { | |
230 | $uibModal.open({ | |
231 | 231 | templateUrl: 'scripts/commons/partials/modalKO.html', |
232 | 232 | controller: 'commonsModalKoCtrl', |
233 | 233 | size: 'sm', |
286 | 286 | }; |
287 | 287 | |
288 | 288 | $scope.selectedHosts = function() { |
289 | selected = []; | |
289 | var selected = []; | |
290 | 290 | $scope.hosts.forEach(function(host) { |
291 | 291 | if(host.selected === true) { |
292 | 292 | selected.push(host); |
2 | 2 | // See the file 'doc/LICENSE' for the license information |
3 | 3 | |
4 | 4 | angular.module('faradayApp') |
5 | .factory('Host', ['BASEURL', '$http', function(BASEURL, $http) { | |
5 | .factory('Host', ['BASEURL', 'ServerAPI', function(BASEURL, ServerAPI) { | |
6 | 6 | Host = function(data){ |
7 | 7 | if(data) { |
8 | 8 | this.set(data); |
20 | 20 | data.type = "Host"; |
21 | 21 | angular.extend(this, data); |
22 | 22 | }, |
23 | ||
23 | 24 | delete: function(ws) { |
24 | var self = this, | |
25 | bulk = {docs:[]}; | |
26 | return $http.get(BASEURL + ws + '/_all_docs?startkey="' + self._id + '"&endkey="' + self._id + '.z"').then(function(all) { | |
27 | all.data.rows.forEach(function(row) { | |
28 | bulk.docs.push({ | |
29 | "_id": row.id, | |
30 | "_rev": row.value.rev, | |
31 | "_deleted": true | |
25 | return ServerAPI.deleteHost(ws, this._id, this.rev); | |
26 | }, | |
27 | ||
28 | update: function(data, interfaceData, ws) { | |
29 | var self = this; | |
30 | return ServerAPI.updateHost(ws, data) | |
31 | .then(function(hostData) { | |
32 | ServerAPI.updateInterface(ws, interfaceData) | |
33 | .then(function(intData) { | |
34 | self._rev = hostData.rev; | |
35 | interfaceData._rev = intData.rev; | |
36 | }); | |
37 | }); | |
38 | }, | |
39 | ||
40 | save: function(ws, interfaceData) { | |
41 | var self = this; | |
42 | return ServerAPI.createHost(ws, self). | |
43 | then(function(host_data) { | |
44 | ServerAPI.createInterface(ws, interfaceData). | |
45 | then(function(interface_data) { | |
46 | self._rev = host_data.rev; | |
47 | interfaceData._rev = interface_data.rev; | |
32 | 48 | }); |
33 | 49 | }); |
50 | }, | |
34 | 51 | |
35 | return $http.post(BASEURL + ws + "/_bulk_docs", JSON.stringify(bulk)); | |
36 | }); | |
37 | }, | |
38 | update: function(data, interfaceData, ws) { | |
39 | var self = this; | |
40 | bulk = {docs:[data,interfaceData]}; | |
41 | return $http.post(BASEURL + ws + "/_bulk_docs", JSON.stringify(bulk)).success(function(data){ | |
42 | if(data.id == self._id){ | |
43 | self._rev = data.rev; | |
44 | } else { | |
45 | interfaceData._rev = data.rev; | |
46 | } | |
47 | }); | |
48 | }, | |
49 | save: function(ws, interfaceData) { | |
50 | var self = this; | |
51 | bulk = {docs:[self,interfaceData]}; | |
52 | return $http.put(BASEURL + ws + "/" + self._id, JSON.stringify(self)).success(function(host_data){ | |
53 | $http.put(BASEURL + ws + "/" + interfaceData._id, JSON.stringify(interfaceData)).success(function(interface_data) { | |
54 | self._rev = host_data.rev; | |
55 | interfaceData._rev = interface_data.rev; | |
56 | }); | |
57 | }); | |
58 | } | |
59 | 52 | } |
60 | ||
61 | 53 | return Host; |
62 | 54 | }]); |
2 | 2 | // See the file 'doc/LICENSE' for the license information |
3 | 3 | |
4 | 4 | angular.module('faradayApp') |
5 | .factory('hostsManager', ['BASEURL', '$http', '$q', 'Host', 'commonsFact', function(BASEURL, $http, $q, Host, commonsFact) { | |
5 | .factory('hostsManager', ['BASEURL', '$http', '$q', 'ServerAPI', 'Host', 'commonsFact', function(BASEURL, $http, $q, ServerAPI, Host, commonsFact) { | |
6 | 6 | var hostsManager = {}; |
7 | 7 | |
8 | 8 | hostsManager._objects = {}; |
26 | 26 | |
27 | 27 | hostsManager._load = function(id, ws, deferred) { |
28 | 28 | var self = this; |
29 | $http.get(BASEURL + ws + '/' + id) | |
30 | .success(function(data){ | |
31 | var host = self._get(data._id, data); | |
32 | deferred.resolve(host); | |
33 | }) | |
34 | .error(function(){ | |
29 | ServerAPI.getHosts(ws, {couchid: id}).then( | |
30 | function(response){ | |
31 | if (response.data.rows.length === 1) { | |
32 | var host = self._get(response.data.rows[0].id, response.data.rows[0].value); | |
33 | deferred.resolve(host); | |
34 | } else { | |
35 | deferred.reject(); | |
36 | } | |
37 | }, function(){ | |
35 | 38 | deferred.reject(); |
36 | 39 | }); |
37 | 40 | }; |
52 | 55 | |
53 | 56 | hostsManager.getHosts = function(ws, page, page_size, filter, sort, sort_direction) { |
54 | 57 | var deferred = $q.defer(); |
55 | var url = BASEURL + '_api/ws/' + ws + '/hosts'; | |
56 | ||
57 | url = commonsFact.addPresentationParams(url, page, page_size, filter, sort, sort_direction); | |
58 | ||
59 | $http.get(url) | |
58 | ||
59 | options = {page: page, page_size: page_size, sort:sort, sort_dir: sort_direction} | |
60 | for( var property in filter ) { | |
61 | if (filter.hasOwnProperty(property)) { | |
62 | options[property] = filter[property]; | |
63 | } | |
64 | }; | |
65 | ServerAPI.getHosts(ws, options) | |
60 | 66 | .then(function(response) { |
61 | 67 | var result = { hosts: [], total: 0 }; |
62 | 68 | response.data.rows.forEach(function(host_data) { |
63 | 69 | host = new Host(host_data.value); |
64 | 70 | result.hosts.push(host); |
65 | 71 | }); |
66 | ||
67 | 72 | result.total = response.data.total_rows; |
68 | ||
69 | 73 | deferred.resolve(result); |
70 | 74 | }, function(response) { |
71 | 75 | deferred.reject(); |
153 | 157 | hostsManager.getAllInterfaces = function(ws) { |
154 | 158 | var deferred = $q.defer(), |
155 | 159 | self = this; |
156 | ||
157 | var url = BASEURL + ws + '/_design/interfaces/_view/interfaces'; | |
158 | ||
159 | $http.get(url) | |
160 | .success(function(ints) { | |
160 | ServerAPI.getInterfaces(ws) | |
161 | .then(function(ints) { | |
161 | 162 | var interfaces = []; |
162 | ||
163 | ints.rows.forEach(function(interf) { | |
163 | ints.data.interfaces.forEach(function(interf) { | |
164 | 164 | interfaces.push(interf.value); |
165 | 165 | }); |
166 | 166 | |
167 | 167 | deferred.resolve(interfaces); |
168 | }) | |
169 | .error(function() { | |
168 | }, function() { | |
170 | 169 | deferred.reject("Unable to retrieve Interfaces"); |
171 | 170 | }); |
172 | 171 | |
173 | 172 | return deferred.promise; |
174 | 173 | }; |
175 | 174 | |
175 | var get_count = function(ws, object) { | |
176 | var deferred = $q.defer(); | |
177 | ServerAPI.getWorkspaceSummary(ws).then( | |
178 | function(summary) { | |
179 | deferred.resolve(summary[object]) | |
180 | }, function() { | |
181 | deferred.reject("Unable to get vulnerability count") | |
182 | } | |
183 | ) | |
184 | return deferred.promise; | |
185 | }; | |
186 | ||
187 | ||
176 | 188 | hostsManager.getAllVulnsCount = function(ws) { |
177 | var deferred = $q.defer(); | |
178 | ||
179 | var url = BASEURL + ws + '/_design/vulns/_view/byhost?group=true'; | |
180 | ||
181 | $http.get(url) | |
182 | .success(function(vulns) { | |
183 | deferred.resolve(vulns.rows); | |
184 | }) | |
185 | .error(function() { | |
186 | deferred.reject("Unable to load Vulnerabilities"); | |
187 | }); | |
188 | ||
189 | return deferred.promise; | |
189 | return this.get_count(ws, 'total_vulns'); | |
190 | 190 | }; |
191 | 191 | |
192 | 192 | hostsManager.getAllServicesCount = function(ws) { |
193 | var deferred = $q.defer(); | |
194 | ||
195 | var url = BASEURL + ws + '/_design/hosts/_view/byservicecount?group=true'; | |
196 | ||
197 | $http.get(url) | |
198 | .success(function(allrows) { | |
199 | var services = {}; | |
200 | ||
201 | allrows.rows.forEach(function(service) { | |
202 | services[service.key] = service.value; | |
203 | }); | |
204 | ||
205 | deferred.resolve(services); | |
206 | }) | |
207 | .error(function() { | |
208 | deferred.reject("Unable to load Services"); | |
209 | }); | |
210 | ||
211 | return deferred.promise; | |
212 | }; | |
213 | ||
193 | return this.get_count(ws, 'services'); | |
194 | }; | |
195 | ||
196 | // XXX: THIS STILL USES VIEWS | |
214 | 197 | hostsManager.getInterfaces = function(ws, id) { |
215 | 198 | var deferred = $q.defer(), |
216 | 199 | self = this; |
3 | 3 | |
4 | 4 | angular.module('faradayApp') |
5 | 5 | .controller('licensesCtrl', |
6 | ['$scope', '$cookies', '$filter', '$location', '$q', '$route', '$routeParams', '$uibModal', 'commonsFact', 'licensesManager', | |
7 | function($scope, $cookies, $filter, $location, $q, $route, $routeParams, $uibModal, commonsFact, licensesManager) { | |
8 | ||
9 | $scope.currentPage; | |
10 | $scope.DBExists = false; | |
11 | $scope.expression; | |
6 | ['$scope', '$filter', '$q', '$uibModal', 'commonsFact', 'licensesManager', | |
7 | function($scope, $filter, $q, $uibModal, commonsFact, licensesManager) { | |
8 | ||
9 | $scope.db_exists = false; | |
10 | $scope.expiration_month = false; | |
12 | 11 | $scope.licenses = []; |
13 | 12 | $scope.loaded_licenses = false; |
14 | $scope.expiration_month = false; | |
15 | $scope.newCurrentPage; | |
16 | $scope.newPageSize; | |
17 | $scope.pageSize; | |
18 | 13 | $scope.reverse; |
19 | 14 | $scope.search; |
20 | $scope.searchParams; | |
21 | 15 | $scope.selectall_licenses; |
22 | $scope.sortField; | |
16 | $scope.sort_field; | |
23 | 17 | $scope.store; |
24 | 18 | |
25 | init = function() { | |
26 | $scope.store = "https://appstore.faradaysec.com/"; | |
27 | ||
28 | // table stuff | |
29 | $scope.selectall_licenses = false; | |
30 | $scope.sortField = "end"; | |
31 | $scope.reverse = true; | |
32 | ||
33 | // pagination stuff | |
34 | $scope.pageSize = 100; | |
35 | $scope.currentPage = 0; | |
36 | $scope.newCurrentPage = 0; | |
37 | if(!isNaN(parseInt($cookies.pageSize))) $scope.pageSize = parseInt($cookies.pageSize); | |
38 | $scope.newPageSize = $scope.pageSize; | |
39 | ||
40 | // current search | |
41 | $scope.search = $routeParams.search; | |
42 | $scope.searchParams = ""; | |
43 | $scope.expression = {}; | |
44 | if($scope.search != "" && $scope.search != undefined && $scope.search.indexOf("=") > -1) { | |
45 | // search expression for filter | |
46 | $scope.expression = commonsFact.decodeSearch($scope.search); | |
47 | // search params for search field, which shouldn't be used for filtering | |
48 | $scope.searchParams = commonsFact.stringSearch($scope.expression); | |
19 | var init = function() { | |
20 | $scope.store = "https://appstore.faradaysec.com/search/?q="; | |
21 | ||
22 | // table stuff | |
23 | $scope.selectall_licenses = false; | |
24 | $scope.sort_field = "end"; | |
25 | $scope.reverse = true; | |
26 | ||
27 | licensesManager.DBExists() | |
28 | .then(function(exists) { | |
29 | if(!exists) { | |
30 | $uibModal.open({ | |
31 | templateUrl: 'scripts/licenses/partials/modalCreateDB.html', | |
32 | controller: 'licensesModalCreateDB', | |
33 | size: 'lg' | |
34 | }).result.then(function() { | |
35 | $scope.db_exists = true; | |
36 | }, function(message) { | |
37 | // The user didn't create the DB, do nothing! | |
38 | }); | |
39 | } else { | |
40 | $scope.db_exists = true; | |
41 | licensesManager.get() | |
42 | .then(function() { | |
43 | $scope.licenses = licensesManager.licenses; | |
44 | $scope.loaded_licenses = true; | |
45 | ||
46 | $scope.expiration_month = $scope.isExpirationMonth($scope.licenses); | |
47 | }); | |
48 | } | |
49 | }, function(message) { | |
50 | commonsFact.errorDialog(message); | |
51 | }); | |
52 | ||
53 | $scope.$watch(function() { | |
54 | return licensesManager.licenses; | |
55 | }, function(newVal, oldVal) { | |
56 | $scope.licenses = licensesManager.licenses; | |
57 | $scope.loaded_licenses = true; | |
58 | $scope.expiration_month = $scope.isExpirationMonth(newVal); | |
59 | }, true); | |
60 | }; | |
61 | ||
62 | $scope.almostExpired = function(end) { | |
63 | var end_date = new Date(end), | |
64 | today = new Date(); | |
65 | return (end_date.getMonth() == today.getMonth()) && (end_date.getYear() == today.getYear()); | |
66 | }; | |
67 | ||
68 | $scope.isExpirationMonth = function(licenses) { | |
69 | return licenses.some(function(elem, index, array) { | |
70 | return $scope.almostExpired(elem.end); | |
71 | }); | |
72 | }; | |
73 | ||
74 | $scope.remove = function(ids) { | |
75 | var confirmations = []; | |
76 | ||
77 | ids.forEach(function(id) { | |
78 | var deferred = $q.defer(); | |
79 | ||
80 | licensesManager.delete(id, $scope.workspace) | |
81 | .then(function(resp) { | |
82 | deferred.resolve(resp); | |
83 | }, function(message) { | |
84 | deferred.reject(message); | |
85 | }); | |
86 | ||
87 | confirmations.push(deferred); | |
88 | }); | |
89 | ||
90 | return $q.all(confirmations); | |
91 | }; | |
92 | ||
93 | $scope.delete = function() { | |
94 | var selected = $scope.selectedLicenses(); | |
95 | ||
96 | if(selected.length == 0) { | |
97 | $uibModal.open({ | |
98 | templateUrl: 'scripts/commons/partials/modalKO.html', | |
99 | controller: 'commonsModalKoCtrl', | |
100 | size: 'sm', | |
101 | resolve: { | |
102 | msg: function() { | |
103 | return 'No licenses were selected to delete'; | |
104 | } | |
105 | } | |
106 | }); | |
107 | } else { | |
108 | var message = "A license will be deleted"; | |
109 | if(selected.length > 1) { | |
110 | message = selected.length + " licenses will be deleted"; | |
111 | } | |
112 | message = message.concat(". This operation cannot be undone. Are you sure you want to proceed?"); | |
113 | $uibModal.open({ | |
114 | templateUrl: 'scripts/commons/partials/modalDelete.html', | |
115 | controller: 'commonsModalDelete', | |
116 | size: 'lg', | |
117 | resolve: { | |
118 | msg: function() { | |
119 | return message; | |
120 | } | |
121 | } | |
122 | }).result.then(function() { | |
123 | $scope.remove(selected); | |
124 | }, function() { | |
125 | //dismised, do nothing | |
126 | }); | |
127 | } | |
128 | }; | |
129 | ||
130 | $scope.insert = function(data) { | |
131 | licensesManager.create(data) | |
132 | .catch(function(message) { | |
133 | commonsFact.errorDialog(message); | |
134 | }); | |
135 | }; | |
136 | ||
137 | $scope.new = function() { | |
138 | var modal = $uibModal.open({ | |
139 | templateUrl: 'scripts/licenses/partials/modalNew.html', | |
140 | controller: 'licensesModalNew', | |
141 | size: 'lg', | |
142 | resolve: {} | |
143 | }); | |
144 | ||
145 | modal.result | |
146 | .then(function(data) { | |
147 | $scope.insert(data); | |
148 | }); | |
149 | }; | |
150 | ||
151 | $scope.update = function(license, data) { | |
152 | licensesManager.update(license, data) | |
153 | .catch(function(message) { | |
154 | commonsFact.errorDialog(message); | |
155 | }); | |
156 | }; | |
157 | ||
158 | $scope.edit = function() { | |
159 | if($scope.selectedLicenses().length == 1) { | |
160 | var license = $scope.selectedLicenses()[0]; | |
161 | var modal = $uibModal.open({ | |
162 | templateUrl: 'scripts/licenses/partials/modalEdit.html', | |
163 | controller: 'licensesModalEdit', | |
164 | size: 'lg', | |
165 | resolve: { | |
166 | license: function() { | |
167 | return license; | |
168 | } | |
169 | } | |
170 | }); | |
171 | ||
172 | modal.result.then(function(data) { | |
173 | $scope.update(license, data); | |
174 | }); | |
175 | } else { | |
176 | commonsFact.errorDialog("No licenses were selected to edit."); | |
177 | } | |
178 | }; | |
179 | ||
180 | $scope.selectedLicenses = function() { | |
181 | var selected = []; | |
182 | ||
183 | $filter('filter')($scope.licenses, $scope.search).forEach(function(license) { | |
184 | if(license.selected === true) { | |
185 | selected.push(license); | |
186 | } | |
187 | }); | |
188 | ||
189 | return selected; | |
190 | }; | |
191 | ||
192 | $scope.checkAll = function() { | |
193 | $scope.selectall_licenses = !$scope.selectall_licenses; | |
194 | ||
195 | tmp_licenses = $filter('filter')($scope.licenses, $scope.search); | |
196 | tmp_licenses.forEach(function(license) { | |
197 | license.selected = $scope.selectall_licenses; | |
198 | }); | |
199 | }; | |
200 | ||
201 | // toggles sort field and order | |
202 | $scope.toggleSort = function(field) { | |
203 | $scope.toggleSortField(field); | |
204 | $scope.toggleReverse(); | |
205 | }; | |
206 | ||
207 | // toggles column sort field | |
208 | $scope.toggleSortField = function(field) { | |
209 | $scope.sort_field = field; | |
210 | }; | |
211 | ||
212 | // toggle column sort order | |
213 | $scope.toggleReverse = function() { | |
214 | $scope.reverse = !$scope.reverse; | |
49 | 215 | } |
50 | 216 | |
51 | licensesManager.DBExists() | |
52 | .then(function(exists) { | |
53 | if(!exists) { | |
54 | $uibModal.open(config = { | |
55 | templateUrl: 'scripts/licenses/partials/modalCreateDB.html', | |
56 | controller: 'licensesModalCreateDB', | |
57 | size: 'lg' | |
58 | }).result.then(function() { | |
59 | $scope.DBExists = true; | |
60 | }, function(message) { | |
61 | // The user didn't create the DB, do nothing! | |
62 | }); | |
63 | } else { | |
64 | $scope.DBExists = true; | |
65 | licensesManager.get() | |
66 | .then(function() { | |
67 | $scope.licenses = licensesManager.licenses; | |
68 | $scope.loaded_licenses = true; | |
69 | ||
70 | $scope.expiration_month = $scope.isExpirationMonth($scope.licenses); | |
71 | }); | |
72 | } | |
73 | }, function(message) { | |
74 | commonsFact.errorDialog(message); | |
75 | }); | |
76 | ||
77 | $scope.$watch(function() { | |
78 | return licensesManager.licenses; | |
79 | }, function(newVal, oldVal) { | |
80 | $scope.licenses = licensesManager.licenses; | |
81 | $scope.loaded_licenses = true; | |
82 | $scope.expiration_month = $scope.isExpirationMonth(newVal); | |
83 | }, true); | |
84 | }; | |
85 | ||
86 | $scope.almostExpired = function(end) { | |
87 | var end_date = new Date(end), | |
88 | today = new Date(); | |
89 | return (end_date.getMonth() == today.getMonth()) && (end_date.getYear() == today.getYear()); | |
90 | }; | |
91 | ||
92 | $scope.isExpirationMonth = function(licenses) { | |
93 | return licenses.some(function(elem, index, array) { | |
94 | return $scope.almostExpired(elem.end); | |
95 | }); | |
96 | }; | |
97 | ||
98 | // changes the URL according to search params | |
99 | $scope.searchFor = function(search, params) { | |
100 | var url = "/licenses"; | |
101 | ||
102 | if(search && params != "" && params != undefined) { | |
103 | url += "/search/" + commonsFact.encodeSearch(params); | |
104 | } | |
105 | ||
106 | $location.path(url); | |
107 | }; | |
108 | ||
109 | $scope.go = function() { | |
110 | $scope.pageSize = $scope.newPageSize; | |
111 | $cookies.pageSize = $scope.pageSize; | |
112 | $scope.currentPage = 0; | |
113 | if($scope.newCurrentPage <= parseInt($scope.licenses.length/$scope.pageSize) | |
114 | && $scope.newCurrentPage > -1 && !isNaN(parseInt($scope.newCurrentPage))) { | |
115 | $scope.currentPage = $scope.newCurrentPage; | |
116 | } | |
117 | }; | |
118 | ||
119 | $scope.remove = function(ids) { | |
120 | var confirmations = []; | |
121 | ||
122 | ids.forEach(function(id) { | |
123 | var deferred = $q.defer(); | |
124 | ||
125 | licensesManager.delete(id, $scope.workspace) | |
126 | .then(function(resp) { | |
127 | deferred.resolve(resp); | |
128 | }, function(message) { | |
129 | deferred.reject(message); | |
130 | }); | |
131 | ||
132 | confirmations.push(deferred); | |
133 | }); | |
134 | ||
135 | return $q.all(confirmations); | |
136 | }; | |
137 | ||
138 | $scope.delete = function() { | |
139 | var selected = $scope.selectedLicenses(); | |
140 | ||
141 | if(selected.length == 0) { | |
142 | $uibModal.open(config = { | |
143 | templateUrl: 'scripts/commons/partials/modalKO.html', | |
144 | controller: 'commonsModalKoCtrl', | |
145 | size: 'sm', | |
146 | resolve: { | |
147 | msg: function() { | |
148 | return 'No licenses were selected to delete'; | |
149 | } | |
150 | } | |
151 | }); | |
152 | } else { | |
153 | var message = "A license will be deleted"; | |
154 | if(selected.length > 1) { | |
155 | message = selected.length + " licenses will be deleted"; | |
156 | } | |
157 | message = message.concat(". This operation cannot be undone. Are you sure you want to proceed?"); | |
158 | $uibModal.open(config = { | |
159 | templateUrl: 'scripts/commons/partials/modalDelete.html', | |
160 | controller: 'commonsModalDelete', | |
161 | size: 'lg', | |
162 | resolve: { | |
163 | msg: function() { | |
164 | return message; | |
165 | } | |
166 | } | |
167 | }).result.then(function() { | |
168 | $scope.remove(selected); | |
169 | }, function() { | |
170 | //dismised, do nothing | |
171 | }); | |
172 | } | |
173 | }; | |
174 | ||
175 | $scope.insert = function(data) { | |
176 | licensesManager.create(data) | |
177 | .catch(function(message) { | |
178 | commonsFact.errorDialog(message); | |
179 | }); | |
180 | }; | |
181 | ||
182 | $scope.new = function() { | |
183 | var modal = $uibModal.open({ | |
184 | templateUrl: 'scripts/licenses/partials/modalNew.html', | |
185 | controller: 'licensesModalNew', | |
186 | size: 'lg', | |
187 | resolve: {} | |
188 | }); | |
189 | ||
190 | modal.result | |
191 | .then(function(data) { | |
192 | $scope.insert(data); | |
193 | }); | |
194 | }; | |
195 | ||
196 | $scope.update = function(license, data) { | |
197 | licensesManager.update(license, data) | |
198 | .catch(function(message) { | |
199 | commonsFact.errorDialog(message); | |
200 | }); | |
201 | }; | |
202 | ||
203 | $scope.edit = function() { | |
204 | if($scope.selectedLicenses().length == 1) { | |
205 | var license = $scope.selectedLicenses()[0]; | |
206 | var modal = $uibModal.open({ | |
207 | templateUrl: 'scripts/licenses/partials/modalEdit.html', | |
208 | controller: 'licensesModalEdit', | |
209 | size: 'lg', | |
210 | resolve: { | |
211 | license: function() { | |
212 | return license; | |
213 | } | |
214 | } | |
215 | }); | |
216 | ||
217 | modal.result.then(function(data) { | |
218 | $scope.update(license, data); | |
219 | }); | |
220 | } else { | |
221 | commonsFact.errorDialog("No licenses were selected to edit."); | |
222 | } | |
223 | }; | |
224 | ||
225 | $scope.selectedLicenses = function() { | |
226 | var selected = []; | |
227 | ||
228 | $scope.filter($scope.licenses).forEach(function(license) { | |
229 | if(license.selected === true) { | |
230 | selected.push(license); | |
231 | } | |
232 | }); | |
233 | ||
234 | return selected; | |
235 | }; | |
236 | ||
237 | $scope.checkAll = function() { | |
238 | $scope.selectall_licenses = !$scope.selectall_licenses; | |
239 | ||
240 | tmp_licenses = $scope.filter($scope.licenses); | |
241 | tmp_licenses.forEach(function(license) { | |
242 | license.selected = $scope.selectall_licenses; | |
243 | }); | |
244 | }; | |
245 | ||
246 | // toggles sort field and order | |
247 | $scope.toggleSort = function(field) { | |
248 | $scope.toggleSortField(field); | |
249 | $scope.toggleReverse(); | |
250 | }; | |
251 | ||
252 | // toggles column sort field | |
253 | $scope.toggleSortField = function(field) { | |
254 | $scope.sortField = field; | |
255 | }; | |
256 | ||
257 | // toggle column sort order | |
258 | $scope.toggleReverse = function() { | |
259 | $scope.reverse = !$scope.reverse; | |
260 | } | |
261 | ||
262 | $scope.filter = function(data) { | |
263 | var tmp_data = $filter('orderBy')(data, $scope.sortField, $scope.reverse); | |
264 | tmp_data = $filter('filter')(tmp_data, $scope.expression); | |
265 | tmp_data = tmp_data.splice($scope.pageSize * $scope.currentPage, $scope.pageSize); | |
266 | ||
267 | return tmp_data; | |
268 | }; | |
269 | ||
270 | init(); | |
217 | init(); | |
271 | 218 | }]); |
8 | 8 | |
9 | 9 | $scope.message; |
10 | 10 | |
11 | init = function() { | |
11 | var init = function() { | |
12 | 12 | $scope.message = "It looks like your Faraday installation is missing "+ |
13 | 13 | "the Licenses database. Would you like to create it now?"; |
14 | 14 | }; |
3 | 3 | |
4 | 4 | angular.module('faradayApp') |
5 | 5 | .controller('licensesModalEdit', |
6 | ['$scope', '$modalInstance', 'license', | |
7 | function($scope, $modalInstance, license) { | |
6 | ['$scope', '$modalInstance', 'License', 'license', | |
7 | function($scope, $modalInstance, License, license) { | |
8 | 8 | |
9 | 9 | $scope.data; |
10 | 10 | $scope.openedStart; |
11 | 11 | $scope.openedEnd; |
12 | 12 | |
13 | init = function() { | |
13 | var init = function() { | |
14 | 14 | $scope.data = new License; |
15 | 15 | $scope.data.set(license); |
16 | 16 |
3 | 3 | |
4 | 4 | angular.module('faradayApp') |
5 | 5 | .controller('licensesModalNew', |
6 | ['$scope', '$modalInstance', 'licensesManager', | |
7 | function($scope, $modalInstance, licensesManager) { | |
6 | ['$scope', '$modalInstance', 'License', 'licensesManager', | |
7 | function($scope, $modalInstance, License, licensesManager) { | |
8 | 8 | |
9 | 9 | $scope.data; |
10 | 10 | $scope.other = false; |
11 | 11 | $scope.other_product; |
12 | 12 | $scope.products; |
13 | 13 | |
14 | init = function() { | |
14 | var init = function() { | |
15 | 15 | $scope.data = new License; |
16 | 16 | |
17 | 17 | $scope.products = licensesManager.products; |
2 | 2 | <!-- See the file 'doc/LICENSE' for the license information --> |
3 | 3 | |
4 | 4 | <section id="main" class="seccion clearfix"> |
5 | <div class="right-main" ng-show="DBExists"><div id="reports-main" class="fila clearfix"> | |
5 | <div class="right-main" ng-show="db_exists"><div id="reports-main" class="fila clearfix"> | |
6 | 6 | <h2 class="ws-label"> |
7 | 7 | <span id="ws-name" title="Licenses">Licenses ({{licenses.length}})</span><!-- WS name --> |
8 | 8 | <button id="delete" type="button" class="btn btn-default" title="Delete selected licenses" ng-click="delete()"> |
20 | 20 | </h2><!-- .ws-label --> |
21 | 21 | <div class="reports col-md-12 col-sm-12 col-xs-12"> |
22 | 22 | <div class="col-md-6 col-sm-3 col-xs-11"> |
23 | <form role="form" ng-submit="searchFor(true, searchParams)"> | |
24 | <div class="form-group"> | |
25 | <div class="input-group input-group-sm"> | |
26 | <span class="input-group-addon glyphicon-btn glyphicon glyphicon-remove" ng-click="searchFor(false, '')" ng-if="search"></span> | |
27 | <input type="text" class="form-control" id="filter-by" | |
28 | placeholder="enter keywords" ng-change="currentPage = 0" ng-model="searchParams" /> | |
29 | <span class="input-group-addon glyphicon-btn" ng-click="searchFor(true, searchParams)"> | |
30 | <i class="fa fa-search" ng-if="loaded_licenses"></i> | |
31 | <i class="fa fa-refresh fa-spin" ng-if="!loaded_licenses"></i> | |
32 | </span> | |
33 | </div> | |
23 | <div class="form-group"> | |
24 | <div class="input-group"> | |
25 | <input type="text" class="form-control input-sm" ng-model="search" placeholder="enter keywords" /> | |
26 | <span class="input-group-btn"> | |
27 | <button class="btn btn-sm" type="button"> | |
28 | <i class="fa fa-search"></i> | |
29 | </button> | |
30 | </span> | |
34 | 31 | </div> |
35 | </form> | |
32 | </div> | |
36 | 33 | </div> |
37 | 34 | <div class="reports col-md-12 col-sm-12 cols-xs-12" ng-if="expiration_month == true"> |
38 | 35 | <h4><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> The licenses marked in dark red are about to expire</h4> |
60 | 57 | </tr> |
61 | 58 | </thead> |
62 | 59 | <tbody> |
63 | <tr ng-repeat="license in filtered = (licenses | filter:expression) | orderBy:sortField:reverse | startFrom:currentPage*pageSize | limitTo:pageSize" | |
60 | <tr ng-repeat="license in licenses | filter:search | orderBy:sort_field:reverse" | |
64 | 61 | selection-model selection-model-type="checkbox" |
65 | 62 | selection-model-mode="multiple-additive" |
66 | 63 | selection-model-selected-class="multi-selected" |
73 | 70 | <td>{{license.start | date:'MM/dd/yyyy'}}</td> |
74 | 71 | <td>{{license.end|date:'MM/dd/yyyy'}}</td> |
75 | 72 | <td><i class="fa fa-history" aria-hidden="true"> |
76 | <a ng-href="{{store}}search/?q={{license.product}}" target="_blank">renew</a> | |
73 | <a ng-href="{{store}}{{license.product}}" target="_blank">renew</a> | |
77 | 74 | </i></td> |
78 | 75 | </tr> |
79 | 76 | </tbody> |
80 | 77 | </table><!-- #licenses --> |
81 | <div class="showPagination"> | |
82 | <div class="form-group"> | |
83 | <ul class="pagination"> | |
84 | <li><a ng-hide="currentPage <= 0" ng-click="currentPage = currentPage - 1"><span aria-hidden="true">«</span><span class="sr-only">Previous</span></a></li> | |
85 | <li><a>{{currentPage}}/{{ ((filtered.length / pageSize) | integer)}}</a></li> | |
86 | <li><a ng-hide="currentPage >= ((filtered.length / pageSize) | integer)" ng-click="currentPage = currentPage + 1"><span aria-hidden="true">»</span><span class="sr-only">Next</span></a></li> | |
87 | </ul> | |
88 | <form name="goToPage" id="goToPageStatus"> | |
89 | <div class="col-md-2"> | |
90 | <input type="number" min="0" max="{{ (filtered.length / pageSize) | integer }}" class="form-control" ng-model="newCurrentPage" placeholder="Go to page"/> | |
91 | </div> | |
92 | <button class="btn btn-default" ng-click="go()">GO</button> | |
93 | <input type="number" min="0" class="form-control vuln_per_page" ng-model=newPageSize placeholder="Number page" /> | |
94 | </form> | |
95 | </div> | |
96 | </div><!-- .showPagination --> | |
97 | 78 | </div><!-- .reports --> |
98 | 79 | </div><!-- #reports-main --></div><!-- .right-main --> |
99 | 80 | </section><!-- #main --> |
4 | 4 | angular.module('faradayApp') |
5 | 5 | .factory('License', ['BASEURL', 'configSrv', '$http', '$q', |
6 | 6 | function(BASEURL, configSrv, $http, $q) { |
7 | License = function(data) { | |
7 | function License(data) { | |
8 | 8 | var now = new Date(), |
9 | 9 | date = now.getTime() / 1000.0; |
10 | 10 |
2 | 2 | // See the file 'doc/LICENSE' for the license information |
3 | 3 | |
4 | 4 | angular.module('faradayApp') |
5 | .controller('navigationCtrl', ['$scope', '$http', '$route', '$routeParams', '$cookies', '$location', '$interval', '$uibModal', 'configSrv', 'workspacesFact', | |
6 | function($scope, $http, $route, $routeParams, $cookies, $location, $interval, $uibModal, configSrv, workspacesFact) { | |
5 | .controller('navigationCtrl', ['$scope', '$http', '$route', '$routeParams', '$cookies', '$location', '$interval', '$uibModal', 'configSrv', 'workspacesFact', 'Notification', | |
6 | function($scope, $http, $route, $routeParams, $cookies, $location, $interval, $uibModal, configSrv, workspacesFact, Notification) { | |
7 | 7 | |
8 | $scope.workspace = ""; | |
8 | $scope.workspace = "asd"; | |
9 | 9 | $scope.component = ""; |
10 | 10 | var componentsNeedsWS = ["dashboard","status","hosts"]; |
11 | 11 | |
12 | $scope.checkCwe = function() { | |
13 | $http.get("https://www.faradaysec.com/scripts/updatedb.php?version=" + configSrv.faraday_version).then(function() { | |
12 | $scope.checkNews = function() { | |
13 | $http.get('https://www.faradaysec.com/scripts/updatedb.php?version=' + configSrv.faraday_version).then(function(response) { | |
14 | try{ | |
15 | response.data['news'].forEach(function(element) { | |
16 | ||
17 | var childScope = $scope.$new(); | |
18 | childScope.url = element['url']; | |
19 | ||
20 | Notification.info({ | |
21 | message: element['description'], | |
22 | title: 'x', | |
23 | scope: childScope, | |
24 | delay: 'ALWAYS', | |
25 | templateUrl: 'scripts/navigation/partials/notification.html'}); | |
26 | }, this); | |
27 | } | |
28 | catch(error){ | |
29 | console.log("Can't connect to faradaysec.com"); | |
30 | } | |
31 | ||
14 | 32 | }, function() { |
15 | console.log("CWE database couldn't be updated"); | |
33 | console.log("Can't connect to faradaysec.com"); | |
16 | 34 | }); |
17 | 35 | }; |
18 | 36 | |
19 | 37 | configSrv.promise.then(function() { |
20 | var timer = $interval($scope.checkCwe, 43200000); | |
21 | $scope.checkCwe(); | |
38 | var timer = $interval($scope.checkNews, 43200000); | |
39 | $scope.checkNews(); | |
22 | 40 | }); |
23 | 41 | |
24 | 42 | $scope.$on('$destroy', function() { |
0 | <div class="ui-notification"> | |
1 | <h3 ng-show="title" ng-bind="title" align="right"></h3> | |
2 | <div class="message"> | |
3 | <a ng-href="{{url}}" target="_blank">{{message}}</a> | |
4 | </div> | |
5 | </div>⏎ |
3 | 3 | |
4 | 4 | angular.module('faradayApp') |
5 | 5 | .controller('serviceModalEdit', |
6 | ['$scope', '$modalInstance', '$routeParams', 'services','service', 'servicesManager', 'commonsFact', | |
7 | function($scope, $modalInstance, $routeParams, services, service, servicesManager, commons) { | |
6 | ['$scope', '$modalInstance', '$routeParams','service', 'servicesManager', 'commonsFact', | |
7 | function($scope, $modalInstance, $routeParams, service, servicesManager, commons) { | |
8 | 8 | |
9 | 9 | init = function() { |
10 | 10 | // current Workspace |
2 | 2 | // See the file 'doc/LICENSE' for the license information |
3 | 3 | |
4 | 4 | angular.module('faradayApp') |
5 | .factory('Service', ['BASEURL', '$http', function(BASEURL, $http) { | |
5 | .factory('Service', ['BASEURL', 'ServerAPI', function(BASEURL, ServerAPI) { | |
6 | 6 | Service = function(data) { |
7 | 7 | if(data) { |
8 | 8 | this.set(data); |
41 | 41 | this.ports = data.ports[0]; |
42 | 42 | } |
43 | 43 | }, |
44 | ||
44 | 45 | delete: function(ws) { |
45 | var self = this, | |
46 | bulk = {docs:[]}; | |
47 | return $http.get(BASEURL + ws + '/_all_docs?startkey="' + self._id + '"&endkey="' + self._id + '.z"').then(function(all) { | |
48 | all.data.rows.forEach(function(row) { | |
49 | bulk.docs.push({ | |
50 | "_id": row.id, | |
51 | "_rev": row.value.rev, | |
52 | "_deleted": true | |
53 | }); | |
54 | }); | |
46 | return ServerAPI.deleteService(ws, this._id, this._rev); | |
47 | }, | |
55 | 48 | |
56 | return $http.post(BASEURL + ws + "/_bulk_docs", JSON.stringify(bulk)); | |
57 | }); | |
58 | }, | |
59 | 49 | update: function(data, ws) { |
60 | 50 | angular.extend(this, data); |
61 | 51 | var self = this; |
64 | 54 | self.ports = [self.ports]; |
65 | 55 | } |
66 | 56 | |
67 | return (self._save(ws, self).success(function(data) { | |
68 | self._rev = data.rev; | |
57 | return (self._save(ws, self, true).then( | |
58 | function(data) { | |
59 | self._rev = data.rev; | |
69 | 60 | })); |
70 | 61 | }, |
71 | 62 | save: function(ws) { |
75 | 66 | self.ports = [self.ports]; |
76 | 67 | } |
77 | 68 | |
78 | return (self._save(ws, self).success(function(data){ | |
79 | self._rev = data.rev; | |
69 | return (self._save(ws, self, false).then( | |
70 | function(data){ | |
71 | self._rev = data.rev; | |
80 | 72 | })); |
81 | 73 | }, |
82 | _save: function(ws, data) { | |
83 | var doc = {}; | |
84 | var url = BASEURL + ws + '/' + this._id | |
74 | _save: function(ws, data, isUpdate) { | |
75 | if (typeof isUpdate === 'undefined') {isUpdate = false}; | |
76 | doc = {}; | |
85 | 77 | for (property in data) { |
86 | 78 | if (this.saved_properties.indexOf(property) != -1) { |
87 | 79 | doc[property] = data[property]; |
88 | 80 | } |
89 | }; | |
90 | return $http.put(url, doc); | |
81 | } | |
82 | ||
83 | if (isUpdate) { | |
84 | return ServerAPI.updateService(ws, doc); | |
85 | } else { | |
86 | return ServerAPI.createService(ws, doc); | |
87 | } | |
91 | 88 | } |
92 | 89 | } |
93 | 90 |
2 | 2 | // See the file 'doc/LICENSE' for the license information |
3 | 3 | |
4 | 4 | angular.module('faradayApp') |
5 | .factory('servicesManager', ['BASEURL', '$http', '$q', 'Service', function(BASEURL, $http, $q, Service) { | |
5 | .factory('servicesManager', ['BASEURL', '$http', '$q', 'Service', 'ServerAPI', | |
6 | function(BASEURL, $http, $q, Service, ServerAPI) { | |
6 | 7 | var servicesManager = {}; |
7 | 8 | |
8 | 9 | servicesManager._objects = {}; |
25 | 26 | |
26 | 27 | servicesManager._load = function(id, ws, deferred) { |
27 | 28 | var self = this; |
28 | $http.get(BASEURL + '/' + ws + '/' + id) | |
29 | .success(function(data){ | |
30 | var service = self._get(data._id, data); | |
31 | deferred.resolve(service); | |
29 | ServerAPI.getServices(ws, {'couchid': id}). | |
30 | then(function(response) { | |
31 | if (response.data.services.length == 1) { | |
32 | var service_data = response.data.services[0].value; | |
33 | var service_id = service_data._id; | |
34 | var service = self._get(service_id, service_data); | |
35 | deferred.resolve(service); | |
36 | } else { | |
37 | deferred.reject("More than one object found by ID"); | |
38 | } | |
39 | }, function(error) { | |
40 | deferred.reject(error); | |
32 | 41 | }) |
33 | .error(function(){ | |
34 | deferred.reject(); | |
35 | }); | |
36 | } | |
42 | }; | |
37 | 43 | |
38 | 44 | servicesManager.getService = function(id, ws, force_reload) { |
39 | 45 | var deferred = $q.defer(); |
53 | 59 | var self = this; |
54 | 60 | this._objects = {}; |
55 | 61 | |
56 | $http.get(BASEURL + '/' + ws + '/_design/services/_view/services') | |
57 | .success(function(servicesArray) { | |
62 | ServerAPI.getServices(ws) | |
63 | .then(function(servicesArray) { | |
58 | 64 | var services = []; |
59 | servicesArray.rows.forEach(function(serviceData) { | |
65 | servicesArray.data.services.forEach(function(serviceData) { | |
60 | 66 | var service = self._get(serviceData.value._id, serviceData.value); |
61 | 67 | services.push(service); |
62 | 68 | }); |
63 | 69 | deferred.resolve(services); |
64 | }) | |
65 | .error(function(){ | |
70 | }, function(){ | |
66 | 71 | deferred.reject(); |
67 | 72 | }) |
68 | 73 | return deferred.promise; |
69 | 74 | } |
70 | 75 | |
76 | // XXX: this still uses couch | |
77 | // host_id is the couch host_id, but the server allows grouping | |
78 | // by server ID D: D: D: D: | |
71 | 79 | servicesManager.getServicesByHost = function(ws, host_id) { |
72 | 80 | var deferred = $q.defer(); |
73 | 81 | var url = BASEURL + "/" + ws + "/_design/services/_view/byhost?key=\"" + host_id + "\""; |
107 | 115 | var deferred = $q.defer(); |
108 | 116 | var promises = []; |
109 | 117 | services.forEach(function(service) { |
110 | var url = BASEURL + "_api/ws/" + ws + "/services?couchid=" + service._id; | |
111 | promises.push($http.get(url)); | |
118 | promises.push(ServerAPI.getServices(ws, {'couchid': service._id})); | |
112 | 119 | }); |
120 | ||
113 | 121 | $q.all(promises).then(function(services){ |
114 | 122 | var result = {}; |
115 | 123 | services.forEach(function(service) { |
2 | 2 | // See the file 'doc/LICENSE' for the license information |
3 | 3 | |
4 | 4 | angular.module('faradayApp') |
5 | .controller('modalEditCtrl', ['$modalInstance', 'EASEOFRESOLUTION', 'commonsFact', 'severities', 'vuln', 'cweFact', | |
6 | function($modalInstance, EASEOFRESOLUTION, commons, severities, vuln, cweFact) { | |
5 | .controller('modalEditCtrl', ['$modalInstance', 'EASEOFRESOLUTION', 'STATUSES', 'commonsFact', 'severities', 'vuln', 'cweFact', | |
6 | function($modalInstance, EASEOFRESOLUTION, STATUSES, commons, severities, vuln, cweFact) { | |
7 | 7 | |
8 | 8 | var vm = this; |
9 | 9 | |
22 | 22 | init = function() { |
23 | 23 | vm.easeofresolution = EASEOFRESOLUTION; |
24 | 24 | vm.severities = severities; |
25 | vm.statuses = STATUSES; | |
25 | 26 | vm.new_ref = ""; |
26 | 27 | vm.icons = {}; |
27 | 28 | |
57 | 58 | query: "", |
58 | 59 | request: "", |
59 | 60 | response: "", |
60 | website: "" | |
61 | website: "", | |
62 | status: "opened", | |
61 | 63 | }; |
62 | 64 | |
63 | 65 | vm.vuln = angular.copy(vuln); |
5 | 5 | .controller('statusReportCtrl', |
6 | 6 | ['$scope', '$filter', '$routeParams', |
7 | 7 | '$location', '$uibModal', '$cookies', '$q', '$window', 'BASEURL', |
8 | 'SEVERITIES', 'EASEOFRESOLUTION', 'hostsManager', 'commonsFact', | |
8 | 'SEVERITIES', 'EASEOFRESOLUTION', 'STATUSES', 'hostsManager', 'commonsFact', | |
9 | 9 | 'vulnsManager', 'workspacesFact', 'csvService', 'uiGridConstants', |
10 | 10 | function($scope, $filter, $routeParams, |
11 | 11 | $location, $uibModal, $cookies, $q, $window, BASEURL, |
12 | SEVERITIES, EASEOFRESOLUTION, hostsManager, commonsFact, | |
12 | SEVERITIES, EASEOFRESOLUTION, STATUSES, hostsManager, commonsFact, | |
13 | 13 | vulnsManager, workspacesFact, csvService, uiGridConstants) { |
14 | 14 | $scope.baseurl; |
15 | 15 | $scope.columns; |
40 | 40 | sortDirection: null |
41 | 41 | }; |
42 | 42 | |
43 | init = function() { | |
43 | var init = function() { | |
44 | 44 | $scope.baseurl = BASEURL; |
45 | 45 | $scope.severities = SEVERITIES; |
46 | 46 | $scope.easeofresolution = EASEOFRESOLUTION; |
67 | 67 | }; |
68 | 68 | $scope.gridOptions.columnDefs = []; |
69 | 69 | |
70 | if ($cookies.get('pageSize') !== undefined) { | |
71 | paginationOptions.pageSize = parseInt($cookies.get('pageSize')); | |
72 | $scope.gridOptions.paginationPageSize = paginationOptions.pageSize; | |
70 | var storedPageSize = parseInt($cookies.get('pageSize')); | |
71 | if ( storedPageSize && storedPageSize > 0 ) { | |
72 | paginationOptions.pageSize = storedPageSize; | |
73 | $scope.gridOptions.paginationPageSize = storedPageSize; | |
73 | 74 | } |
74 | 75 | |
75 | 76 | if($cookies.get('confirmed') === 'true') { |
172 | 173 | "pname": false, |
173 | 174 | "query": false, |
174 | 175 | "response": false, |
175 | "web": false | |
176 | "web": false, | |
177 | "creator": false | |
176 | 178 | }; |
177 | 179 | |
178 | 180 | // created object for columns cookie columns |
278 | 280 | visible: $scope.columns["easeofresolution"] |
279 | 281 | }); |
280 | 282 | $scope.gridOptions.columnDefs.push({ name : 'status', |
281 | cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/defaultcolumn.html', | |
282 | headerCellTemplate: header, | |
283 | cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/statuscolumn.html', | |
284 | headerCellTemplate: header, | |
285 | width: '100', | |
283 | 286 | visible: $scope.columns["status"] |
284 | 287 | }); |
285 | 288 | $scope.gridOptions.columnDefs.push({ name : 'website', |
344 | 347 | width: '80', |
345 | 348 | visible: $scope.columns["web"] |
346 | 349 | }); |
350 | $scope.gridOptions.columnDefs.push({ name : 'metadata.creator', | |
351 | displayName : "creator", | |
352 | cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/creatorcolumn.html', | |
353 | headerCellTemplate: header, | |
354 | width: '100', | |
355 | visible: $scope.columns["creator"] | |
356 | }); | |
347 | 357 | }; |
348 | 358 | |
349 | 359 | |
350 | 360 | var groupByColumn = function() { |
351 | 361 | for (var i = 0; i < $scope.gridOptions.columnDefs.length; i++) { |
352 | 362 | var column = $scope.gridOptions.columnDefs[i]; |
353 | if ( column.name == $scope.propertyGroupBy && $scope.columns[column.name] == true) { | |
363 | var colname = column.displayName !== undefined ? column.displayName : column.name; | |
364 | if ( colname == $scope.propertyGroupBy && $scope.columns[colname] == true) { | |
354 | 365 | column.grouping = { groupPriority: 0 }; |
355 | paginationOptions.sortColumn = column.name; | |
366 | paginationOptions.sortColumn = colname; | |
356 | 367 | paginationOptions.sortDirection = 'asc'; |
357 | 368 | } |
358 | 369 | } |
468 | 479 | $scope.toggleFilter = function() { |
469 | 480 | $scope.confirmed = !$scope.confirmed; |
470 | 481 | $cookies.put('confirmed', $scope.confirmed); |
471 | console.log($scope.confirmed); | |
472 | 482 | loadVulns(); |
473 | 483 | }; |
474 | 484 | |
475 | showMessage = function(msg) { | |
485 | var showMessage = function(msg) { | |
476 | 486 | var modal = $uibModal.open({ |
477 | 487 | templateUrl: 'scripts/commons/partials/modalKO.html', |
478 | 488 | controller: 'commonsModalKoCtrl', |
508 | 518 | _delete([vuln]); |
509 | 519 | }; |
510 | 520 | |
511 | _delete = function(vulns) { | |
521 | var _delete = function(vulns) { | |
512 | 522 | if(vulns.length > 0) { |
513 | 523 | var modal = $uibModal.open({ |
514 | 524 | templateUrl: 'scripts/commons/partials/modalDelete.html', |
540 | 550 | _toggleConfirm([vuln], confirm); |
541 | 551 | }; |
542 | 552 | |
543 | _toggleConfirm = function(vulns, confirm) { | |
553 | var _toggleConfirm = function(vulns, confirm) { | |
544 | 554 | var toggleConfirm = {'confirmed': !confirm}, |
545 | 555 | deferred = $q.defer(), |
546 | 556 | promises = []; |
565 | 575 | _edit([vuln]); |
566 | 576 | }; |
567 | 577 | |
568 | _edit = function(vulns) { | |
578 | var _edit = function(vulns) { | |
569 | 579 | if (vulns.length == 1) { |
570 | 580 | var modal = $uibModal.open({ |
571 | 581 | templateUrl: 'scripts/statusReport/partials/modalEdit.html', |
636 | 646 | 'Enter the new severity:', |
637 | 647 | 'severity', |
638 | 648 | {options: SEVERITIES}); |
649 | }; | |
650 | ||
651 | $scope.editStatus = function() { | |
652 | editProperty( | |
653 | 'scripts/commons/partials/editOptions.html', | |
654 | 'commonsModalEditOptions', | |
655 | 'Enter the new status:', | |
656 | 'status', | |
657 | {options: STATUSES}); | |
639 | 658 | }; |
640 | 659 | |
641 | 660 | $scope.editEaseofresolution = function() { |
795 | 814 | */ |
796 | 815 | }; |
797 | 816 | |
798 | loadVulns = function() { | |
817 | var loadVulns = function() { | |
799 | 818 | delete searchFilter.confirmed; |
800 | 819 | if ($scope.confirmed) |
801 | 820 | searchFilter.confirmed = true; |
814 | 833 | // if it is larger than our biggest page size |
815 | 834 | if ($scope.gridOptions.totalItems > paginationOptions.defaultPageSizes[paginationOptions.defaultPageSizes.length - 1]) { |
816 | 835 | $scope.gridOptions.paginationPageSizes = paginationOptions.defaultPageSizes.concat([$scope.gridOptions.totalItems]); |
836 | // sadly, this will load the vuln list again because it fires a paginationChanged event | |
837 | if ($scope.gridOptions.paginationPageSize != $scope.gridOptions.totalItems) $scope.gridOptions.paginationPageSize = $scope.gridOptions.totalItems; | |
817 | 838 | } |
818 | 839 | }); |
819 | 840 | }; |
889 | 910 | |
890 | 911 | $scope.serviceSearch = function(srvStr) { |
891 | 912 | //TODO: this is horrible |
892 | srvName = srvStr.split(') ')[1]; | |
913 | var srvName = srvStr.split(') ')[1]; | |
893 | 914 | return $scope.encodeUrl(srvName); |
894 | 915 | } |
895 | 916 |
23 | 23 | </div> |
24 | 24 | </div> |
25 | 25 | <div class="form-group"> |
26 | <div class="col-md-4 severities"> | |
26 | <div class="col-md-3 severities"> | |
27 | 27 | <h5>Severity</h5> |
28 | 28 | <button type="button" class="btn btn-default dropdown-toggle color-{{modal.data.severity}}" name="severity" data-toggle="dropdown" title="Change severity" ng-class="{'button-error': modal.data.severity === undefined}"> |
29 | 29 | {{modal.data.severity || 'Edit severity'}} <span class="caret" style="color:#000"></span> |
32 | 32 | <li ng-repeat="s in modal.severities"><a href="" class="ws color-{{s}}" ng-click="modal.data.severity=s">{{s}}</a></li> |
33 | 33 | </ul><!-- WS navigation --> |
34 | 34 | </div> |
35 | <div class="col-md-4"> | |
35 | <div class="col-md-3"> | |
36 | 36 | <h5>Ease of Resolution</h5> |
37 | 37 | <select class="form-control" ng-model="modal.data.easeofresolution" ng-options="e as e for e in modal.easeofresolution"> |
38 | 38 | <option value=""></option> |
39 | 39 | </select> |
40 | 40 | </div> |
41 | <div class="col-md-4"> | |
41 | <div class="col-md-3"> | |
42 | <h5>Status</h5> | |
43 | <select class="form-control" ng-model="modal.data.status" ng-options="e as e for e in modal.statuses"> | |
44 | <option value=""></option> | |
45 | </select> | |
46 | </div> | |
47 | <div class="col-md-3"> | |
42 | 48 | <h5>Confirmed</h5> |
43 | 49 | <input type="checkbox" id="confirmed" ng-model="modal.data.confirmed" /> |
44 | 50 | <span class="normal-size">Confirmed</span> |
24 | 24 | <li ng-repeat="ws in workspaces"><a href="#/status/ws/{{ws}}" class="ws" >{{ws}}</a></li> |
25 | 25 | </ul><!-- WS navigation --> |
26 | 26 | </div><!-- #ws-control --> |
27 | ||
27 | ||
28 | 28 | <div class="button-control col-md-6 col-sm-6 col-xs-12"> |
29 | 29 | <button id="delete" type="button" class="btn btn-default" title="Delete selected items" ng-click="delete()"> |
30 | <span class="glyphicon glyphicon-trash"></span> | |
30 | <span class="glyphicon glyphicon-trash"></span> | |
31 | 31 | Delete |
32 | 32 | </button> |
33 | 33 | <div id="merge" class="btn-group btn-small-margin"> |
34 | 34 | <button type="button" class="btn btn-default" title="Edit selected vulns" ng-click="edit()" ng-disabled="getCurrentSelection().length != 1"> |
35 | <span class="glyphicon glyphicon-pencil"></span> | |
35 | <span class="glyphicon glyphicon-pencil"></span> | |
36 | 36 | Edit |
37 | 37 | </button> |
38 | 38 | <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" title="Actions" ng-hide="getCurrentSelection().length < 2"> |
47 | 47 | <li><a class="ws" ng-click="editEaseofresolution()">Edit ease of resolution</a></li> |
48 | 48 | <li><a class="ws" ng-click="editReferences()">Add references</a></li> |
49 | 49 | <li><a class="ws" ng-click="editImpact()">Edit impact</a></li> |
50 | <li><a class="ws" ng-click="editStatus()">Edit status</a></li> | |
50 | 51 | <li><a class="ws" ng-click="editConfirm()">Confirm/Change to false positive</a></li> |
51 | 52 | <li ng-show="vulnWebSelected" role="separator" class="divider"></li> |
52 | 53 | <li ng-show="vulnWebSelected"><a class="ws" ng-click="editString('method')">Edit method</a></li> |
97 | 98 | </span> |
98 | 99 | </div> |
99 | 100 | </div> |
100 | </form> | |
101 | </form> | |
101 | 102 | </div> |
102 | 103 | <div class="col-md-12 col-sm-9 col-xs-12"> |
103 | 104 | <h4><span class="label label-default" title="Add columns">Add columns</span></h4> |
0 | <div ng-if="row.entity._id != undefined"> | |
1 | <div ng-if="!col.grouping || col.grouping.groupPriority === undefined || col.grouping.groupPriority === null || ( row.groupHeader && col.grouping.groupPriority === row.treeLevel )" class="ui-grid-cell-contents white-space" uib-tooltip="{{COL_FIELD}}"> | |
2 | <a href='{{grid.appScope.hash}}/search/creator={{grid.appScope.encodeUrl(row.entity.metadata.creator)}}'>{{COL_FIELD CUSTOM_FILTERS}}</a> | |
3 | </div> | |
4 | </div> | |
5 | <div ng-if="row.groupHeader && col.grouping.groupPriority !== undefined" class="ui-grid-cell-contents white-space">{{COL_FIELD.split("(")[0] !== " " ? COL_FIELD : "EMPTY" + COL_FIELD}}</div>⏎ |
0 | <div ng-if="row.entity._id != undefined"><div ng-if="!col.grouping || col.grouping.groupPriority === undefined || col.grouping.groupPriority === null || ( row.groupHeader && col.grouping.groupPriority === row.treeLevel )" class="ui-grid-cell-contents text-center"><a href="#/status/ws/{{grid.appScope.workspace}}/search/status={{COL_FIELD}}\"><span class="label vuln fondo-{{COL_FIELD}}">{{COL_FIELD | uppercase}}</span></a></div></div><div ng-if="row.groupHeader && col.grouping.groupPriority !== undefined"><span class="label vuln fondo-{{COL_FIELD}}">{{COL_FIELD | uppercase}}</span></div>⏎ |
18 | 18 | }); |
19 | 19 | hostsManager.getAllInterfaces(workspace).then(function(interfaces) { |
20 | 20 | interfaces.forEach(function(interf) { |
21 | if (hosts_dict.hasOwnProperty(interf.parent)) { | |
22 | hosts_dict[interf.parent].hostnames = hosts_dict[interf.parent].hostnames.concat(interf.hostnames); | |
21 | host_id = interf._id.split(".")[0]; | |
22 | if (hosts_dict.hasOwnProperty(host_id)) { | |
23 | hosts_dict[host_id].hostnames = hosts_dict[host_id].hostnames.concat(interf.hostnames); | |
23 | 24 | } |
24 | 25 | }); |
25 | 26 | }, function(err) {deferred.reject(err)}); |
26 | 27 | servicesManager.getServices(workspace).then(function(services) { |
27 | 28 | services.forEach(function(service) { |
28 | host_id = service.parent.split(".")[0]; | |
29 | host_id = service._id.split(".")[0]; | |
29 | 30 | if (hosts_dict.hasOwnProperty(host_id)) { |
30 | 31 | hosts_dict[host_id].services.push(service); |
31 | 32 | } |
2 | 2 | // See the file 'doc/LICENSE' for the license information |
3 | 3 | |
4 | 4 | angular.module('faradayApp') |
5 | .factory('Vuln', ['BASEURL', '$http', '$q', 'attachmentsFact', | |
6 | function(BASEURL, $http, $q, attachmentsFact) { | |
5 | .factory('Vuln', ['BASEURL', '$q', 'ServerAPI', 'attachmentsFact', | |
6 | function(BASEURL, $q, ServerAPI, attachmentsFact) { | |
7 | 7 | Vuln = function(ws, data) { |
8 | 8 | var now = new Date(), |
9 | 9 | date = now.getTime() / 1000.0; |
43 | 43 | this.target = ""; |
44 | 44 | this.type = "Vulnerability"; |
45 | 45 | this.ws = ""; |
46 | this.status = "opened"; | |
46 | 47 | |
47 | 48 | if(data) { |
48 | 49 | if(data.name === undefined || data.name === "") { |
55 | 56 | var public_properties = [ |
56 | 57 | '_attachments', 'confirmed', 'data', 'desc', 'easeofresolution', |
57 | 58 | 'impact', 'name', 'owned', 'refs', 'resolution', 'severity', |
59 | 'status', | |
58 | 60 | ]; |
59 | 61 | |
60 | 62 | var saved_properties = public_properties.concat( |
90 | 92 | }); |
91 | 93 | }, |
92 | 94 | remove: function() { |
93 | var self = this, | |
94 | url = BASEURL + self.ws + "/" + self._id + "?rev=" + self._rev; | |
95 | return $http.delete(url); | |
95 | var self = this; | |
96 | return ServerAPI.deleteVuln(self.ws, self._id, self._rev); | |
97 | ||
96 | 98 | }, |
97 | 99 | _update: function(vuln, data) { |
98 | 100 | var deferred = $q.defer(), |
130 | 132 | angular.extend(vuln._attachments, stubs); |
131 | 133 | attachmentsFact.loadAttachments(files).then(function(atts) { |
132 | 134 | angular.extend(vuln._attachments, atts); |
133 | self._save(vuln) | |
134 | .success(function(response) { | |
135 | self._save(vuln, true) | |
136 | .then(function(response) { | |
135 | 137 | self.set(self.ws, vuln); |
136 | 138 | self._rev = response.rev; |
137 | 139 | deferred.resolve(); |
138 | }) | |
139 | .error(function() { | |
140 | }, function() { | |
140 | 141 | deferred.reject(); |
141 | 142 | }); |
142 | 143 | }); |
143 | 144 | } else { |
144 | self._save(vuln) | |
145 | .success(function(response) { | |
145 | self._save(vuln, true) | |
146 | .then(function(response) { | |
146 | 147 | self.set(self.ws, vuln); |
147 | 148 | self._rev = response.rev; |
148 | 149 | deferred.resolve(); |
149 | }) | |
150 | .error(function() { | |
150 | }, function() { | |
151 | 151 | deferred.reject(); |
152 | 152 | }); |
153 | 153 | } |
154 | 154 | |
155 | 155 | return deferred.promise; |
156 | 156 | }, |
157 | ||
157 | 158 | update: function(data) { |
158 | 159 | var self = this, |
159 | 160 | vuln = new Vuln(self.ws, self); |
160 | 161 | return self._update(vuln, data); |
161 | 162 | }, |
163 | ||
162 | 164 | populate: function() { |
163 | 165 | var deferred = $q.defer(), |
164 | 166 | self = this, |
196 | 198 | url = BASEURL + self.ws + "/" + self._id; |
197 | 199 | |
198 | 200 | self.populate().then(function(resp) { |
199 | self._save(resp) | |
200 | .success(function(data) { | |
201 | self._save(resp, false) | |
202 | .then(function(data) { | |
201 | 203 | self._rev = data.rev; |
202 | 204 | deferred.resolve(self); |
203 | }) | |
204 | .error(function(data, status, headers, config) { | |
205 | }, function(data, status, headers, config) { | |
205 | 206 | deferred.reject(status); |
206 | 207 | }); |
207 | 208 | }, function() { |
210 | 211 | |
211 | 212 | return deferred.promise; |
212 | 213 | }, |
213 | _save: function(data) { | |
214 | _save: function(data, update) { | |
214 | 215 | var doc = {}; |
215 | var url = BASEURL + this.ws + "/" + this._id; | |
216 | for (property in data) { | |
216 | if (typeof update === "undefined") {var update = false}; | |
217 | for (var property in data) { | |
217 | 218 | if (this.saved_properties.indexOf(property) != -1) { |
218 | 219 | doc[property] = data[property]; |
219 | 220 | } |
220 | }; | |
221 | return $http.put(url, doc); | |
222 | } | |
223 | }; | |
224 | ||
221 | } | |
222 | if (update) { | |
223 | return ServerAPI.updateVuln(this.ws, data); | |
224 | } | |
225 | else { | |
226 | return ServerAPI.createVuln(this.ws, data); | |
227 | } | |
228 | } | |
229 | } | |
225 | 230 | return Vuln; |
226 | 231 | }]); |
3 | 3 | |
4 | 4 | angular.module('faradayApp') |
5 | 5 | .factory('vulnsManager', |
6 | ['Vuln', 'WebVuln', 'BASEURL', '$http', '$q', 'commonsFact', | |
7 | function(Vuln, WebVuln, BASEURL, $http, $q, commonsFact) { | |
6 | ['Vuln', 'WebVuln', 'BASEURL', '$q', 'ServerAPI', 'commonsFact', | |
7 | function(Vuln, WebVuln, BASEURL, $q, ServerAPI, commonsFact) { | |
8 | 8 | var vulnsManager = {}; |
9 | 9 | |
10 | 10 | vulnsManager.createVuln = function(ws, data) { |
23 | 23 | |
24 | 24 | vulnsManager.getVulns = function(ws, page, page_size, filter, sort, sort_direction) { |
25 | 25 | var deferred = $q.defer(); |
26 | ||
27 | var url = BASEURL + '_api/ws/' + ws + '/vulns'; | |
28 | url = commonsFact.addPresentationParams(url, page, page_size, filter, sort, sort_direction); | |
29 | ||
30 | $http.get(url) | |
26 | var options = {page: page, page_size: page_size, sort:sort, sort_dir: sort_direction} | |
27 | for( var property in filter ) { | |
28 | if (filter.hasOwnProperty(property)) { | |
29 | options[property] = filter[property]; | |
30 | } | |
31 | }; | |
32 | ServerAPI.getVulns(ws, options) | |
31 | 33 | .then(function(response) { |
32 | 34 | var result = { |
33 | 35 | vulnerabilities: [], |
53 | 55 | }, function(response) { |
54 | 56 | deferred.reject("Unable to retrieve vulnerabilities from server"); |
55 | 57 | }); |
56 | ||
57 | 58 | return deferred.promise; |
58 | 59 | }; |
59 | 60 |
2 | 2 | // See the file 'doc/LICENSE' for the license information |
3 | 3 | |
4 | 4 | angular.module('faradayApp') |
5 | .factory('WebVuln', ['Vuln', 'BASEURL', '$http', function(Vuln, BASEURL, $http) { | |
5 | .factory('WebVuln', ['Vuln', 'BASEURL', function(Vuln, BASEURL) { | |
6 | 6 | WebVuln = function(ws, data) { |
7 | 7 | Vuln.call(this, data); |
8 | 8 | if(data) { |
3 | 3 | |
4 | 4 | <form novalidate> |
5 | 5 | <section id="main" class="seccion clearfix"> |
6 | <div class="right-main"><div id="reports-main" class="fila clearfix"> | |
7 | <h2 class="ws-label"> | |
8 | <div class="row"> | |
9 | <div class="col-md-12"> | |
10 | <div class="col-md-4"> | |
11 | <span id="ws-name" title="Workspaces">Workspaces</span><!-- WS name --> | |
12 | </div> | |
13 | <div class="col-md-3 pull-right"> | |
14 | <button id="delete" type="button" class="btn btn-default" title="Delete selected Workspaces" ng-click="delete()"> | |
15 | <span class="glyphicon glyphicon-trash"></span> | |
16 | Delete | |
17 | </button> | |
18 | <button id="merge" type="button" class="btn btn-default" title="Edit selected Workspaces" ng-click="edit()"> | |
19 | <span class="glyphicon glyphicon-pencil"></span> | |
20 | Edit | |
21 | </button> | |
22 | <button id="merge" type="button" class="btn btn-success" title="New Workspace" ng-click="new()"> | |
23 | <span class="glyphicon glyphicon-plus-sign"></span> | |
24 | New | |
25 | </button> | |
26 | </div> | |
27 | <div class="col-md-4 pull-right"> | |
28 | <div id="custom-search-input"> | |
29 | <div class="input-group"> | |
30 | <input type="text" class="form-control input-md" ng-model="search" placeholder="Search" /> | |
31 | <span class="input-group-btn"> | |
32 | <button class="btn btn-info btn-md" type="button"> | |
33 | <i class="glyphicon glyphicon-search"></i> | |
34 | </button> | |
35 | </span> | |
36 | </div> | |
37 | </div> | |
6 | <div class="right-main"><div id="reports-main" class="fila clearfix"> | |
7 | <h2 class="ws-label"> | |
8 | <div class="row"> | |
9 | <div class="col-md-12"> | |
10 | <div class="col-md-4"> | |
11 | <span id="ws-name" title="Workspaces">Workspaces</span><!-- WS name --> | |
12 | </div> | |
13 | <div class="col-md-3 pull-right"> | |
14 | <button id="delete" type="button" class="btn btn-default" title="Delete selected Workspaces" ng-click="delete()"> | |
15 | <span class="glyphicon glyphicon-trash"></span> | |
16 | Delete | |
17 | </button> | |
18 | <button id="merge" type="button" class="btn btn-default" title="Edit selected Workspaces" ng-click="edit()"> | |
19 | <span class="glyphicon glyphicon-pencil"></span> | |
20 | Edit | |
21 | </button> | |
22 | <button id="merge" type="button" class="btn btn-success" title="New Workspace" ng-click="new()"> | |
23 | <span class="glyphicon glyphicon-plus-sign"></span> | |
24 | New | |
25 | </button> | |
26 | </div> | |
27 | <div class="col-md-4 pull-right"> | |
28 | <div id="custom-search-input"> | |
29 | <div class="input-group"> | |
30 | <input type="text" class="form-control input-md" ng-model="search" placeholder="Search" /> | |
31 | <span class="input-group-btn"> | |
32 | <button class="btn btn-info btn-sm" type="button"> | |
33 | <i class="fa fa-search"></i> | |
34 | </button> | |
35 | </span> | |
38 | 36 | </div> |
39 | 37 | </div> |
40 | 38 | </div> |
41 | </h2><!-- .ws-label --> | |
42 | <div class="reports"> | |
43 | <table class="status-report table table-responsive"> | |
44 | <thead> | |
45 | <tr> | |
46 | <th>Name</th> | |
47 | <th>Start Date</th> | |
48 | <th>End Date</th> | |
49 | <th>Vulns</th> | |
50 | <th>Hosts</th> | |
51 | <th>Services</th> | |
52 | </tr> | |
53 | </thead> | |
54 | <tbody> | |
55 | <tr ng-repeat="ws in workspaces | filter:query | filter:search | orderBy:sortField:reverse" | |
56 | selection-model selection-model-selected-class="multi-selected"> | |
57 | <td> | |
58 | <span class="onhover upsize" ng-click="redirect(ws.name)"> | |
59 | <b>{{ws.name}}</b> | |
60 | </span> | |
61 | </td> | |
62 | <td ng-bind="ws.duration.start || '-' | date:'MM/dd/yyyy'"></td> | |
63 | <td ng-bind="ws.duration.end || '-' | date:'MM/dd/yyyy'"></td> | |
64 | <td><a href="#/status/ws/{{ws.name}}">{{objects[ws.name]['total_vulns']}}</a></td> | |
65 | <td><a href="#/hosts/ws/{{ws.name}}">{{objects[ws.name]['hosts']}}</a></td> | |
66 | <td ng-bind="objects[ws.name]['services']"></td> | |
67 | </tr> | |
68 | </tbody> | |
69 | </table><!-- #hosts --> | |
70 | </div><!-- .reports --> | |
71 | </div><!-- #reports-main --></div><!-- .right-main --> | |
72 | </section><!-- #main --> | |
39 | </div> | |
40 | </div> | |
41 | </h2><!-- .ws-label --> | |
42 | <div class="reports"> | |
43 | <table class="status-report table table-responsive"> | |
44 | <thead> | |
45 | <tr> | |
46 | <th>Name</th> | |
47 | <th>Start Date</th> | |
48 | <th>End Date</th> | |
49 | <th>Vulns</th> | |
50 | <th>Hosts</th> | |
51 | <th>Services</th> | |
52 | </tr> | |
53 | </thead> | |
54 | <tbody> | |
55 | <tr ng-repeat="ws in workspaces | filter:query | filter:search | orderBy:sortField:reverse" | |
56 | selection-model selection-model-selected-class="multi-selected"> | |
57 | <td> | |
58 | <span class="onhover upsize" ng-click="redirect(ws.name)"> | |
59 | <b>{{ws.name}}</b> | |
60 | </span> | |
61 | </td> | |
62 | <td ng-bind="ws.duration.start || '-' | date:'MM/dd/yyyy'"></td> | |
63 | <td ng-bind="ws.duration.end || '-' | date:'MM/dd/yyyy'"></td> | |
64 | <td><a href="#/status/ws/{{ws.name}}">{{objects[ws.name]['total_vulns']}}</a></td> | |
65 | <td><a href="#/hosts/ws/{{ws.name}}">{{objects[ws.name]['hosts']}}</a></td> | |
66 | <td ng-bind="objects[ws.name]['services']"></td> | |
67 | </tr> | |
68 | </tbody> | |
69 | </table><!-- #hosts --> | |
70 | </div><!-- .reports --> | |
71 | </div><!-- #reports-main --></div><!-- .right-main --> | |
72 | </section><!-- #main --> | |
73 | 73 | </form> |
2 | 2 | // See the file 'doc/LICENSE' for the license information |
3 | 3 | |
4 | 4 | angular.module('faradayApp') |
5 | .factory('workspacesFact', ['BASEURL', '$http', '$q', function(BASEURL, $http, $q) { | |
5 | .factory('workspacesFact', ['BASEURL', 'ServerAPI', '$http', '$q', function(BASEURL, ServerAPI, $http, $q) { | |
6 | 6 | var workspacesFact = {}; |
7 | 7 | |
8 | 8 | workspacesFact.list = function() { |
9 | var url = BASEURL + "_api/ws", | |
10 | deferred = $q.defer(); | |
11 | $http.get(url). | |
9 | var deferred = $q.defer(); | |
10 | ServerAPI.getWorkspacesNames(). | |
12 | 11 | then(function(response) { deferred.resolve(response.data.workspaces) }, errorHandler); |
13 | 12 | return deferred.promise; |
14 | 13 | }; |
19 | 18 | |
20 | 19 | workspacesFact.get = function(workspace_name) { |
21 | 20 | var deferred = $q.defer(); |
22 | $http.get(BASEURL + workspace_name + '/' + workspace_name) | |
23 | .success(function(data, status, headers, config) { | |
24 | deferred.resolve(data); | |
25 | }) | |
26 | .error(function() { | |
21 | ServerAPI.getWorkspace(workspace_name). | |
22 | then(function(ws) { | |
23 | deferred.resolve(ws.data); | |
24 | }, function() { | |
27 | 25 | deferred.reject(); |
28 | 26 | }); |
29 | 27 | return deferred.promise; |
31 | 29 | |
32 | 30 | workspacesFact.getDuration = function(workspace_name) { |
33 | 31 | var deferred = $q.defer(); |
34 | workspacesFact.get(workspace_name).then(function(workspace) { | |
35 | var ws = workspace; | |
32 | ServerAPI.getWorkspace(workspace_name).then(function(workspace) { | |
33 | var ws = workspace.data; | |
36 | 34 | var dur = {}; |
37 | 35 | |
38 | 36 | if(ws.hasOwnProperty('duration')) { |
51 | 49 | |
52 | 50 | workspacesFact.exists = function(workspace_name) { |
53 | 51 | var deferred = $q.defer(); |
54 | var request = { | |
55 | method: 'HEAD', | |
56 | url: BASEURL + workspace_name | |
57 | }; | |
58 | $http(request).success(function(data) { | |
52 | ServerAPI.getWorkspace(workspace_name).then( | |
53 | function(response) { | |
59 | 54 | deferred.resolve(true); |
60 | }) | |
61 | .error(function() { | |
55 | }, function(error) { | |
62 | 56 | deferred.resolve(false); |
63 | 57 | }); |
64 | 58 | return deferred.promise; |
74 | 68 | }; |
75 | 69 | |
76 | 70 | workspacesFact.put = function(workspace) { |
77 | return createDatabase(workspace). | |
78 | then(function(resp) { createWorkspaceDoc(resp, workspace); }, errorHandler). | |
79 | then(function(resp) { uploadDocs(workspace.name); }, errorHandler); | |
80 | }; | |
81 | ||
82 | createDatabase = function(workspace){ | |
83 | return $http.put(BASEURL + workspace.name, workspace); | |
84 | }; | |
85 | ||
86 | createWorkspaceDoc = function(response, workspace){ | |
87 | $http.put(BASEURL + workspace.name + '/' + workspace.name, workspace). | |
88 | success(function(data){ | |
89 | workspace._rev = data.rev; | |
90 | }). | |
91 | error(function(data) { | |
92 | errorHandler; | |
93 | }); | |
94 | }; | |
95 | ||
96 | uploadDocs = function(workspace) { | |
97 | var files = {}, | |
98 | reports = BASEURL + 'reports/_design/reports'; | |
99 | $http.get(reports). | |
100 | success(function(data) { | |
101 | var attachments = data._attachments; | |
102 | if(Object.keys(attachments).length > 0) { | |
103 | for(var prop in attachments) { | |
104 | if(attachments.hasOwnProperty(prop)) { | |
105 | if(prop.indexOf("views/") > -1) { | |
106 | files[prop] = $http.get(reports + "/" + prop); | |
107 | } | |
108 | } | |
109 | } | |
110 | } | |
111 | $q.all(files).then(function(resp) { | |
112 | var bulk = {docs:[]}; | |
113 | for(var file in files) { | |
114 | if(files.hasOwnProperty(file)) { | |
115 | var views = [], | |
116 | parts = file.split("/"), | |
117 | component = parts[1], | |
118 | type = parts[2], | |
119 | name = parts[3], | |
120 | filename = parts[4].split(".")[0], | |
121 | docIndex = indexOfDocument(bulk.docs, "_design/"+component); | |
122 | ||
123 | if(docIndex == -1) { | |
124 | bulk.docs.push({ | |
125 | _id: "_design/"+component, | |
126 | language: "javascript", | |
127 | views: {} | |
128 | }); | |
129 | docIndex = bulk.docs.length - 1; | |
130 | } | |
131 | ||
132 | if(!bulk["docs"][docIndex]["views"].hasOwnProperty(name)) { | |
133 | bulk["docs"][docIndex]["views"][name] = {}; | |
134 | } | |
135 | ||
136 | bulk["docs"][docIndex]["views"][name][filename] = resp[file]["data"]; | |
137 | } | |
138 | } | |
139 | $http.post(BASEURL + workspace + "/_bulk_docs", JSON.stringify(bulk)); | |
140 | }, errorHandler); | |
141 | }). | |
142 | error(function(data) { | |
143 | errorHandler; | |
144 | }); | |
71 | return ServerAPI.createWorkspace(workspace.name, workspace); | |
145 | 72 | }; |
146 | 73 | |
147 | 74 | indexOfDocument = function(list, name) { |
156 | 83 | |
157 | 84 | workspacesFact.update = function(workspace) { |
158 | 85 | var deferred = $q.defer(); |
159 | document_url = BASEURL + workspace.name + '/' + workspace.name + '?rev=' + workspace._rev; | |
160 | $http.put(document_url, workspace).success(function(data){ | |
86 | ServerAPI.updateWorkspace(workspace).then(function(data){ | |
161 | 87 | workspace._rev = data.rev; |
162 | 88 | deferred.resolve(workspace); |
163 | 89 | }); |
166 | 92 | |
167 | 93 | workspacesFact.delete = function(workspace_name) { |
168 | 94 | var deferred = $q.defer(); |
169 | var request = { | |
170 | method: 'DELETE', | |
171 | url: BASEURL + workspace_name | |
172 | }; | |
173 | $http(request).success(function(data) { | |
95 | ServerAPI.deleteWorkspace(workspace_name).then(function(data) { | |
174 | 96 | deferred.resolve(workspace_name); |
175 | }) | |
176 | .error(function() { | |
97 | }, function() { | |
177 | 98 | deferred.reject(); |
178 | 99 | }); |
179 | 100 | return deferred.promise; |
0 | import unittest | |
1 | import json | |
2 | from persistence.server import models | |
3 | from persistence.server import server_io_exceptions | |
4 | from mock import MagicMock, patch, create_autospec | |
5 | ||
6 | HOST_JSON_STRING = '{"_id":1,"id":"08d3b6545ec70897daf05cd471f4166a8e605c00","key":"08d3b6545ec70897daf05cd471f4166a8e605c00","value":{"_id":"08d3b6545ec70897daf05cd471f4166a8e605c00","_rev":"1-a12368dc03d557c337e833f8090db568","default_gateway":["192.168.20.1","00:1d:aa:c9:83:e8"],"description":"","interfaces":[1],"metadata":{"create_time":1475852074.455225,"creator":"","owner":"","update_action":0,"update_controller_action":"ModelControler._processAction ModelControler.newHost","update_time":1475852074.455226,"update_user":""},"name":"10.31.112.29","os":"Microsoft Windows Server 2008 R2 Standard Service Pack 1","owned":"false","owner":"","services":12,"vulns":43}}' | |
7 | ||
8 | INTERFACE_JSON_STRING = '{"_id":1,"id":"08d3b6545ec70897daf05cd471f4166a8e605c00.02946afc59c50a4d76c1adbb082c2d5439baf50a","key":"08d3b6545ec70897daf05cd471f4166a8e605c00.02946afc59c50a4d76c1adbb082c2d5439baf50a","value":{"_id":"08d3b6545ec70897daf05cd471f4166a8e605c00.02946afc59c50a4d76c1adbb082c2d5439baf50a","_rev":"1-c279e0906d2b1f02b832a99d5f58f99c","description":"","host_id":1,"hostnames":["qa3app09"],"ipv4":{"DNS":[],"address":"10.31.112.29","gateway":"0.0.0.0","mask":"0.0.0.0"},"ipv6":{"DNS":[],"address":"0000:0000:0000:0000:0000:0000:0000:0000","gateway":"0000:0000:0000:0000:0000:0000:0000:0000","prefix":"00"},"mac":"00:50:56:81:01:e3","metadata":{"create_time":1475852074.456803,"creator":"","owner":"","update_action":0,"update_controller_action":"ModelControler._processAction ModelControler.newInterface","update_time":1475852074.456803,"update_user":""},"name":"10.31.112.29","network_segment":"","owned":false,"owner":"","ports":{"closed":null,"filtered":null,"opened":null}}}' | |
9 | ||
10 | SERVICE_JSON_STRING = '{"_id":1,"id":"08d3b6545ec70897daf05cd471f4166a8e605c00.02946afc59c50a4d76c1adbb082c2d5439baf50a.029384202ef91fff5892042392875595fb0b41ed","key":"08d3b6545ec70897daf05cd471f4166a8e605c00.02946afc59c50a4d76c1adbb082c2d5439baf50a.029384202ef91fff5892042392875595fb0b41ed","value":{"_id":"08d3b6545ec70897daf05cd471f4166a8e605c00.02946afc59c50a4d76c1adbb082c2d5439baf50a.029384202ef91fff5892042392875595fb0b41ed","_rev":"1-73ef6b9e6488fd05823b89e36bbbb626","description":"","metadata":{"create_time":1475852074.457551,"creator":"","owner":"","update_action":0,"update_controller_action":"ModelControler._processAction ModelControler.newService","update_time":1475852074.457551,"update_user":""},"name":"msrdp","owned":false,"owner":"","ports":[3389],"protocol":"tcp","status":"open","version":"unknown"},"vulns":8}' | |
11 | ||
12 | VULN_JSON_STRING = '{"_id":8,"id":"08d3b6545ec70897daf05cd471f4166a8e605c00.2a21f3916b8c9a40e70b2fc6b7ea8f7a3a498558","key":"08d3b6545ec70897daf05cd471f4166a8e605c00.2a21f3916b8c9a40e70b2fc6b7ea8f7a3a498558","value":{"_attachments":{},"_id":"08d3b6545ec70897daf05cd471f4166a8e605c00.2a21f3916b8c9a40e70b2fc6b7ea8f7a3a498558","_rev":"1-28cb6b1372f4712dbbf7b8e1e23699e4","confirmed":false,"data":"","desc":"Each ethernet MAC address starts with a 24-bit Organizationally Unique Identifier.\\nThese OUI are registered by IEEE.\\nOutput: The following card manufacturers were identified :\\n\\n00:50:56:81:01:e3 : VMware, Inc.","description":"Each ethernet MAC address starts with a 24-bit Organizationally Unique Identifier.\\nThese OUI are registered by IEEE.\\nOutput: The following card manufacturers were identified :\\n\\n00:50:56:81:01:e3 : VMware, Inc.","easeofresolution":null,"hostnames":["qa3app09"],"impact":{"accountability":null,"availability":null,"confidentiality":null,"integrity":null},"issuetracker":{},"metadata":{"create_time":1475852074.459108,"creator":"","owner":"","update_action":0,"update_controller_action":"ModelControler._processAction ModelControler.newVuln","update_time":1475852074.459108,"update_user":""},"method":null,"name":"Ethernet Card Manufacturer Detection","obj_id":"2a21f3916b8c9a40e70b2fc6b7ea8f7a3a498558","owned":"false","owner":"","params":"","parent":"08d3b6545ec70897daf05cd471f4166a8e605c00","path":null,"pname":null,"query":null,"refs":[],"request":null,"resolution":"n/a","response":null,"service":"","severity":"info","status":"","tags":[],"target":"10.31.112.29","type":"Vulnerability","website":null}}' | |
13 | ||
14 | VULN_WEB_JSON_STRING = '{"_id":20,"id":"08d3b6545ec70897daf05cd471f4166a8e605c00.02946afc59c50a4d76c1adbb082c2d5439baf50a.790670b8824bf95588c1a00e4e65cb3c681e94d6.f0390f7e450cb71a4ff31e3bd38c2049c5f189f8","key":"08d3b6545ec70897daf05cd471f4166a8e605c00.02946afc59c50a4d76c1adbb082c2d5439baf50a.790670b8824bf95588c1a00e4e65cb3c681e94d6.f0390f7e450cb71a4ff31e3bd38c2049c5f189f8","value":{"_attachments":{},"_id":"08d3b6545ec70897daf05cd471f4166a8e605c00.02946afc59c50a4d76c1adbb082c2d5439baf50a.790670b8824bf95588c1a00e4e65cb3c681e94d6.f0390f7e450cb71a4ff31e3bd38c2049c5f189f8","_rev":"1-aeee90afddaa938dff756baf8d2cebda","confirmed":false,"data":"","desc":"It was possible to identify the remote service by its banner or by looking at the error message it sends when it receives an HTTP request.\\nOutput: A web server is running on this port.","description":"It was possible to identify the remote service by its banner or by looking at the error message it sends when it receives an HTTP request.\\nOutput: A web server is running on this port.","easeofresolution":null,"hostnames":["qa3app09"],"impact":{"accountability":null,"availability":null,"confidentiality":null,"integrity":null},"issuetracker":{},"metadata":{"create_time":1475852074.464117,"creator":"","owner":"","update_action":0,"update_controller_action":"ModelControler.newVulnWeb","update_time":1475852074.464117,"update_user":""},"method":"","name":"Service Detection","obj_id":"f0390f7e450cb71a4ff31e3bd38c2049c5f189f8","owned":"false","owner":"","params":"","parent":"08d3b6545ec70897daf05cd471f4166a8e605c00.02946afc59c50a4d76c1adbb082c2d5439baf50a.790670b8824bf95588c1a00e4e65cb3c681e94d6","path":"","pname":"","query":"","refs":[],"request":"","resolution":"n/a","response":"","service":"(80/tcp) www","severity":"info","status":"","tags":[],"target":"10.31.112.29","type":"VulnerabilityWeb","website":"qa3app09"}}' | |
15 | ||
16 | NOTE_JSON_STRING = '{"_id":1,"id":"08d3b6545ec70897daf05cd471f4166a8e605c00.02946afc59c50a4d76c1adbb082c2d5439baf50a.790670b8824bf95588c1a00e4e65cb3c681e94d6.83b3a120d6928b3c1f04a41cfccc59a55c627cf2","key":"08d3b6545ec70897daf05cd471f4166a8e605c00.02946afc59c50a4d76c1adbb082c2d5439baf50a.790670b8824bf95588c1a00e4e65cb3c681e94d6.83b3a120d6928b3c1f04a41cfccc59a55c627cf2","value":{"_id":"08d3b6545ec70897daf05cd471f4166a8e605c00.02946afc59c50a4d76c1adbb082c2d5439baf50a.790670b8824bf95588c1a00e4e65cb3c681e94d6.83b3a120d6928b3c1f04a41cfccc59a55c627cf2","couchid":"08d3b6545ec70897daf05cd471f4166a8e605c00.02946afc59c50a4d76c1adbb082c2d5439baf50a.790670b8824bf95588c1a00e4e65cb3c681e94d6.83b3a120d6928b3c1f04a41cfccc59a55c627cf2","description":"","metadata":{"create_time":1475852074.461232,"creator":"","owner":"","update_action":0,"update_controller_action":"ModelControler._processAction ModelControler.newNote","update_time":1475852074.461232,"update_user":""},"name":"website","owned":false,"owner":"","text":""}}' | |
17 | ||
18 | models.FARADAY_UP = False | |
19 | models.MERGE_STRATEGY = None # this is the default :) | |
20 | ||
21 | class ModelsTest(unittest.TestCase): | |
22 | ||
23 | def setUp(self): | |
24 | self.ws = 'a_workspace_name' | |
25 | self.a_host_dictionary = json.loads(HOST_JSON_STRING) | |
26 | self.an_interface_dictionary = json.loads(INTERFACE_JSON_STRING) | |
27 | self.a_service_dictionary = json.loads(SERVICE_JSON_STRING) | |
28 | self.a_vuln_dictionary = json.loads(VULN_JSON_STRING) | |
29 | self.a_vuln_web_dictionary = json.loads(VULN_WEB_JSON_STRING) | |
30 | self.a_note_dictionary = json.loads(NOTE_JSON_STRING) | |
31 | ||
32 | self.maxDiff = None # show the diff when test run no matter how big | |
33 | ||
34 | def test_ignore_in_changes(self): | |
35 | def server_io(): return {'ok': True, 'rev': 1, 'id': 2} | |
36 | decorated = models._ignore_in_changes(server_io) | |
37 | with patch.dict(models._LOCAL_CHANGES_ID_TO_REV, clear=True): | |
38 | json = decorated() | |
39 | self.assertEqual(models._LOCAL_CHANGES_ID_TO_REV[json['id']], json['rev']) | |
40 | ||
41 | def test_flatten_dictionary(self): | |
42 | flattened_host_dictionary = models._flatten_dictionary(self.a_host_dictionary) | |
43 | what_the_flattened_dict_should_look_like = { | |
44 | u"_id":1, | |
45 | u"id":u"08d3b6545ec70897daf05cd471f4166a8e605c00", | |
46 | u"_rev":u"1-a12368dc03d557c337e833f8090db568", | |
47 | u"default_gateway":[u"192.168.20.1",u"00:1d:aa:c9:83:e8"], | |
48 | u"description":u"", | |
49 | u"interfaces":[1], | |
50 | u"metadata":{u"create_time":1475852074.455225, | |
51 | u"creator":u"", | |
52 | u"owner":u"", | |
53 | u"update_action":0, | |
54 | u"update_controller_action":u"ModelControler._processAction ModelControler.newHost", | |
55 | u"update_time":1475852074.455226, | |
56 | u"update_user":u""}, | |
57 | u"name":u"10.31.112.29", | |
58 | u"os":u"Microsoft Windows Server 2008 R2 Standard Service Pack 1", | |
59 | u"owned": u'false', | |
60 | u"owner":u"", | |
61 | u"services":12, | |
62 | u"vulns":43} | |
63 | ||
64 | self.assertDictEqual(flattened_host_dictionary, what_the_flattened_dict_should_look_like) | |
65 | ||
66 | def test_faraday_ready_objects_getter(self): | |
67 | hosts = models._get_faraday_ready_objects(self.ws, [self.a_host_dictionary], 'hosts') | |
68 | interfaces = models._get_faraday_ready_objects(self.ws, [self.an_interface_dictionary], 'interfaces') | |
69 | services = models._get_faraday_ready_objects(self.ws, [self.a_service_dictionary], 'services') | |
70 | vulns = models._get_faraday_ready_objects(self.ws, [self.a_vuln_dictionary], 'vulns') | |
71 | vulns_web = models._get_faraday_ready_objects(self.ws, [self.a_vuln_dictionary], 'vulns_web') | |
72 | ||
73 | self.assertTrue(all([isinstance(h, models.Host) for h in hosts])) | |
74 | self.assertTrue(all([isinstance(i, models.Interface) for i in interfaces])) | |
75 | self.assertTrue(all([isinstance(s, models.Service) for s in services])) | |
76 | self.assertTrue(all([isinstance(v, models.Vuln) for v in vulns])) | |
77 | self.assertTrue(all([isinstance(v, models.VulnWeb) for v in vulns_web])) | |
78 | ||
79 | def test_id_creation(self): | |
80 | # ideally, the flatten dictionary should be provided and shouldnt depend upon | |
81 | # our implementation. | |
82 | # ideally. | |
83 | classes = [models.Host, models.Interface, models.Service, models.Vuln, models.VulnWeb, models.Note] | |
84 | dicts = [self.a_host_dictionary, self.an_interface_dictionary, self.a_service_dictionary, | |
85 | self.a_vuln_dictionary, self.a_vuln_web_dictionary, self.a_note_dictionary] | |
86 | dicts = map(models._flatten_dictionary, dicts) | |
87 | for class_, dictionary in zip(classes, dicts): | |
88 | expected_id = dictionary['id'] | |
89 | parent_id = '.'.join(expected_id.split('.')[:-1]) | |
90 | obj = class_(dictionary, self.ws) | |
91 | obj.setID(parent_id) | |
92 | self.assertEqual(expected_id, unicode(obj.id)) |
52 | 52 | action = self.plugin._pending_actions.get(block=True) |
53 | 53 | self.assertEqual(action[0], modelactions.CADDNOTESRV) |
54 | 54 | action = self.plugin._pending_actions.get(block=True) |
55 | self.assertEqual(action[0], modelactions.CADDNOTENOTE) | |
56 | 55 | action = self.plugin._pending_actions.get(block=True) |
57 | 56 | self.assertEqual(action[0], modelactions.CADDVULNWEBSRV) |
58 | 57 | self.assertEqual(action[3], "ASP.NET error message") |
1 | 1 | import requests |
2 | 2 | import unittest |
3 | 3 | from persistence.server import server |
4 | from persistence.server import utils | |
4 | from persistence.server import server_io_exceptions | |
5 | 5 | from mock import MagicMock, patch |
6 | 6 | |
7 | 7 | server.FARADAY_UP = False |
40 | 40 | url = "http://just_raise_conflict.com" |
41 | 41 | responses.add(responses.PUT, url, body='{"name": "betcha"}', status=409, |
42 | 42 | content_type="application/json", json={'error': 'conflict'}) |
43 | with self.assertRaises(server.ConflictInDatabase): | |
43 | with self.assertRaises(server_io_exceptions.ConflictInDatabase): | |
44 | 44 | server._unsafe_io_with_server(requests.put, 200, url, json={"name": "betcha"}) |
45 | 45 | |
46 | 46 | @responses.activate |
47 | 47 | def test_raise_resource_does_not_exist(self): |
48 | 48 | url = "http://dont_exist.com" |
49 | 49 | responses.add(responses.GET, url, body='{"name": "betcha"}', status=404) |
50 | with self.assertRaises(server.ResourceDoesNotExist): | |
50 | with self.assertRaises(server_io_exceptions.ResourceDoesNotExist): | |
51 | 51 | server._unsafe_io_with_server(requests.get, 200, url, json={"name": "betcha"}) |
52 | 52 | |
53 | 53 | @responses.activate |
54 | 54 | def test_raise_unauthorized(self): |
55 | 55 | url = "http://nope.com" |
56 | 56 | responses.add(responses.GET, url, body='{"name": "betcha"}', status=403) |
57 | with self.assertRaises(server.Unauthorized): | |
57 | with self.assertRaises(server_io_exceptions.Unauthorized): | |
58 | 58 | server._unsafe_io_with_server(requests.get, 200, url, json={"name": "betcha"}) |
59 | 59 | url2 = "http://nope2.com" |
60 | 60 | responses.add(responses.GET, url2, body='{"name": "betcha"}', status=401) |
61 | with self.assertRaises(server.Unauthorized): | |
61 | with self.assertRaises(server_io_exceptions.Unauthorized): | |
62 | 62 | server._unsafe_io_with_server(requests.get, 200, url, json={"name": "betcha"}) |
63 | 63 | |
64 | 64 | @responses.activate |
65 | 65 | def test_raise_cant_comm_with_server_on_wrong_response_code(self): |
66 | 66 | url = "http://yes.com" |
67 | 67 | responses.add(responses.GET, url, status=204) |
68 | with self.assertRaises(server.CantCommunicateWithServerError): | |
68 | with self.assertRaises(server_io_exceptions.CantCommunicateWithServerError): | |
69 | 69 | server._unsafe_io_with_server(requests.get, 200, url) |
70 | 70 | |
71 | 71 | @responses.activate |
138 | 138 | def test_faraday_dictionary_dispatcher_calls(self, mock_hosts, mock_vulns, mock_interfaces, |
139 | 139 | mock_services, mock_notes, mock_credentials, mock_commands): |
140 | 140 | # NOTE: if you finds any bugs here, i have the suspipcion that mock_host is actually mock_commands |
141 | # i mean, the parameters names are wrong. I'd check for that. Good luck. | |
141 | # i mean that the parameters names are wrong. I'd check for that. Good luck. | |
142 | 142 | server._get_faraday_ready_dictionaries('a', 'hosts', 'whatever') |
143 | 143 | server._get_faraday_ready_dictionaries('a', 'interfaces', 'whatever') |
144 | 144 | server._get_faraday_ready_dictionaries('a', 'vulns', 'whatever') |
169 | 169 | for obj_sign in obj_sign_to_mock.keys(): |
170 | 170 | server.get_objects('a', obj_sign) |
171 | 171 | obj_sign_to_mock[obj_sign].assert_called_once_with('a') |
172 | with self.assertRaises(utils.WrongObjectSignature): | |
172 | with self.assertRaises(server_io_exceptions.WrongObjectSignature): | |
173 | 173 | server.get_objects('a', 'not a signature') |