├── .gitignore ├── README.md ├── install ├── createPostgresDB.py └── testDataPostgres.py ├── lib ├── Config.py ├── Toolkit.py ├── database │ ├── CVESearch.py │ ├── Users.py │ └── VulnManager │ │ ├── Add.py │ │ ├── Calculations.py │ │ ├── Get-Dicts.py │ │ ├── Get-IDs.py │ │ ├── Get-Names.py │ │ ├── Get-Objects.py │ │ ├── Head.py │ │ ├── Tail.py │ │ ├── Update.py │ │ ├── __init-orig__.py │ │ ├── __init__.py │ │ └── merge.py ├── exceptions │ ├── AlreadyExistsException.py │ ├── InvalidSystemGroupInfo.py │ ├── InvalidVariableTypes.py │ ├── TeamRequiredException.py │ ├── UserNotFoundError.py │ └── __init__.py └── objects │ ├── Component.py │ ├── SystemGroup.py │ ├── Ticket.py │ ├── User.py │ └── __init__.py ├── requirements.txt └── sbin ├── static ├── css │ ├── bootstrap.min.css │ └── style.css ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 ├── img │ └── favicon.ico └── js │ ├── bootstrap.min.js │ ├── custom │ ├── components.js │ ├── scripts.js │ └── status.js │ ├── highcharts │ ├── exporting.js │ ├── highcharts-more.js │ ├── highcharts.js │ ├── mootools-adapter.js │ └── prototype-adapter.js │ ├── html5shiv.js │ └── jquery-1.11.2.min.js ├── templates ├── Error.html ├── Login.html ├── Profile.html ├── Team.html ├── addSystem.html ├── defaultHead.html ├── statistics.html ├── systemTickets.html ├── systems.html ├── teamContent.html ├── test.html ├── ticket.html └── vulnerabilities.html └── webserver.py /.gitignore: -------------------------------------------------------------------------------- 1 | lib/__pycache__/ 2 | lib/database/__pycache__/ 3 | lib/exceptions/__pycache__/ 4 | lib/objects/__pycache__/ 5 | lib/database/VulnManager/__pycache__/ 6 | 7 | lib/database/VulnManager/__init-previous__.py 8 | lib/database/VulnManager/__init-orig__.py 9 | lib/database/VulnManager/__init__.py.bak 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vulnerability-management 2 | This is a prototype for a vulnerability management tool, directly implementing the CVE-Search database for automating the 3 | creation of tickets. 4 | 5 | ##Installation: 6 | ###Requirements 7 | You will need to install postgresql to your repository. It should be in your default repository. On Ubuntu, just type `apt-get install postgresql` in a terminal window.< br /> 8 | You will also need to install `psycopg2` using `sudo pip3 install psycopg2`. A requirements file for `pip3` will soon follow 9 | ###Setting up the database 10 | **Optional modifications:**
11 | If you want, you can change the data at the top of `install/createPostgresDB.py`. Read carefully what you can and cannot edit, and proceed at your own risk.
12 |
13 | **Installation:**
14 | * `cd install` 15 | * `sudo su - postgres` 16 | * `createuser vulnmanager` 17 | * `psql -c "\password vulnmanager"` and set the password to vulnmanager (you can change this later on, or change the passwords in the install script) 18 | * `createdb -O vulnmanager vulnmanager` 19 | * `exit` 20 | * If you run the vulnerability manager on the same server as the postgresql server, change 21 | `local all postgres peer` to `local all postgres md5"` in the `/etc/postgresql//main/pg_hba.conf` file 22 | * Restart the postgres database 23 | * `python3 createPostgresDB.py` 24 | * **Optionally:** `python3 testDataPostgres.py` - add test-data like default users, teams and systems to the database 25 | 26 | 27 | **NOTE** This tool is still in development, and is not yet operational! 28 | **NOTE** Currently, we are only supporting PostgreSQL as a database. If you want support for other database types, we encourage 29 | you to help us build database layers for all sorts of databases. 30 | -------------------------------------------------------------------------------- /install/createPostgresDB.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3.3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Database creation script for PostgreSQL database 5 | # 6 | # Copyright (c) 2015 Pieter-Jan Moreels 7 | # 8 | # Software is free software released under the "Original BSD license" 9 | 10 | # Imports 11 | import os 12 | import sys 13 | _runPath = os.path.dirname(os.path.realpath(__file__)) 14 | sys.path.append(os.path.join(_runPath, "..")) 15 | 16 | import traceback 17 | 18 | 19 | from lib.Config import Configuration as conf 20 | 21 | ############################## 22 | # DEFAULT VALUES & CONSTANTS # 23 | ############################## 24 | # Put all values in lowercase. The program will take care of formatting 25 | # Start closed ticket statusses with "closed-" 26 | # The first one in the list is the default value 27 | # closed-implemented is the option for succesfully implemented patches 28 | def_ticket_statusses=["new", "in progress", "closed-implemented", "closed-irrelevant", "reopened"] 29 | # max_implement_time is in days 30 | def_ticket_priorities=[{"name": "overruled", "max_implement_time": 4, "min_cvss": None}, 31 | {"name": "extreme", "max_implement_time": 7, "min_cvss": 9.0 }, 32 | {"name": "high", "max_implement_time": 14, "min_cvss": 7.0 }, 33 | {"name": "risky", "max_implement_time": 21, "min_cvss": 5.0 }, 34 | {"name": "regular", "max_implement_time": 28, "min_cvss": 2.5 }, 35 | {"name": "low", "max_implement_time": 31, "min_cvss": 0 }] 36 | # Only edit the score, not the name: 37 | def_ticket_urgencies=[{"name": "exploit-kits", "score":20}, 38 | {"name": "exploit-framework", "score":10}, 39 | {"name": "exploit-scripts", "score":5 }, 40 | {"name": "no_known_exploits", "score":1 }] 41 | # Use the access type names from def_access_rights (below) 42 | # Statistics are read only 43 | def_roles=[{"name": "reviewer", "ticket_access": "write-team", "system_group_access": "read-team", "statistics_access": "read-team", "team_access": "read-team", "db_access": False}, 44 | {"name": "teamleader", "ticket_access": "read-team", "system_group_access": "write-team", "statistics_access": "read-team", "team_access": "write-team", "db_access": False}, 45 | {"name": "management", "ticket_access": "none", "system_group_access": "none", "statistics_access": "read-all", "team_access": "none", "db_access": False}, 46 | {"name": "db-engineer", "ticket_access": "write-all", "system_group_access": "write-all", "statistics_access": "read-all", "team_access": "write-all", "db_access": True}] 47 | ############################### 48 | # DO NOT EDIT THESE CONSTANTS # 49 | ############################### 50 | def_ticket_resolutions=["upgrade", "merge", "remove", "patch"] 51 | def_access_rights=["none", "read-team", "write-team", "read-all", "write-all"] 52 | def_teams=[["-", "Placeholder for roles that don't have teams"]] 53 | 54 | def defaultValues(conn): 55 | import lib.database.VulnManager as vmdb 56 | for x in def_ticket_statusses: vmdb.addTicket_Status(x) 57 | for x in def_ticket_resolutions: vmdb.addTicket_Resolution(x) 58 | for x in def_access_rights: vmdb.addAccess_Right(x) 59 | for x in def_ticket_priorities: vmdb.addTicket_Priority(x["name"], x["max_implement_time"], x["min_cvss"]) 60 | for x in def_ticket_urgencies: vmdb.addTicket_Urgency(x["name"], x["score"]) 61 | for x in def_teams: vmdb.addTeam(x[0], x[1]) 62 | for x in def_roles: vmdb.addRole(x["name"], x["ticket_access"], x["system_group_access"], 63 | x["statistics_access"], x["team_access"], x["db_access"]) 64 | 65 | def createTables(conn): 66 | cur = conn.cursor() 67 | # Components 68 | cur.execute("""CREATE TABLE Components( 69 | id serial PRIMARY KEY, 70 | name text NOT NULL UNIQUE);""") 71 | 72 | # System_Group 73 | cur.execute("""CREATE TABLE System_Groups( 74 | id serial PRIMARY KEY, 75 | name text NOT NULL, 76 | team_id int NOT NULL, 77 | info text, 78 | 79 | UNIQUE(name, team_id));""") 80 | 81 | # Tickets 82 | cur.execute("""CREATE TABLE Tickets( 83 | id serial PRIMARY KEY, 84 | history_created_id int UNIQUE, 85 | history_last_id int UNIQUE, 86 | cve text NOT NULL, 87 | system_group_id int NOT NULL, 88 | component_id int NOT NULL, 89 | 90 | UNIQUE(cve, system_group_id, component_id));""") 91 | 92 | 93 | # Ticket_History 94 | cur.execute("""CREATE TABLE Ticket_Histories( 95 | id serial PRIMARY KEY, 96 | ticket_id int NOT NULL, 97 | datetime timestamp NOT NULL, 98 | user_id int, 99 | cvss decimal DEFAULT 0.0, 100 | completion_percentage int DEFAULT 0, 101 | ticket_notes text, 102 | ticket_resolution_id int, 103 | ticket_resolution_fix text, 104 | ticket_status_id int NOT NULL, 105 | ticket_priority_id int, 106 | ticket_urgency_id int);""") 107 | 108 | # Ticket_Statusses 109 | cur.execute("""CREATE TABLE Ticket_Statusses( 110 | id serial PRIMARY KEY, 111 | name text NOT NULL UNIQUE, 112 | description text );""") 113 | 114 | # Ticket_Resolutions 115 | cur.execute("""CREATE TABLE Ticket_Resolutions( 116 | id serial PRIMARY KEY, 117 | name text NOT NULL UNIQUE, 118 | description text );""") 119 | 120 | # Ticket_Priorities 121 | cur.execute("""CREATE TABLE Ticket_Priorities( 122 | id serial PRIMARY KEY, 123 | name text NOT NULL UNIQUE, 124 | max_implement_time int NOT NULL UNIQUE, 125 | min_cvss decimal UNIQUE);""") 126 | 127 | # Ticket_Urgencies 128 | cur.execute("""CREATE TABLE Ticket_Urgencies( 129 | id serial PRIMARY KEY, 130 | name text NOT NULL UNIQUE, 131 | score text NOT NULL );""") 132 | 133 | # Users 134 | cur.execute("""CREATE TABLE Users( 135 | id serial PRIMARY KEY, 136 | user_name text NOT NULL UNIQUE, 137 | first_name text NOT NULL, 138 | last_name text NOT NULL, 139 | password text NOT NULL);""") 140 | 141 | # Team_Types 142 | cur.execute("""CREATE TABLE Roles( 143 | id serial PRIMARY KEY, 144 | name text NOT NULL UNIQUE, 145 | ticket_access_id int NOT NULL, 146 | system_group_access_id int NOT NULL, 147 | statistics_access_id int NOT NULL, 148 | team_access_id int NOT NULL, 149 | db_access boolean NOT NULL);""") 150 | 151 | # Access_Rights 152 | cur.execute("""CREATE TABLE Access_Rights( 153 | id serial PRIMARY KEY, 154 | name text NOT NULL UNIQUE);""") 155 | 156 | # Teams 157 | cur.execute("""CREATE TABLE Teams( 158 | id serial PRIMARY KEY, 159 | name text NOT NULL UNIQUE, 160 | description text NOT NULL);""") 161 | 162 | # Users_in_Teams 163 | cur.execute("""CREATE TABLE Users_in_Team( 164 | user_id int, 165 | team_id int, 166 | role_id int, 167 | 168 | PRIMARY KEY(user_id, team_id, role_id) );""") 169 | 170 | # Components_in_System_Group 171 | cur.execute("""CREATE TABLE Components_in_System_Group( 172 | component_id int, 173 | system_group_id int, 174 | 175 | PRIMARY KEY(component_id, system_group_id) );""") 176 | 177 | conn.commit() 178 | cur.close() 179 | 180 | def addForeignKeys(conn): 181 | cur = conn.cursor() 182 | cur.execute("""ALTER TABLE System_Groups ADD FOREIGN KEY(team_id) REFERENCES Teams(id)""") 183 | 184 | cur.execute("""ALTER TABLE Tickets ADD FOREIGN KEY(component_id) REFERENCES Components(id)""") 185 | cur.execute("""ALTER TABLE Tickets ADD FOREIGN KEY(system_group_id) REFERENCES System_Groups(id)""") 186 | cur.execute("""ALTER TABLE Tickets ADD FOREIGN KEY(history_created_id) REFERENCES Ticket_Histories(id)""") 187 | cur.execute("""ALTER TABLE Tickets ADD FOREIGN KEY(history_last_id) REFERENCES Ticket_Histories(id)""") 188 | 189 | cur.execute("""ALTER TABLE Ticket_Histories ADD FOREIGN KEY(ticket_id) REFERENCES Tickets(id)""") 190 | cur.execute("""ALTER TABLE Ticket_Histories ADD FOREIGN KEY(ticket_status_id) REFERENCES Ticket_Statusses(id)""") 191 | cur.execute("""ALTER TABLE Ticket_Histories ADD FOREIGN KEY(ticket_resolution_id) REFERENCES Ticket_Resolutions(id)""") 192 | cur.execute("""ALTER TABLE Ticket_Histories ADD FOREIGN KEY(user_id) REFERENCES Users(id)""") 193 | cur.execute("""ALTER TABLE Ticket_Histories ADD FOREIGN KEY(ticket_priority_id) REFERENCES Ticket_Priorities(id)""") 194 | cur.execute("""ALTER TABLE Ticket_Histories ADD FOREIGN KEY(ticket_urgency_id) REFERENCES Ticket_Urgencies(id)""") 195 | 196 | cur.execute("""ALTER TABLE Users_in_Team ADD FOREIGN KEY(user_id) REFERENCES Users(id)""") 197 | cur.execute("""ALTER TABLE Users_in_Team ADD FOREIGN KEY(team_id) REFERENCES Teams(id)""") 198 | cur.execute("""ALTER TABLE Users_in_Team ADD FOREIGN KEY(role_id) REFERENCES Roles(id)""") 199 | 200 | cur.execute("""ALTER TABLE Roles ADD FOREIGN KEY(ticket_access_id) REFERENCES Access_Rights(id)""") 201 | cur.execute("""ALTER TABLE Roles ADD FOREIGN KEY(system_group_access_id) REFERENCES Access_Rights(id)""") 202 | cur.execute("""ALTER TABLE Roles ADD FOREIGN KEY(statistics_access_id) REFERENCES Access_Rights(id)""") 203 | cur.execute("""ALTER TABLE Roles ADD FOREIGN KEY(team_access_id) REFERENCES Access_Rights(id)""") 204 | 205 | cur.execute("""ALTER TABLE Components_in_System_Group ADD FOREIGN KEY(system_group_id) REFERENCES System_Groups(id)""") 206 | cur.execute("""ALTER TABLE Components_in_System_Group ADD FOREIGN KEY(component_id) REFERENCES Components(id)""") 207 | 208 | conn.commit() 209 | cur.close() 210 | 211 | try: 212 | conn=conf.getVMpsqlConnection() 213 | createTables(conn) 214 | print("created tables") 215 | addForeignKeys(conn) 216 | print("created foreign keys") 217 | defaultValues(conn) 218 | print("added default values") 219 | print("database creation complete") 220 | 221 | except Exception as e: 222 | print(traceback.format_exc()) 223 | -------------------------------------------------------------------------------- /install/testDataPostgres.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3.3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Database creation script for PostgreSQL database 5 | # 6 | # Copyright (c) 2015 Pieter-Jan Moreels 7 | # 8 | # Software is free software released under the "Original BSD license" 9 | import sys 10 | try: 11 | import os 12 | _runPath = os.path.dirname(os.path.realpath(__file__)) 13 | sys.path.append(os.path.join(_runPath, "..")) 14 | from passlib.hash import pbkdf2_sha256 15 | from lib.Config import Configuration as conf 16 | except Exception as e: 17 | print(e) 18 | sys.exit("Dependencies missing! First run the install script.") 19 | 20 | import traceback 21 | 22 | rounds = conf.getUserEncryptionRounds() 23 | saltLength = conf.getUserSaltLength() 24 | 25 | try: 26 | conn=conf.getVMpsqlConnection() 27 | cur = conn.cursor() 28 | 29 | teams=[ ["Team A", "TestTeam one"], 30 | ["Team B", "TestTeam two"], 31 | ["engineers", "", ] ] 32 | users=[ ["test", "firstname", "lastname", "test", [["Team A", "reviewer" ],["Team B", "reviewer"] ]], 33 | ["teamleader", "John", "Doe", "test", [["Team A", "teamleader" ],["Team A", "reviewer"], ["Team B", "reviewer"]]], 34 | ["engineer", "Foo", "Bar", "test", [["-", "db-engineer"] ]], 35 | ["management", "Foo", "Bar", "test", [["-", "management" ] ]]] 36 | systemgroups=[ ["DNS", "Team A", "Our DNS Servers", ["cpe:2.3:a:libssh:libssh:2.3", 37 | "cpe:2.3:a:openssl:openssl:1.0.1e", 38 | "cpe:2.3:a:haxx:curl:7.31.0" ]], 39 | ["Proxy", "Team A", "", ["cpe:2.3:a:gnu:gnutls:", 40 | "cpe:2.3:a:haxx:curl:7.31.0", 41 | "cpe:2.3:a:openssl:openssl:1.0.1e" ]], 42 | ["Proxy", "Team B", "", ["cpe:2.3:a:gnu:gnutls:", 43 | "cpe:2.3:a:apache:xerces-c%2b%2b", 44 | "cpe:2.3:a:redhat:richfaces", 45 | "cpe:2.3:a:openssl:openssl:1.0.1e" ]], 46 | ["Citrix", "engineers", "Some Servers", ["cpe:/o:microsoft:windows_server_2008:", 47 | "cpe:/a:citrix:netscaler", 48 | "cpe:/a:mcafee:virusscan_enterprise" ]]] 49 | 50 | for x in teams: 51 | cur.execute("""INSERT INTO Teams VALUES(DEFAULT, %s, %s) RETURNING id;""" , (x[0], x[1])) 52 | conn.commit() 53 | for x in users: 54 | pwd=pbkdf2_sha256.encrypt(x[3], rounds=rounds, salt_size=saltLength) 55 | cur.execute("""INSERT INTO Users VALUES(DEFAULT, %s, %s, %s, %s) RETURNING id""", (x[0], x[1], x[2], pwd)) 56 | uid=cur.fetchone()[0] 57 | cur.execute("""SELECT id, name FROM Roles""") 58 | roles={x[1]:x[0] for x in cur.fetchall()} 59 | for y in x[4]: 60 | cur.execute("""INSERT INTO Users_in_Team (user_id, team_id, role_id) 61 | SELECT %s, id, %s 62 | FROM Teams 63 | WHERE name = %s;""" , (uid, roles[y[1]], y[0])) 64 | conn.commit() 65 | for x in systemgroups: 66 | cur.execute("""INSERT INTO System_Groups (name, team_id, info) 67 | SELECT %s, id, %s 68 | FROM Teams 69 | WHERE name = %s 70 | RETURNING id;""" , (x[0], x[2], x[1])) 71 | groupID=cur.fetchall()[0][0] 72 | conn.commit() 73 | for c in x[3]: 74 | cur.execute("""with s as ( 75 | select id, name 76 | from Components 77 | where name = %s 78 | ), i as ( 79 | insert into Components (name) 80 | select %s 81 | where not exists (select 1 from s) 82 | returning id, name 83 | ) 84 | select id, name from i 85 | union all 86 | select id, name from s; """,(c, c)) 87 | componentID=cur.fetchall()[0][0] 88 | try: 89 | cur.execute("""INSERT INTO Components_in_System_Group VALUES(%s, %s);""",(componentID, groupID)) 90 | except: 91 | pass 92 | 93 | 94 | conn.commit() 95 | except Exception as e: 96 | print(traceback.format_exc()) 97 | -------------------------------------------------------------------------------- /lib/Config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3.3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Config reader to read the configuration file 5 | # 6 | # Copyright (c) 2014-2015 Pieter-Jan Moreels 7 | # 8 | # Software is free software released under the "Original BSD license" 9 | 10 | # Imports 11 | import sys 12 | import os 13 | runPath = os.path.dirname(os.path.realpath(__file__)) 14 | try: 15 | import psycopg2 16 | import pymongo 17 | except: 18 | sys.exit("Dependencies missing! First run the install script.") 19 | 20 | import configparser 21 | 22 | class Configuration(): 23 | ConfigParser = configparser.ConfigParser() 24 | ConfigParser.read(os.path.join(runPath, "../etc/configuration.ini")) 25 | default={'csMongoHost': 'localhost', 'csMongoPort': 27017, 'csMongoDB': 'cvedb', 26 | 'vmHost': 'localhost', 'vmPort': 5432, 'VMDB': 'vulnmanager', 27 | 'FlaskHost':'localhost', 'FlaskPort':5001, 'FlaskDebug':True, 28 | 'ssl': False, 'sslCertificate': "./ssl/cve-search.crt", 29 | 'sslKey': "./ssl/cve-search.crt", 30 | 'UserDBRounds':8000 , 'UserDBSaltLength':10} 31 | 32 | @classmethod 33 | def readSetting(cls, section, item, default): 34 | result = default 35 | try: 36 | if type(default) == bool: 37 | result = cls.ConfigParser.getboolean(section, item) 38 | elif type(default) == int: 39 | result = cls.ConfigParser.getint(section, item) 40 | elif type(default) == list: 41 | result = cls.ConfigParser.get(section, item).split(",") 42 | else: 43 | result = cls.ConfigParser.get(section, item) 44 | except: 45 | pass 46 | return result 47 | 48 | # Mongo - CVE-Search 49 | @classmethod 50 | def getCSMongoDB(cls): 51 | return cls.readSetting("CVE-Search", "DB", cls.default['csMongoDB']) 52 | @classmethod 53 | def getCSMongoConnection(cls): 54 | mongoHost = cls.readSetting("CVE-Search", "Host", cls.default['csMongoHost']) 55 | mongoPort = cls.readSetting("CVE-Search", "Port", cls.default['csMongoPort']) 56 | mongoDB = cls.getCSMongoDB() 57 | try: 58 | connect = pymongo.MongoClient(mongoHost, mongoPort) 59 | except: 60 | sys.exit("Unable to connect to Mongo. Is it running on %s:%s?"%(mongoHost,mongoPort)) 61 | return connect[mongoDB] 62 | 63 | # Mongo - Vulnerability Manager 64 | @classmethod 65 | def getVMDB(cls): 66 | return cls.readSetting("Vulnerability-Manager", "DB", cls.default['VMDB']) 67 | @classmethod 68 | def getVMMongoConnection(cls): 69 | mongoHost = cls.readSetting("Vulnerability-Manager", "Host", cls.default['vmHost']) 70 | mongoPort = cls.readSetting("Vulnerability-Manager", "Port", cls.default['vmPort']) 71 | try: 72 | connect = pymongo.MongoClient(mongoHost, mongoPort) 73 | except: 74 | sys.exit("Unable to connect to Mongo. Is it running on %s:%s?"%(mongoHost,mongoPort)) 75 | return connect[cls.getVMDB()] 76 | 77 | # PostgreSQL - Vulnerability Manager 78 | @classmethod 79 | def getVMpsqlConnection(cls): 80 | h = cls.readSetting("Vulnerability-Manager", "Host", cls.default['vmHost']) 81 | p = cls.readSetting("Vulnerability-Manager", "Port", cls.default['vmPort']) 82 | d = cls.getVMDB() 83 | try: 84 | conn = psycopg2.connect("host='%s' port='%s' dbname=%s user=vulnmanager password=vulnmanager"%(h, p, d)) 85 | except Exception as e: 86 | print(e) 87 | sys.exit("Unable to connect to PostgreSQL. Is it running on %s:%s?"%(h,p)) 88 | return conn 89 | 90 | # Encryption for users 91 | @classmethod 92 | def getUserEncryptionRounds(cls): 93 | return cls.readSetting("User-DB", "EncryptionRounds", cls.default['UserDBRounds']) 94 | @classmethod 95 | def getUserSaltLength(cls): 96 | return cls.readSetting("User-DB", "SaltLength", cls.default['UserDBSaltLength']) 97 | 98 | # Flask 99 | @classmethod 100 | def getFlaskHost(cls): 101 | return cls.readSetting("Server", "Host", cls.default['FlaskHost']) 102 | @classmethod 103 | def getFlaskPort(cls): 104 | return cls.readSetting("Server", "Port", cls.default['FlaskPort']) 105 | @classmethod 106 | def getFlaskDebug(cls): 107 | return cls.readSetting("Server", "Debug", cls.default['FlaskDebug']) 108 | 109 | # SSL 110 | @classmethod 111 | def useSSL(cls): 112 | return cls.readSetting("Webserver", "SSL", cls.default['ssl']) 113 | @classmethod 114 | def getSSLCert(cls): 115 | return os.path.join(runPath, "..", cls.readSetting("Webserver", "Certificate", cls.default['sslCertificate'])) 116 | @classmethod 117 | def getSSLKey(cls): 118 | return os.path.join(runPath, "..", cls.readSetting("Webserver", "Key", cls.default['sslKey'])) 119 | -------------------------------------------------------------------------------- /lib/Toolkit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3.3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Toolkit for functions between scripts 5 | # 6 | # Software is free software released under the "Modified BSD license" 7 | # 8 | # Copyright (c) 2014-2015 Pieter-Jan Moreels 9 | 10 | # Note of warning: CPEs like cpe:/o:microsoft:windows_8:-:-:x64 are given to us by Mitre 11 | # x64 will be parsed as Edition in this case, not Architecture 12 | def toStringFormattedCPE(cpe,autofill=False): 13 | cpe=cpe.strip() 14 | if not cpe.startswith('cpe:2.3:'): 15 | if not cpe.startswith('cpe:/'): return False 16 | cpe=cpe.replace('cpe:/','cpe:2.3:') 17 | cpe=cpe.replace('::',':-:') 18 | cpe=cpe.replace('~-','~') 19 | cpe=cpe.replace('~',':-:') 20 | cpe=cpe.replace('::',':') 21 | cpe=cpe.strip(':-') 22 | if autofill: 23 | e=cpe.split(':') 24 | for x in range(0,13-len(e)): 25 | cpe+=':-' 26 | return cpe 27 | 28 | # Function to take either the xth element from list y, or key x from dict y 29 | # Returns None if x or y is none, key is not found, index x out of range or not a valid type 30 | def xFromy(x, y): 31 | try: 32 | return y[x] 33 | except: 34 | return None 35 | 36 | def addToListOfDicts(lst, key, val): 37 | return ([x.update({key: val}) for x in lst],lst)[1] 38 | -------------------------------------------------------------------------------- /lib/database/CVESearch.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3.3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Interface for CVE-Search 5 | # > Written for MongoDB 6 | # 7 | # Copyright (c) 2015 Pieter-Jan Moreels 8 | # 9 | # Software is free software released under the "Original BSD license" 10 | 11 | # Imports 12 | import re 13 | 14 | from lib.Config import Configuration as conf 15 | 16 | # Variables 17 | csdb = conf.getCSMongoConnection() 18 | cves = csdb.cves 19 | 20 | # Get 21 | def getCVEInfo(cve): 22 | info=cves.find_one({"id":cve}) 23 | if info: info.pop("_id") 24 | try: 25 | info["cvss"]=float(info["cvss"]) 26 | except: 27 | pass 28 | return info 29 | 30 | def getVulns(cpes, limit): 31 | if not cpes: return [] 32 | regexes = [re.compile(re.escape(x)) for x in cpes] 33 | return list(cves.find({'vulnerable_configuration': {"$in":regexes}}).sort("Modified", -1).limit(limit)) 34 | 35 | def isVulnerable(cpe): 36 | return True if len(list(cves.find({'vulnerable_configuration': cpe}).limit(1)))==1 else False 37 | -------------------------------------------------------------------------------- /lib/database/Users.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3.3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Interface for users in the log-in system 5 | # > Written for MongoDB 6 | # 7 | # Copyright (c) 2015 Pieter-Jan Moreels 8 | # 9 | # Software is free software released under the "Original BSD license" 10 | 11 | # Imports 12 | from passlib.hash import pbkdf2_sha256 13 | 14 | from lib.Config import Configuration as conf 15 | 16 | # Variables 17 | conn = conf.getVMpsqlConnection() 18 | 19 | rounds = conf.getUserEncryptionRounds() 20 | saltLength = conf.getUserSaltLength() 21 | 22 | # Functions 23 | # Decorators 24 | def cursor_wrapped(func): 25 | def func_wrapper(*args, **kwargs): 26 | cur=conn.cursor() 27 | result = func(cur, *args, **kwargs) 28 | cur.close() 29 | return result 30 | return func_wrapper 31 | 32 | @cursor_wrapped 33 | def getUser(cur, username): 34 | cur.execute("""SELECT * FROM Users WHERE user_name=%s;""", (username,)) 35 | c = cur.fetchone() 36 | if not c: return None 37 | teams={} 38 | cur.execute("""SELECT id, name from Teams;""") 39 | t={x[0]: x[1] for x in cur.fetchall()} 40 | cur.execute("""SELECT * from Roles;""") 41 | r={x[0]: [x[1], x[2], x[3], x[4], x[5], x[6]] for x in cur.fetchall()} 42 | cur.execute("""SELECT * from Access_Rights;""") 43 | a={x[0]: x[1] for x in cur.fetchall()} 44 | cur.execute("""SELECT team_id, role_id from Users_in_Team WHERE user_id=%s;""", (c[0],)) 45 | uit=cur.fetchall() 46 | for x in uit: 47 | if x[0] in teams: 48 | teams[x[0]]["roles"].append({"name": r[x[1]][0], 49 | "access":{ "ticket":a[r[x[1]][1]], "system_group": a[r[x[1]][2]], "statistics": a[r[x[1]][3]], "team":a[r[x[1]][4]], "db":r[x[1]][5]}}) 50 | else: 51 | teams[x[0]]={"name":t[x[0]], "roles":[ {"name": r[x[1]][0], 52 | "access":{ "ticket":a[r[x[1]][1]], "system_group": a[r[x[1]][2]], "statistics": a[r[x[1]][3]], "team":a[r[x[1]][4]], "db":r[x[1]][5]}}]} 53 | return {"username": username, "first_name": c[2], "last_name": c[3], "teams": teams.values()} 54 | 55 | @cursor_wrapped 56 | def checkPass(cur, name, pw): 57 | cur.execute("""SELECT * FROM Users WHERE user_name=%s;""", (name,)) 58 | user = cur.fetchone() 59 | return True if user and pbkdf2_sha256.verify(pw, user[4]) else False 60 | 61 | @cursor_wrapped 62 | def addUser(cur, user): 63 | # TODO all user stuff 64 | u=user.getDict(); 65 | pwd=pbkdf2_sha256.encrypt(u["password"], rounds=rounds, salt_size=saltLength) 66 | try: 67 | groupID=cur.execute("""INSERT INTO Users VALUES(DEFAULT, %s, first_name, last_name, password);""" , (u['userID'], u['notes'], j['team'])) 68 | return True 69 | except: 70 | return False 71 | -------------------------------------------------------------------------------- /lib/database/VulnManager/Add.py: -------------------------------------------------------------------------------- 1 | @cursor_wrapped 2 | def addComponent(cur, component): 3 | if type(component) is not str: raise(InvalidVariableTypes) 4 | cur.execute("""WITH s as ( 5 | SELECT id, name 6 | FROM Components 7 | WHERE name = %s 8 | ), i AS ( 9 | INSERT INTO Components (name) 10 | SELECT %s 11 | WHERE NOT EXISTS (SELECT 1 FROM s) 12 | RETURNING id, name 13 | ) SELECT id, name FROM i 14 | UNION ALL 15 | SELECT id, name FROM s; """,(component, component)) 16 | vmdb.commit() 17 | return cur.fetchone()[0] 18 | 19 | @cursor_wrapped 20 | def addSystemGroup(cur, _group): 21 | if type(_group) is not SystemGroup: raise(InvalidVariableTypes) 22 | j=_group.getDict() 23 | try: 24 | groupID=cur.execute("""INSERT INTO System_Groups (name, team_id, info) 25 | SELECT %s, id, %s 26 | FROM Teams 27 | WHERE name = %s 28 | RETURNING id;""" , (j['groupName'], j['notes'], j['team'])) 29 | for c in j["components"]: 30 | componentID=addComponent(c) 31 | try: 32 | cur.execute("""INSERT INTO Components_in_System_Group VALUES(%s, %s);""",(componentID, groupID)) 33 | except: 34 | pass 35 | vmdb.commit() 36 | return groupID 37 | except Exception as e: 38 | print(traceback.format_exc()) 39 | return None 40 | 41 | @cursor_wrapped 42 | def addTicket(cur, _ticket): 43 | if type(_ticket) is not Ticket: raise(InvalidVariableTypes) 44 | t=_ticket.getDict() 45 | try: 46 | cur.execute("""SELECT id FROM System_Groups WHERE name = %s AND team_id IN (SELECT id FROM Teams WHERE name = %s);""",(t["groupName"], t["team"])) 47 | sgid=cur.fetchone()[0] 48 | cur.execute("""SELECT id FROM Components WHERE name = %s;""",(t["vulnComp"]["cpe"],)) 49 | cid=cur.fetchone()[0] 50 | cur.execute("""SELECT id FROM Teams WHERE name = %s;""",(t["team"],)) 51 | tid=cur.fetchone()[0] 52 | priority = calculatePriority(t) 53 | info = cvedb.getCVEInfo(t["cve"]) 54 | cur.execute("""INSERT INTO Tickets VALUES (DEFAULT, %s, %s, %s, %s, %s, %s) RETURNING ID;""",(None, None, t["cve"], sgid, cid, tid)) 55 | ticket_id=cur.fetchone()[0] 56 | cur.execute("""INSERT INTO Ticket_Histories(ticket_id, datetime, cvss, ticket_status_id, ticket_priority_id) VALUES(%s, %s, %s, %s, %s) RETURNING ID;""", 57 | (ticket_id, datetime.fromtimestamp(time.time()), info["cvss"], _DEFAULT_STATUS, priority)) 58 | history=cur.fetchone()[0] 59 | cur.execute("""UPDATE Tickets SET history_created_id=%s, history_last_id=%s WHERE id=%s"""%(ticket_id, ticket_id, history)) 60 | vmdb.commit() 61 | return ticket_id 62 | except Exception as e: 63 | print(traceback.format_exc()) 64 | return None 65 | 66 | @cursor_wrapped 67 | def addUser(cur, _user): 68 | # TODO all user stuff 69 | u=_user.getDict(); 70 | pwd=pbkdf2_sha256.encrypt(u["password"], rounds=rounds, salt_size=saltLength) 71 | try: 72 | groupID=cur.execute("""INSERT INTO Users VALUES(DEFAULT, %s, first_name, last_name, password);""" , (u['userID'], u['notes'], j['team'])) 73 | return groupID 74 | except: 75 | return None 76 | 77 | @cursor_wrapped 78 | def addTicket_Status(cur, status): 79 | if type(status) is not str: raise(InvalidVariableTypes) 80 | cur.execute("INSERT INTO Ticket_Statusses VALUES(DEFAULT, %s) RETURNING id",(status,)) 81 | vmdb.commit() 82 | return cur.fetchone()[0] 83 | 84 | @cursor_wrapped 85 | def addTicket_Resolution(cur, resolution): 86 | if type(resolution) is not str: raise(InvalidVariableTypes) 87 | cur.execute("INSERT INTO Ticket_Resolutions VALUES(DEFAULT, %s) RETURNING id",(resolution,)) 88 | vmdb.commit() 89 | return cur.fetchone()[0] 90 | 91 | @cursor_wrapped 92 | def addAccess_Right(cur, access_right): 93 | if type(access_right) is not str: raise(InvalidVariableTypes) 94 | cur.execute("INSERT INTO Access_Rights VALUES(DEFAULT, %s) RETURNING id",(access_right,)) 95 | vmdb.commit() 96 | return cur.fetchone()[0] 97 | 98 | @cursor_wrapped 99 | def addTicket_Priority(cur, name, max_implement_time, min_cvss): 100 | if type(min_cvss) is int: min_cvss = float(min_cvss) 101 | if ((type(name) is not str) or (type(max_implement_time) is not int) or 102 | (type(min_cvss) is not float and type(min_cvss) is not decimal.Decimal and 103 | min_cvss is not None)): 104 | raise(InvalidVariableTypes) 105 | cur.execute("INSERT INTO Ticket_Priorities VALUES(DEFAULT, %s, %s, %s) RETURNING id", 106 | (name, max_implement_time, min_cvss)) 107 | vmdb.commit() 108 | return cur.fetchone()[0] 109 | 110 | @cursor_wrapped 111 | def addTicket_Urgency(cur, name, score): 112 | if ((type(name) is not str) or (type(score) is not int)): raise(InvalidVariableTypes) 113 | cur.execute("INSERT INTO Ticket_Urgencies VALUES(DEFAULT, %s, %s) RETURNING id",(name, score)) 114 | vmdb.commit() 115 | return cur.fetchone()[0] 116 | 117 | @cursor_wrapped 118 | def addTeam(cur, name, description): 119 | if ((type(name) is not str) or (type(description) is not str)): raise(InvalidVariableTypes) 120 | cur.execute("INSERT INTO Teams VALUES(DEFAULT, %s, %s) RETURNING id",(name, description)) 121 | vmdb.commit() 122 | return cur.fetchone()[0] 123 | 124 | @cursor_wrapped 125 | def addRole(cur, name, ticket_access, system_group_access, statistics_access, team_access, db_access): 126 | a = getAccessRights() 127 | if (not all(isinstance(x,str) for x in [name, ticket_access, system_group_access, statistics_access, team_access]) or 128 | (type(db_access) is not bool)): raise(InvalidVariableTypes) 129 | try: 130 | cur.execute("INSERT INTO Roles VALUES(DEFAULT, %s, %s, %s, %s, %s, %s);", 131 | (name, a[ticket_access], a[system_group_access], a[statistics_access], a[team_access], db_access)) 132 | vmdb.commit() 133 | return cur.fetchone()[0] 134 | except: 135 | return None 136 | -------------------------------------------------------------------------------- /lib/database/VulnManager/Calculations.py: -------------------------------------------------------------------------------- 1 | def calculatePriority(_ticket): 2 | priority=None 3 | try: 4 | cvss=float(_ticket.cveInfo["cvss"]) 5 | except: 6 | return None 7 | # TODO might to reverse loop to fix None problem 8 | for x in getPriorityLevels(): 9 | if x[1]= start and aDate < end: 38 | if action["status"] == _DEFAULT_STATUS_TEXT: 39 | new+=1 40 | opened=True 41 | elif action["status"].lower().startswith("closed"): 42 | if opened: 43 | oac+=1 44 | new-=1 45 | else: 46 | closed+=1 47 | systems.append({"systems":x["groupName"], "new": new, "closed": closed, "openedAndClosed":oac}) 48 | return systems 49 | 50 | @cursor_wrapped 51 | def getTeamDicts(cur): 52 | cur.execute("SELECT id, name FROM Teams;") 53 | return {x[0]: x[1] for x in cur.fetchall()} 54 | 55 | @cursor_wrapped 56 | def getAccessRights(cur): 57 | cur.execute("SELECT * FROM Access_Rights;") 58 | return {v: k for k, v in dict(cur.fetchall()).items()} 59 | -------------------------------------------------------------------------------- /lib/database/VulnManager/Get-IDs.py: -------------------------------------------------------------------------------- 1 | # Object ID's 2 | @cursor_wrapped 3 | def getSystemGroupID(cur, name, team): 4 | cur.execute("""SELECT System_Groups.id 5 | FROM System_Groups, Teams 6 | WHERE System_Groups.name = %s 7 | and Teams.name = %s 8 | and System_Groups.team_id = Teams.id;""", (name, team)) 9 | sid=cur.fetchone() 10 | return sid[0] if sid else None 11 | 12 | 13 | @cursor_wrapped 14 | def getComponentID(cur, name): 15 | cur.execute("""SELECT id FROM Components WHERE name = %s;""", (name, )) 16 | cid=cur.fetchone() 17 | return cid[0] if cid else None 18 | 19 | 20 | # List of ID's 21 | @cursor_wrapped 22 | def getPriorityLevels(cur): 23 | cur.execute("""SELECT id, min_cvss FROM Ticket_Priorities WHERE min_cvss IS NOT NULL;""") 24 | # Sort from highest CVSS to lowest 25 | priorities=sorted(cur.fetchall(),key=lambda x: x[1]) 26 | return priorities 27 | -------------------------------------------------------------------------------- /lib/database/VulnManager/Get-Names.py: -------------------------------------------------------------------------------- 1 | @cursor_wrapped 2 | def getComponentNames(cur, team=None, system=None): 3 | if team and system: 4 | cur.execute("""SELECT name FROM Components WHERE id IN ( 5 | SELECT component_ID FROM Components_in_System_Group WHERE system_group_id IN ( 6 | SELECT id FROM System_Group WHERE team_id IN ( 7 | SELECT id FROM Teams WHERE name = %s ) AND name = %s;));""", (team,system)) 8 | else: 9 | cur.execute("""SELECT name FROM Components;""") 10 | c=[x[0] for x in cur.fetchall()] 11 | return sorted(list(set(c)), key=lambda s: s.lower()) 12 | 13 | 14 | @cursor_wrapped 15 | def getCPENames(cur, team=None): 16 | query="SELECT name FROM Components " 17 | if team: 18 | query+="""WHERE id IN( 19 | SELECT component_id FROM Components_in_System_Group WHERE system_group_id IN( 20 | SELECT id from System_Groups WHERE team_id IN( 21 | SELECT id from Teams where name = %s)))""" 22 | cur.execute(query, list(filter(None, [team]))) 23 | cpes=[x[0] for x in cur.fetchall()] 24 | return list(set(itertools.chain(*cpes))) 25 | 26 | 27 | @cursor_wrapped 28 | def getStatusses(cur): 29 | cur.execute("""SELECT name FROM Ticket_Statusses;""") 30 | return [x[0] for x in cur.fetchall()] 31 | 32 | 33 | @cursor_wrapped 34 | def getTeamNames(cur): 35 | cur.execute("SELECT name FROM Teams;") 36 | return [x[0] for x in cur.fetchall()] 37 | -------------------------------------------------------------------------------- /lib/database/VulnManager/Get-Objects.py: -------------------------------------------------------------------------------- 1 | @cursor_wrapped 2 | def getTicket(cur, cve, cpe, system, team): 3 | cur.execute("""SELECT Tickets.id, Teams.name, System_Groups.name, Tickets.cve, Components.name, 4 | Ticket_Statusses.name, Ticket_Histories.ticket_notes, Ticket_Histories.ticket_resolution_id, 5 | Ticket_Histories.ticket_resolution_fix, Ticket_Histories.cvss, Ticket_Histories.ticket_priority_id 6 | FROM Tickets, System_Groups, Components, Teams, Ticket_Histories, Ticket_Resolutions, Ticket_Statusses 7 | WHERE cve=%s 8 | AND component_id IN ( SELECT id FROM Components WHERE name = %s ) 9 | AND system_group_id IN ( SELECT id FROM System_Groups WHERE name = %s 10 | AND team_id IN ( SELECT id FROM Teams WHERE name = %s )) 11 | AND Teams.id = System_Groups.team_id 12 | AND System_Groups.id = Tickets.system_group_id 13 | AND Components.id = Tickets.component_id 14 | AND Ticket_Histories.id = Tickets.history_last_id 15 | AND Ticket_Statusses.id = Ticket_Histories.ticket_status_id;""", (cve, cpe, system, team)) 16 | data=cur.fetchone() 17 | c=list(data) if data else data 18 | 19 | if c: 20 | if c[10]: 21 | cur.execute("SELECT name FROM Ticket_Priorities where id=%s", (c[10],)) 22 | priority=(cur.fetchone())[0] 23 | else: 24 | priority=None 25 | cur.execute("SELECT name FROM Ticket_Resolutions") 26 | t_res= [x[0] for x in cur.fetchall()] 27 | cur.execute("SELECT name FROM Ticket_Priorities") 28 | t_prior= [x[0] for x in cur.fetchall()] 29 | cur.execute("SELECT name FROM Ticket_Urgencies") 30 | t_urgent= [x[0] for x in cur.fetchall()] 31 | cur.execute("""SELECT datetime, Users.user_name, cvss, ticket_notes, Ticket_Statusses.name, ticket_resolution_id, ticket_resolution_fix, 32 | ticket_priority_id, ticket_urgency_id 33 | FROM Ticket_Histories, Ticket_Statusses, Users 34 | WHERE ticket_id=%s 35 | AND Ticket_Statusses.id = Ticket_Histories.ticket_status_id 36 | AND Users.id = Ticket_Histories.user_id;""", (c[0],)) 37 | histories=cur.fetchall() 38 | h=[] 39 | for x in histories: 40 | h.append({"time": x[0], "user": x[1], "cvss": x[2], "notes": x[3], "status": x[4], "update": {"type": xFromy(x[5], t_res), "cpe": x[6]}, 41 | "priority": xFromy(x[7], t_prior), "urgency": xFromy(x[8], t_urgent)}) 42 | if c[7]: 43 | cur.execute("""SELECT name FROM Ticket_Resolutions WHERE id = %s""", (c[7],)) 44 | c[7]=cur.fetchone()[0] 45 | c=Ticket(c[1], c[2], c[3], c[4], status=c[5], notes=c[6], history=h, resolution=c[7], resolution_fix=c[8], cvss=c[9], priority=priority, objID=c[0]) 46 | else: 47 | tick=Ticket(team, system, cve, cpe) 48 | addTicket(tick) 49 | c=getTicket(tick.cve, tick.vulnComp["cpe"], tick.groupName, tick.team) 50 | c.addInfo(cvedb.getCVEInfo(cve)) 51 | updatePriority(c) 52 | return c 53 | 54 | 55 | @cursor_wrapped 56 | def getTickets(cur, team, system=None, limit=None): 57 | # Build up statement 58 | command = """SELECT Tickets.cve, Components.name, System_Groups.name, Teams.name 59 | FROM Tickets, System_Groups, Components, Teams, Ticket_histories 60 | WHERE System_Groups.id = Tickets.system_group_id 61 | AND Components.id = Tickets.component_id 62 | AND Teams.id = System_Groups.team_id 63 | AND Ticket_Histories.id = Tickets.history_last_id """ 64 | if limit and limit.lower()=="open": limit = """AND Ticket_Histories.ticket_status_id IN 65 | ( SELECT id FROM Ticket_Statusses WHERE name NOT LIKE 'closed-%%' ) """ 66 | elif limit and limit.lower()=="closed": limit = """AND Ticket_Histories.ticket_status_id IN 67 | ( SELECT id FROM Ticket_Statusses WHERE name LIKE 'closed-%%' ) """ 68 | else: limit = "" 69 | # Execute statement based on system or all systems 70 | if system: 71 | cur.execute(command + limit + """AND system_group_id IN ( SELECT id FROM System_Groups WHERE name = %s 72 | AND team_id IN ( SELECT id FROM Teams WHERE name = %s ))""", (system, team)) 73 | else: 74 | cur.execute(command + limit + """AND system_group_id IN ( SELECT id FROM System_Groups WHERE team_id 75 | IN ( SELECT id FROM Teams WHERE name = %s ))""", (team, )) 76 | return [getTicket(x[0],x[1],x[2],x[3]) for x in cur.fetchall()] 77 | 78 | 79 | @cursor_wrapped 80 | def getVulnsForSystem(cur, groupName, systemTeam, cpes=None): 81 | if type(cpes) is not list and cpes:cpes=[cpes] 82 | if not cpes: 83 | cur.execute("""SELECT name FROM Components WHERE id IN ( 84 | SELECT component_id FROM Components_in_System_Group WHERE system_group_id IN ( 85 | SELECT id FROM System_Groups WHERE name = %s AND team_id IN ( 86 | SELECT id FROM Teams WHERE name = %s )));""",(groupName, systemTeam)) 87 | cpes=[x[0] for x in cur.fetchall()] 88 | 89 | # Get tickets for every cve for the vulnerable systems 90 | tickets=[] 91 | for cpe in cpes: 92 | tickets.extend([getTicket(cve, cpe, groupName, systemTeam) for cve in [x["id"] for x in cvedb.getVulns([cpe], 0)]]) 93 | tickets=[x for x in tickets if not x.status.lower().startswith("closed")] 94 | return tickets 95 | 96 | 97 | @cursor_wrapped 98 | def getGroups(cur, team, group=None): 99 | teams=getTeamDicts() 100 | groups=[] 101 | query = """SELECT * FROM System_Groups WHERE team_id IN (SELECT id FROM Teams WHERE name=%s)""" 102 | if group: query+= "AND name=%s;" 103 | cur.execute(query, list(filter(None, [team, group]))) 104 | for g in cur.fetchall(): 105 | clist=[] 106 | cur.execute("""SELECT * FROM Components WHERE id IN ( 107 | SELECT component_ID FROM Components_in_System_Group WHERE system_group_id = %s );""", (g[0],)) 108 | for comp in cur.fetchall(): 109 | clist.append(Component(comp[1])) 110 | groups.append(SystemGroup(None, g[1], teams[g[2]], None, [], clist)) 111 | return groups 112 | 113 | 114 | @cursor_wrapped 115 | def getGroupDetails(cur, group): 116 | cur.execute("""SELECT name FROM Teams WHERE id=%s;""", (group[2],)) 117 | tid=cur.fetchone()[0] 118 | clist=[] 119 | cur.execute("""SELECT * FROM Components WHERE id IN ( SELECT component_ID FROM Components_in_System_Group WHERE system_group_id = %s );""", (group[0],)) 120 | for comp in cur.fetchall(): 121 | clist.append(Component(comp[1])) 122 | return SystemGroup(None, group[1], tid, None, [], clist) 123 | 124 | 125 | @cursor_wrapped 126 | def getTicketsForSystemComponent(cur, component, _system): 127 | tickets=getTickets(team, _system.groupName) 128 | #for x in tickets: 129 | # print(x) 130 | #tickets.append(getTicket(x[0], x[1], x[2], x[3])) 131 | return tickets 132 | 133 | -------------------------------------------------------------------------------- /lib/database/VulnManager/Head.py: -------------------------------------------------------------------------------- 1 | # Imports 2 | import copy 3 | import decimal 4 | import itertools 5 | import re 6 | import time 7 | import traceback 8 | import sys 9 | from datetime import date 10 | from datetime import datetime 11 | from passlib.hash import pbkdf2_sha256 12 | 13 | from lib.objects import SystemGroup, Component, Ticket 14 | from lib.Config import Configuration as conf 15 | from lib.exceptions import InvalidVariableTypes 16 | from lib.Toolkit import xFromy 17 | 18 | import lib.database.CVESearch as cvedb 19 | 20 | # Variables 21 | vmdb = conf.getVMpsqlConnection() 22 | 23 | # Decorators 24 | def cursor_wrapped(func): 25 | def func_wrapper(*args, **kwargs): 26 | cur=vmdb.cursor() 27 | result = func(cur, *args, **kwargs) 28 | cur.close() 29 | return result 30 | return func_wrapper 31 | 32 | -------------------------------------------------------------------------------- /lib/database/VulnManager/Tail.py: -------------------------------------------------------------------------------- 1 | # Constants 2 | statusses = getStatusses() 3 | _DEFAULT_STATUS = 1 4 | _DEFAULT_STATUS_TEXT=statusses[0] if statusses else None # None when creating the DB 5 | del statusses 6 | -------------------------------------------------------------------------------- /lib/database/VulnManager/Update.py: -------------------------------------------------------------------------------- 1 | @cursor_wrapped 2 | def setDBTicketStatus(cur, ticket, user, status=None, notes=None, update=None, updateType=None, ): 3 | #Allows us to work both with dict and Ticket types 4 | t=ticket.getDict() if type(ticket) == Ticket else ticket 5 | if type(ticket) == Ticket: t=getTicket(t["cve"], t["vulnComp"]["cpe"], t["groupName"], t["team"]).getDict() 6 | 7 | # Prevent unnecesarry database operations 8 | if all(None == i for i in [status, notes, update, updateType]): return False 9 | if status and t["status"] == status.strip(): return False 10 | if notes and t["notes"] == notes.strip(): return False 11 | if update and t["vulnComp"]["update"] == update.strip(): return False 12 | if updateType and t["vulnComp"]["type"] == updateType.strip(): return False 13 | # Fetch the current ticket ID and history 14 | cur.execute("""SELECT id, history_last_id FROM Tickets WHERE cve=%s 15 | AND component_id IN ( SELECT id FROM Components WHERE name = %s ) 16 | AND system_group_id IN ( SELECT id FROM System_Groups WHERE name = %s 17 | AND team_id IN ( SELECT id FROM Teams WHERE name = %s ));""", (t["cve"], t["vulnComp"]["cpe"], t["groupName"], t["team"])) 18 | x=cur.fetchone() 19 | if not x: return False 20 | t_id, h_id = x 21 | # Fetch all the ticket history information 22 | cur.execute("""SELECT * FROM Ticket_Histories WHERE id = %s;""", (h_id,)) 23 | h=cur.fetchone() 24 | # Get ID's of settings, or get the old id's 25 | if status == None: 26 | status=t["status"] 27 | cur.execute("""SELECT id FROM Ticket_Statusses WHERE name = %s;""", (status,)) 28 | status=cur.fetchone()[0] 29 | notes = t["notes"] if notes == None else notes 30 | update = t["vulnComp"]["update"] if update == None else update 31 | if updateType == None: updateType=t["vulnComp"]["type"] 32 | cur.execute("""SELECT id FROM Ticket_Resolutions WHERE name = %s;""", (updateType,)) 33 | updateType=cur.fetchone() 34 | updateType=updateType[0] if updateType else None 35 | # Get user ID 36 | cur.execute("""SELECT id FROM Users WHERE user_name = %s""", (user,)) 37 | uid=cur.fetchone()[0] 38 | # Now make a new entry using above data 39 | now = datetime.fromtimestamp(time.time()) 40 | cur.execute("""INSERT INTO Ticket_Histories VALUES(DEFAULT, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;""", 41 | (h[1], now, uid, h[4], h[5], notes, updateType, update, status, h[10], h[11] )) 42 | last_id=cur.fetchone()[0] 43 | cur.execute("""UPDATE Tickets SET history_last_id = %s WHERE id = %s;""", (last_id, t_id)) 44 | vmdb.commit() 45 | return True 46 | 47 | 48 | @cursor_wrapped 49 | def updateCPE(cur, _ticket): 50 | # temporary disable this function 51 | return 52 | # TODO: Verify if this structure won't cause Race conditions 53 | t=_ticket.getDict() 54 | update=t['vulnComp'] 55 | cid=addComponent(update['update']) 56 | sid=getSystemGroupID(t['groupName'], t['team']) 57 | ocid=getComponentID(update['cpe']) 58 | if update['type'] in ["upgrade", "merge"]: 59 | cur.execute("""UPDATE Components_in_System_Group 60 | SET component_id = %s 61 | WHERE component_id = %s 62 | and system_group_id = %s;""", (cid, ocid, sid)) 63 | vmdb.commit() 64 | elif update["type"] == "remove": 65 | cur.execute("""DELETE FROM Components_in_System_Group 66 | WHERE component_id = %s 67 | and system_group_id = %s;""", (ocid, sid)) 68 | vmdb.commit() 69 | # Else we assume patch, in which case nothing needs to happen, only return cpes. 70 | 71 | # Find list of related tickets 72 | return getTicketsForSystemComponent(update['cpe'], t['groupName'], t['team']) 73 | 74 | 75 | @cursor_wrapped 76 | def updatePriority(cur, _ticket): 77 | cvss=_ticket.cveInfo["cvss"] 78 | if _ticket.cvss == cvss: 79 | return 80 | priority=calculatePriority(ticket) 81 | #if priority 82 | -------------------------------------------------------------------------------- /lib/database/VulnManager/__init-orig__.py: -------------------------------------------------------------------------------- 1 | # -- Head.py -- 2 | # Imports 3 | import copy 4 | import decimal 5 | import itertools 6 | import re 7 | import time 8 | import traceback 9 | import sys 10 | from datetime import date 11 | from datetime import datetime 12 | from passlib.hash import pbkdf2_sha256 13 | 14 | from lib.objects import SystemGroup, Component, Ticket 15 | from lib.Config import Configuration as conf 16 | from lib.exceptions import InvalidVariableTypes 17 | from lib.Toolkit import xFromy 18 | 19 | import lib.database.CVESearch as cvedb 20 | 21 | # Variables 22 | vmdb = conf.getVMpsqlConnection() 23 | 24 | # Decorators 25 | def cursor_wrapped(func): 26 | def func_wrapper(*args, **kwargs): 27 | cur=vmdb.cursor() 28 | result = func(cur, *args, **kwargs) 29 | cur.close() 30 | return result 31 | return func_wrapper 32 | 33 | # -- Add.py -- 34 | @cursor_wrapped 35 | def addComponent(cur, component): 36 | if type(component) is not str: raise(InvalidVariableTypes) 37 | cur.execute("""WITH s as ( 38 | SELECT id, name 39 | FROM Components 40 | WHERE name = %s 41 | ), i AS ( 42 | INSERT INTO Components (name) 43 | SELECT %s 44 | WHERE NOT EXISTS (SELECT 1 FROM s) 45 | RETURNING id, name 46 | ) SELECT id, name FROM i 47 | UNION ALL 48 | SELECT id, name FROM s; """,(component, component)) 49 | vmdb.commit() 50 | return cur.fetchone()[0] 51 | 52 | @cursor_wrapped 53 | def addSystemGroup(cur, _group): 54 | if type(_group) is not SystemGroup: raise(InvalidVariableTypes) 55 | j=_group.getDict() 56 | try: 57 | groupID=cur.execute("""INSERT INTO System_Groups (name, team_id, info) 58 | SELECT %s, id, %s 59 | FROM Teams 60 | WHERE name = %s 61 | RETURNING id;""" , (j['groupName'], j['notes'], j['team'])) 62 | for c in j["components"]: 63 | componentID=addComponent(c) 64 | try: 65 | cur.execute("""INSERT INTO Components_in_System_Group VALUES(%s, %s);""",(componentID, groupID)) 66 | except: 67 | pass 68 | vmdb.commit() 69 | return groupID 70 | except Exception as e: 71 | print(traceback.format_exc()) 72 | return None 73 | 74 | @cursor_wrapped 75 | def addTicket(cur, _ticket): 76 | if type(_ticket) is not Ticket: raise(InvalidVariableTypes) 77 | t=_ticket.getDict() 78 | try: 79 | cur.execute("""SELECT id FROM System_Groups WHERE name = %s AND team_id IN (SELECT id FROM Teams WHERE name = %s);""",(t["groupName"], t["team"])) 80 | sgid=cur.fetchone()[0] 81 | cur.execute("""SELECT id FROM Components WHERE name = %s;""",(t["vulnComp"]["cpe"],)) 82 | cid=cur.fetchone()[0] 83 | cur.execute("""SELECT id FROM Teams WHERE name = %s;""",(t["team"],)) 84 | tid=cur.fetchone()[0] 85 | priority = calculatePriority(t) 86 | info = cvedb.getCVEInfo(t["cve"]) 87 | cur.execute("""INSERT INTO Tickets VALUES (DEFAULT, %s, %s, %s, %s, %s, %s) RETURNING ID;""",(None, None, t["cve"], sgid, cid, tid)) 88 | ticket_id=cur.fetchone()[0] 89 | cur.execute("""INSERT INTO Ticket_Histories(ticket_id, datetime, cvss, ticket_status_id, ticket_priority_id) VALUES(%s, %s, %s, %s, %s) RETURNING ID;""", 90 | (ticket_id, datetime.fromtimestamp(time.time()), info["cvss"], _DEFAULT_STATUS, priority)) 91 | history=cur.fetchone()[0] 92 | cur.execute("""UPDATE Tickets SET history_created_id=%s, history_last_id=%s WHERE id=%s"""%(ticket_id, ticket_id, history)) 93 | vmdb.commit() 94 | return ticket_id 95 | except Exception as e: 96 | print(traceback.format_exc()) 97 | return None 98 | 99 | @cursor_wrapped 100 | def addUser(cur, _user): 101 | # TODO all user stuff 102 | u=_user.getDict(); 103 | pwd=pbkdf2_sha256.encrypt(u["password"], rounds=rounds, salt_size=saltLength) 104 | try: 105 | groupID=cur.execute("""INSERT INTO Users VALUES(DEFAULT, %s, first_name, last_name, password);""" , (u['userID'], u['notes'], j['team'])) 106 | return groupID 107 | except: 108 | return None 109 | 110 | @cursor_wrapped 111 | def addTicket_Status(cur, status): 112 | if type(status) is not str: raise(InvalidVariableTypes) 113 | cur.execute("INSERT INTO Ticket_Statusses VALUES(DEFAULT, %s) RETURNING id",(status,)) 114 | vmdb.commit() 115 | return cur.fetchone()[0] 116 | 117 | @cursor_wrapped 118 | def add_ticket_Resolutions(cur, resolution): 119 | if type(resolution) is not str: raise(InvalidVariableTypes) 120 | cur.execute("INSERT INTO Ticket_Resolutions VALUES(DEFAULT, %s) RETURNING id",(resolution,)) 121 | vmdb.commit() 122 | return cur.fetchone()[0] 123 | 124 | @cursor_wrapped 125 | def addAccess_Right(cur, access_right): 126 | if type(access_right) is not str: raise(InvalidVariableTypes) 127 | cur.execute("INSERT INTO Access_Rights VALUES(DEFAULT, %s) RETURNING id",(access_right,)) 128 | vmdb.commit() 129 | return cur.fetchone()[0] 130 | 131 | def addTicket_Priority(cur, name, max_implement_time, min_cvss): 132 | if ((type(name) is not str) or (type(max_implement_time) is not int) or 133 | (type(min_cvss) is not float and type(min_cvss) is not decimal.Decimal)): 134 | raise(InvalidVariableTypes) 135 | cur.execute("INSERT INTO Ticket_Priorities VALUES(DEFAULT, %s, %s, %s) RETURNING id", 136 | (name, max_implement_time, min_cvss)) 137 | vmdb.commit() 138 | return cur.fetchone()[0] 139 | 140 | def addTicket_Urgency(cur, name, score): 141 | if ((type(name) is not str) or (type(score) is not int)): raise(InvalidVariableTypes) 142 | cur.execute("INSERT INTO Ticket_Urgencies VALUES(DEFAULT, %s, %s) RETURNING id",(name, score)) 143 | vmdb.commit() 144 | return cur.fetchone()[0] 145 | 146 | def addTeam(cur, name, description): 147 | if ((type(name) is not str) or (type(description) is not int)): raise(InvalidVariableTypes) 148 | cur.execute("INSERT INTO Teams VALUES(DEFAULT, %s, %s) RETURNING id",(name, description)) 149 | vmdb.commit() 150 | return cur.fetchone()[0] 151 | 152 | def addRole(cur, name, ticket_access, system_group_access, statistics_access, team_access, db_access): 153 | a = getAccessRights() 154 | if (not all(isinstance(x,str) for x in [name, ticket_access, system_group_access, statistics_access, team_access]) or 155 | (type(db_access) is not bool)): raise(InvalidVariableTypes) 156 | try: 157 | cur.execute("INSERT INTO Roles VALUES(DEFAULT, %s, %s, %s, %s, %s, %s);", 158 | (name, a[ticket_access], a[system_group_access], a[statistics_access], a[team_access], db_access)) 159 | vmdb.commit() 160 | return cur.fetchone()[0] 161 | except: 162 | return None 163 | # -- Get-IDs.py -- 164 | # Object ID's 165 | @cursor_wrapped 166 | def getSystemGroupID(cur, name, team): 167 | cur.execute("""SELECT System_Groups.id 168 | FROM System_Groups, Teams 169 | WHERE System_Groups.name = %s 170 | and Teams.name = %s 171 | and System_Groups.team_id = Teams.id;""", (name, team)) 172 | sid=cur.fetchone() 173 | return sid[0] if sid else None 174 | 175 | 176 | @cursor_wrapped 177 | def getComponentID(cur, name): 178 | cur.execute("""SELECT id FROM Components WHERE name = %s;""", (name, )) 179 | cid=cur.fetchone() 180 | return cid[0] if cid else None 181 | 182 | 183 | # List of ID's 184 | @cursor_wrapped 185 | def getPriorityLevels(cur): 186 | cur.execute("""SELECT id, min_cvss FROM Ticket_Priorities WHERE min_cvss IS NOT NULL;""") 187 | # Sort from highest CVSS to lowest 188 | priorities=sorted(cur.fetchall(),key=lambda x: x[1]) 189 | return priorities 190 | # -- Get-Names.py -- 191 | @cursor_wrapped 192 | def getComponentNames(cur, team=None, system=None): 193 | if team and system: 194 | cur.execute("""SELECT name FROM Components WHERE id IN ( 195 | SELECT component_ID FROM Components_in_System_Group WHERE system_group_id IN ( 196 | SELECT id FROM System_Group WHERE team_id IN ( 197 | SELECT id FROM Teams WHERE name = %s ) AND name = %s;));""", (team,system)) 198 | else: 199 | cur.execute("""SELECT name FROM Components;""") 200 | c=[x[0] for x in cur.fetchall()] 201 | return sorted(list(set(c)), key=lambda s: s.lower()) 202 | 203 | 204 | @cursor_wrapped 205 | def getCPENames(cur, team=None): 206 | query="SELECT name FROM Components " 207 | if team: 208 | query+="""WHERE id IN( 209 | SELECT component_id FROM Components_in_System_Group WHERE system_group_id IN( 210 | SELECT id from System_Groups WHERE team_id IN( 211 | SELECT id from Teams where name = %s)))""" 212 | cur.execute(query, list(filter(None, [team]))) 213 | cpes=[x[0] for x in cur.fetchall()] 214 | return list(set(itertools.chain(*cpes))) 215 | 216 | 217 | @cursor_wrapped 218 | def getStatusses(cur): 219 | cur.execute("""SELECT name FROM Ticket_Statusses;""") 220 | return [x[0] for x in cur.fetchall()] 221 | 222 | 223 | @cursor_wrapped 224 | def getTeamNames(cur): 225 | cur.execute("SELECT name FROM Teams;") 226 | return [x[0] for x in cur.fetchall()] 227 | # -- Get-Objects.py -- 228 | @cursor_wrapped 229 | def getTicket(cur, cve, cpe, system, team): 230 | cur.execute("""SELECT Tickets.id, Teams.name, System_Groups.name, Tickets.cve, Components.name, 231 | Ticket_Statusses.name, Ticket_Histories.ticket_notes, Ticket_Histories.ticket_resolution_id, 232 | Ticket_Histories.ticket_resolution_fix, Ticket_Histories.cvss, Ticket_Histories.ticket_priority_id 233 | FROM Tickets, System_Groups, Components, Teams, Ticket_Histories, Ticket_Resolutions, Ticket_Statusses 234 | WHERE cve=%s 235 | AND component_id IN ( SELECT id FROM Components WHERE name = %s ) 236 | AND system_group_id IN ( SELECT id FROM System_Groups WHERE name = %s 237 | AND team_id IN ( SELECT id FROM Teams WHERE name = %s )) 238 | AND Teams.id = System_Groups.team_id 239 | AND System_Groups.id = Tickets.system_group_id 240 | AND Components.id = Tickets.component_id 241 | AND Ticket_Histories.id = Tickets.history_last_id 242 | AND Ticket_Statusses.id = Ticket_Histories.ticket_status_id;""", (cve, cpe, system, team)) 243 | data=cur.fetchone() 244 | c=list(data) if data else data 245 | 246 | if c: 247 | if c[10]: 248 | cur.execute("SELECT name FROM Ticket_Priorities where id=%s", (c[10],)) 249 | priority=(cur.fetchone())[0] 250 | else: 251 | priority=None 252 | cur.execute("SELECT name FROM Ticket_Resolutions") 253 | t_res= [x[0] for x in cur.fetchall()] 254 | cur.execute("SELECT name FROM Ticket_Priorities") 255 | t_prior= [x[0] for x in cur.fetchall()] 256 | cur.execute("SELECT name FROM Ticket_Urgencies") 257 | t_urgent= [x[0] for x in cur.fetchall()] 258 | cur.execute("""SELECT datetime, Users.user_name, cvss, ticket_notes, Ticket_Statusses.name, ticket_resolution_id, ticket_resolution_fix, 259 | ticket_priority_id, ticket_urgency_id 260 | FROM Ticket_Histories, Ticket_Statusses, Users 261 | WHERE ticket_id=%s 262 | AND Ticket_Statusses.id = Ticket_Histories.ticket_status_id 263 | AND Users.id = Ticket_Histories.user_id;""", (c[0],)) 264 | histories=cur.fetchall() 265 | h=[] 266 | for x in histories: 267 | h.append({"time": x[0], "user": x[1], "cvss": x[2], "notes": x[3], "status": x[4], "update": {"type": xFromy(x[5], t_res), "cpe": x[6]}, 268 | "priority": xFromy(x[7], t_prior), "urgency": xFromy(x[8], t_urgent)}) 269 | if c[7]: 270 | cur.execute("""SELECT name FROM Ticket_Resolutions WHERE id = %s""", (c[7],)) 271 | c[7]=cur.fetchone()[0] 272 | c=Ticket(c[1], c[2], c[3], c[4], status=c[5], notes=c[6], history=h, resolution=c[7], resolution_fix=c[8], cvss=c[9], priority=priority, objID=c[0]) 273 | else: 274 | tick=Ticket(team, system, cve, cpe) 275 | addTicket(tick) 276 | c=getTicket(tick.cve, tick.vulnComp["cpe"], tick.groupName, tick.team) 277 | c.addInfo(cvedb.getCVEInfo(cve)) 278 | updatePriority(c) 279 | return c 280 | 281 | 282 | @cursor_wrapped 283 | def getTickets(cur, team, system=None, limit=None): 284 | # Build up statement 285 | command = """SELECT Tickets.cve, Components.name, System_Groups.name, Teams.name 286 | FROM Tickets, System_Groups, Components, Teams, Ticket_histories 287 | WHERE System_Groups.id = Tickets.system_group_id 288 | AND Components.id = Tickets.component_id 289 | AND Teams.id = System_Groups.team_id 290 | AND Ticket_Histories.id = Tickets.history_last_id """ 291 | if limit and limit.lower()=="open": limit = """AND Ticket_Histories.ticket_status_id IN 292 | ( SELECT id FROM Ticket_Statusses WHERE name NOT LIKE 'closed-%%' ) """ 293 | elif limit and limit.lower()=="closed": limit = """AND Ticket_Histories.ticket_status_id IN 294 | ( SELECT id FROM Ticket_Statusses WHERE name LIKE 'closed-%%' ) """ 295 | else: limit = "" 296 | # Execute statement based on system or all systems 297 | if system: 298 | cur.execute(command + limit + """AND system_group_id IN ( SELECT id FROM System_Groups WHERE name = %s 299 | AND team_id IN ( SELECT id FROM Teams WHERE name = %s ))""", (system, team)) 300 | else: 301 | cur.execute(command + limit + """AND system_group_id IN ( SELECT id FROM System_Groups WHERE team_id 302 | IN ( SELECT id FROM Teams WHERE name = %s ))""", (team, )) 303 | return [getTicket(x[0],x[1],x[2],x[3]) for x in cur.fetchall()] 304 | 305 | 306 | @cursor_wrapped 307 | def getVulnsForSystem(cur, groupName, systemTeam, cpes=None): 308 | if type(cpes) is not list and cpes:cpes=[cpes] 309 | if not cpes: 310 | cur.execute("""SELECT name FROM Components WHERE id IN ( 311 | SELECT component_id FROM Components_in_System_Group WHERE system_group_id IN ( 312 | SELECT id FROM System_Groups WHERE name = %s AND team_id IN ( 313 | SELECT id FROM Teams WHERE name = %s )));""",(groupName, systemTeam)) 314 | cpes=[x[0] for x in cur.fetchall()] 315 | 316 | # Get tickets for every cve for the vulnerable systems 317 | tickets=[] 318 | for cpe in cpes: 319 | tickets.extend([getTicket(cve, cpe, groupName, systemTeam) for cve in [x["id"] for x in cvedb.getVulns([cpe], 0)]]) 320 | tickets=[x for x in tickets if not x.status.lower().startswith("closed")] 321 | return tickets 322 | 323 | 324 | @cursor_wrapped 325 | def getGroups(cur, team, group=None): 326 | teams=getTeamDicts() 327 | groups=[] 328 | query = """SELECT * FROM System_Groups WHERE team_id IN (SELECT id FROM Teams WHERE name=%s)""" 329 | if group: query+= "AND name=%s;" 330 | cur.execute(query, list(filter(None, [team, group]))) 331 | for g in cur.fetchall(): 332 | clist=[] 333 | cur.execute("""SELECT * FROM Components WHERE id IN ( 334 | SELECT component_ID FROM Components_in_System_Group WHERE system_group_id = %s );""", (g[0],)) 335 | for comp in cur.fetchall(): 336 | clist.append(Component(comp[1])) 337 | groups.append(SystemGroup(None, g[1], teams[g[2]], None, [], clist)) 338 | return groups 339 | 340 | 341 | @cursor_wrapped 342 | def getGroupDetails(cur, group): 343 | cur.execute("""SELECT name FROM Teams WHERE id=%s;""", (group[2],)) 344 | tid=cur.fetchone()[0] 345 | clist=[] 346 | cur.execute("""SELECT * FROM Components WHERE id IN ( SELECT component_ID FROM Components_in_System_Group WHERE system_group_id = %s );""", (group[0],)) 347 | for comp in cur.fetchall(): 348 | clist.append(Component(comp[1])) 349 | return SystemGroup(None, group[1], tid, None, [], clist) 350 | 351 | 352 | @cursor_wrapped 353 | def getTicketsForSystemComponent(cur, component, _system): 354 | tickets=getTickets(team, _system.groupName) 355 | #for x in tickets: 356 | # print(x) 357 | #tickets.append(getTicket(x[0], x[1], x[2], x[3])) 358 | return tickets 359 | 360 | # -- Get-IDs.py -- 361 | # Object ID's 362 | @cursor_wrapped 363 | def getSystemGroupID(cur, name, team): 364 | cur.execute("""SELECT System_Groups.id 365 | FROM System_Groups, Teams 366 | WHERE System_Groups.name = %s 367 | and Teams.name = %s 368 | and System_Groups.team_id = Teams.id;""", (name, team)) 369 | sid=cur.fetchone() 370 | return sid[0] if sid else None 371 | 372 | 373 | @cursor_wrapped 374 | def getComponentID(cur, name): 375 | cur.execute("""SELECT id FROM Components WHERE name = %s;""", (name, )) 376 | cid=cur.fetchone() 377 | return cid[0] if cid else None 378 | 379 | 380 | # List of ID's 381 | @cursor_wrapped 382 | def getPriorityLevels(cur): 383 | cur.execute("""SELECT id, min_cvss FROM Ticket_Priorities WHERE min_cvss IS NOT NULL;""") 384 | # Sort from highest CVSS to lowest 385 | priorities=sorted(cur.fetchall(),key=lambda x: x[1]) 386 | return priorities 387 | # -- Get-Dicts.py -- 388 | @cursor_wrapped 389 | def getLastTeamVulns(cur, team, limit): 390 | return cvedb.getVulns(getCPENames(team), limit) 391 | 392 | @cursor_wrapped 393 | def getGroupVulns(cur, team, colorVulnerable=False): 394 | c=getGroups(team) 395 | glist=[] 396 | for x in c: 397 | glist.append(x.getDict()) 398 | if colorVulnerable: 399 | for x in glist: 400 | for comp in x["components"]: 401 | vuln=cvedb.isVulnerable(comp["cpe"]) 402 | if vuln: x["vulnerable"]=True 403 | comp["vulnerable"]=vuln 404 | return sorted(glist, key=lambda s: s["groupName"].lower()) 405 | 406 | @cursor_wrapped 407 | def getStatistics(cur, team): 408 | today=date.today() 409 | start=date(today.year, today.month,1) 410 | if today.month < 12: end=date(today.year, today.month+1,1) 411 | else: end=date(today.year+1, 1, 1) 412 | systems=[] 413 | for x in getGroupVulns(team): 414 | new=0 415 | closed=0 416 | oac=0 417 | tickets=[] 418 | for cpe in [y["cpe"] for y in x["components"]]: 419 | tickets.extend([getTicket(cve, cpe, x["groupName"], team) for cve in [x["id"] for x in cvedb.getVulns([cpe], 0)]]) 420 | for ticket in tickets: 421 | opened=False 422 | for action in ticket.history: 423 | aDate=date(action["time"].year, action["time"].month, action["time"].day) 424 | if "status" in action and aDate >= start and aDate < end: 425 | if action["status"] == _DEFAULT_STATUS_TEXT: 426 | new+=1 427 | opened=True 428 | elif action["status"].lower().startswith("closed"): 429 | if opened: 430 | oac+=1 431 | new-=1 432 | else: 433 | closed+=1 434 | systems.append({"systems":x["groupName"], "new": new, "closed": closed, "openedAndClosed":oac}) 435 | return systems 436 | 437 | @cursor_wrapped 438 | def getTeamDicts(cur): 439 | cur.execute("SELECT id, name FROM Teams;") 440 | return {x[0]: x[1] for x in cur.fetchall()} 441 | 442 | @cursor_wrapped 443 | def getAccessRights(cur): 444 | cur.execute("SELECT * FROM Access_Rights;") 445 | return {v: k for k, v in dict(cur.fetchall()).items()} 446 | # -- Update.py -- 447 | @cursor_wrapped 448 | def setDBTicketStatus(cur, ticket, user, status=None, notes=None, update=None, updateType=None, ): 449 | #Allows us to work both with dict and Ticket types 450 | t=ticket.getDict() if type(ticket) == Ticket else ticket 451 | if type(ticket) == Ticket: t=getTicket(t["cve"], t["vulnComp"]["cpe"], t["groupName"], t["team"]).getDict() 452 | 453 | # Prevent unnecesarry database operations 454 | if all(None == i for i in [status, notes, update, updateType]): return False 455 | if status and t["status"] == status.strip(): return False 456 | if notes and t["notes"] == notes.strip(): return False 457 | if update and t["vulnComp"]["update"] == update.strip(): return False 458 | if updateType and t["vulnComp"]["type"] == updateType.strip(): return False 459 | # Fetch the current ticket ID and history 460 | cur.execute("""SELECT id, history_last_id FROM Tickets WHERE cve=%s 461 | AND component_id IN ( SELECT id FROM Components WHERE name = %s ) 462 | AND system_group_id IN ( SELECT id FROM System_Groups WHERE name = %s 463 | AND team_id IN ( SELECT id FROM Teams WHERE name = %s ));""", (t["cve"], t["vulnComp"]["cpe"], t["groupName"], t["team"])) 464 | x=cur.fetchone() 465 | if not x: return False 466 | t_id, h_id = x 467 | # Fetch all the ticket history information 468 | cur.execute("""SELECT * FROM Ticket_Histories WHERE id = %s;""", (h_id,)) 469 | h=cur.fetchone() 470 | # Get ID's of settings, or get the old id's 471 | if status == None: 472 | status=t["status"] 473 | cur.execute("""SELECT id FROM Ticket_Statusses WHERE name = %s;""", (status,)) 474 | status=cur.fetchone()[0] 475 | notes = t["notes"] if notes == None else notes 476 | update = t["vulnComp"]["update"] if update == None else update 477 | if updateType == None: updateType=t["vulnComp"]["type"] 478 | cur.execute("""SELECT id FROM Ticket_Resolutions WHERE name = %s;""", (updateType,)) 479 | updateType=cur.fetchone() 480 | updateType=updateType[0] if updateType else None 481 | # Get user ID 482 | cur.execute("""SELECT id FROM Users WHERE user_name = %s""", (user,)) 483 | uid=cur.fetchone()[0] 484 | # Now make a new entry using above data 485 | now = datetime.fromtimestamp(time.time()) 486 | cur.execute("""INSERT INTO Ticket_Histories VALUES(DEFAULT, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;""", 487 | (h[1], now, uid, h[4], h[5], notes, updateType, update, status, h[10], h[11] )) 488 | last_id=cur.fetchone()[0] 489 | cur.execute("""UPDATE Tickets SET history_last_id = %s WHERE id = %s;""", (last_id, t_id)) 490 | vmdb.commit() 491 | return True 492 | 493 | 494 | @cursor_wrapped 495 | def updateCPE(cur, _ticket): 496 | # temporary disable this function 497 | return 498 | # TODO: Verify if this structure won't cause Race conditions 499 | t=_ticket.getDict() 500 | update=t['vulnComp'] 501 | cid=addComponent(update['update']) 502 | sid=getSystemGroupID(t['groupName'], t['team']) 503 | ocid=getComponentID(update['cpe']) 504 | if update['type'] in ["upgrade", "merge"]: 505 | cur.execute("""UPDATE Components_in_System_Group 506 | SET component_id = %s 507 | WHERE component_id = %s 508 | and system_group_id = %s;""", (cid, ocid, sid)) 509 | vmdb.commit() 510 | elif update["type"] == "remove": 511 | cur.execute("""DELETE FROM Components_in_System_Group 512 | WHERE component_id = %s 513 | and system_group_id = %s;""", (ocid, sid)) 514 | vmdb.commit() 515 | # Else we assume patch, in which case nothing needs to happen, only return cpes. 516 | 517 | # Find list of related tickets 518 | return getTicketsForSystemComponent(update['cpe'], t['groupName'], t['team']) 519 | 520 | 521 | @cursor_wrapped 522 | def updatePriority(cur, _ticket): 523 | cvss=_ticket.cveInfo["cvss"] 524 | if _ticket.cvss == cvss: 525 | return 526 | priority=calculatePriority(ticket) 527 | #if priority 528 | # -- Calculations.py -- 529 | def calculatePriority(_ticket): 530 | priority=None 531 | try: 532 | cvss=float(_ticket.cveInfo["cvss"]) 533 | except: 534 | return None 535 | # TODO might to reverse loop to fix None problem 536 | for x in getPriorityLevels(): 537 | if x[1]4+x else None 33 | 34 | # get/set 35 | def getDict(self): 36 | return {"cpe":self.cpe, "vendor": self.vendor, "product": self.product, "version": self.version} 37 | -------------------------------------------------------------------------------- /lib/objects/SystemGroup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3.3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Vulnerability Manager SystemGroup object 5 | # 6 | # Copyright (c) 2014-2015 Pieter-Jan Moreels 7 | # 8 | # Software is free software released under the "Original BSD license" 9 | 10 | # Imports 11 | import os 12 | import sys 13 | _runPath = os.path.dirname(os.path.realpath(__file__)) 14 | sys.path.append(os.path.join(_runPath, "../..")) 15 | 16 | from lib.objects import Component 17 | from lib.exceptions import InvalidVariableTypes 18 | 19 | # Class 20 | class SystemGroup(): 21 | def verifyVars(self, groupID=None, groupName=None, team=None, notes=None, systems=None, components=None): 22 | if type(groupName) is not str or groupName.strip() is "": raise(InvalidVariableTypes) 23 | if not all(isinstance(x,str) for x in systems) or not all(isinstance(x,Component) for x in components): raise(InvalidVariableTypes) 24 | if notes is not None and type(notes) is not str: raise(InvalidVariableTypes) 25 | 26 | # init 27 | def __init__(self, groupID=None, groupName=None, team=None, notes=None, systems=None, components=None): 28 | self.verifyVars(groupID, groupName, team, notes, systems, components) 29 | self.groupID=groupID;self.groupName=groupName;self.team=team;self.notes=notes;self.components=components 30 | self.systems=[x for x in systems if x is not ""] 31 | 32 | # get/set 33 | def getDict(self): 34 | return {"groupID":self.groupID, "groupName": self.groupName, "team": self.team, 35 | "systems":self.systems, "components":[x.getDict() for x in self.components]} 36 | -------------------------------------------------------------------------------- /lib/objects/Ticket.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3.3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Vulnerability Manager SystemGroup object 5 | # 6 | # Copyright (c) 2014-2015 Pieter-Jan Moreels 7 | # 8 | # Software is free software released under the "Original BSD license" 9 | 10 | # Imports 11 | import os 12 | import sys 13 | _runPath = os.path.dirname(os.path.realpath(__file__)) 14 | sys.path.append(os.path.join(_runPath, "../..")) 15 | 16 | import decimal 17 | from datetime import datetime 18 | from lib.exceptions import InvalidVariableTypes 19 | 20 | # Object 21 | class Ticket(): 22 | def verifyVars(self, team, groupName, cve, cpe, status, notes, history, res, res_fix, user, cvss, priority, cveInfo, objID): 23 | if (type(status) is not str and status is not None or 24 | type(notes) is not str and notes is not None or 25 | type(res) is not str and res is not None or 26 | type(res_fix) is not str and res_fix is not None or 27 | type(history) is not list and history is not None or 28 | type(user) is not str and user is not None or 29 | type(priority) is not str and priority is not None or 30 | type(cvss) is not decimal.Decimal and cvss is not None or 31 | type(cveInfo) is not dict and cveInfo is not None or 32 | type(objID) is not int and objID is not None): raise(InvalidVariableTypes) 33 | if not all(isinstance(x,str) for x in [team, groupName, cve, cpe]): raise(InvalidVariableTypes) 34 | if history: 35 | if not all(isinstance(x,dict) for x in history): raise(InvalidVariableTypes) 36 | 37 | # init 38 | def __init__(self, team, groupName, cve, cpe, status=None, notes=None, history=None, resolution=None, resolution_fix=None, 39 | user=None, cvss=None, priority=None, cveInfo=None, objID=None): 40 | self.verifyVars(team, groupName, cve, cpe, status, notes, history, resolution, resolution_fix, user, cvss, priority, cveInfo, objID) 41 | self.team=team.strip(); self.groupName=groupName.strip(); self.cve=cve.strip(); self.cvss=cvss; self.cveInfo=cveInfo; self.objID=objID 42 | self.vulnComp={'cpe':cpe.strip(), 'update':resolution_fix, 'type':resolution} 43 | self.resolution=resolution.strip() if resolution else None; 44 | self.resolution_fix=resolution_fix.strip() if resolution_fix else None 45 | self.status=status.strip() if status else None 46 | self.notes=notes.strip() if notes else "" 47 | self.priority=priority.strip() if priority else "None" 48 | self.history=history if history else [{"time": datetime.now(), "status": self.status, "user":user}] 49 | 50 | def getDict(self): 51 | return {"team":self.team, "groupName":self.groupName, "cve":self.cve, "vulnComp":self.vulnComp, "status":self.status, 52 | "notes":self.notes, "history":self.history, "cvss":self.cvss, "priority": self.priority, "cve-info": self.cveInfo} 53 | 54 | def addInfo(self, info): 55 | if type(info) is not dict and info is not None: raise(InvalidVariableTypes) 56 | self.cveInfo = info 57 | -------------------------------------------------------------------------------- /lib/objects/User.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3.3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Vulnerability Manager SystemGroup object 5 | # 6 | # Copyright (c) 2014-2015 Pieter-Jan Moreels 7 | # 8 | # Software is free software released under the "Original BSD license" 9 | 10 | # Imports 11 | import os 12 | import sys 13 | _runPath = os.path.dirname(os.path.realpath(__file__)) 14 | sys.path.append(os.path.join(_runPath, "../..")) 15 | 16 | from flask.ext.login import UserMixin 17 | 18 | from lib.database.Users import getUser, checkPass 19 | from lib.exceptions import InvalidVariableTypes, UserNotFoundError 20 | 21 | # Object 22 | class User(UserMixin): 23 | def __init__(self, id): 24 | user=getUser(id) 25 | if not user: raise UserNotFoundError() 26 | self.id = id 27 | self.first_name=user["first_name"] 28 | self.last_name=user["last_name"] 29 | self.teams=user["teams"] 30 | 31 | @classmethod 32 | def get(self_class, id): 33 | try: 34 | return self_class(id) 35 | except UserNotFoundError: 36 | return None 37 | 38 | def verifyPass(self, pw): 39 | return True if checkPass(self.id, pw) else False 40 | -------------------------------------------------------------------------------- /lib/objects/__init__.py: -------------------------------------------------------------------------------- 1 | from .Component import Component 2 | from .SystemGroup import SystemGroup 3 | from .Ticket import Ticket 4 | from .User import User 5 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | psycopg2 2 | flask 3 | flask-login<=0.2.11 4 | -------------------------------------------------------------------------------- /sbin/static/css/style.css: -------------------------------------------------------------------------------- 1 | select, option { 2 | min-width:150px; 3 | } 4 | 5 | #status { 6 | position:fixed; 7 | top: 50%; 8 | left: 50%; 9 | width:50em; 10 | height:6em; 11 | margin-top: -3em; 12 | margin-left: -25em; 13 | border: 1px solid #ccc; 14 | text-align:center; 15 | z-index:99; 16 | } 17 | 18 | .hidden { 19 | visibility:hide; 20 | } 21 | 22 | #systemContent{ 23 | overflow-y:auto; 24 | max-height:90vh; 25 | } 26 | 27 | .clickable:hover{ 28 | cursor:pointer; 29 | } 30 | 31 | .no-link { 32 | color:inherit; 33 | text-decoration:none; 34 | cursor:pointer; 35 | } 36 | 37 | li.vulnerable { 38 | color: red; 39 | } 40 | 41 | span.vulnerable { 42 | color: red; 43 | } 44 | -------------------------------------------------------------------------------- /sbin/static/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NorthernSec/Vulnerability-management/5b4893428f0fbc451aed12ef46753ee97deec088/sbin/static/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /sbin/static/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NorthernSec/Vulnerability-management/5b4893428f0fbc451aed12ef46753ee97deec088/sbin/static/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /sbin/static/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NorthernSec/Vulnerability-management/5b4893428f0fbc451aed12ef46753ee97deec088/sbin/static/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /sbin/static/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NorthernSec/Vulnerability-management/5b4893428f0fbc451aed12ef46753ee97deec088/sbin/static/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /sbin/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NorthernSec/Vulnerability-management/5b4893428f0fbc451aed12ef46753ee97deec088/sbin/static/img/favicon.ico -------------------------------------------------------------------------------- /sbin/static/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.2 (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.2",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.2",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active"));a&&this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.2",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&"show"==b&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a(this.options.trigger).filter('[href="#'+b.id+'"], [data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.2",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0,trigger:'[data-toggle="collapse"]'},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":a.extend({},e.data(),{trigger:this});c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){b&&3===b.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=c(d),f={relatedTarget:this};e.hasClass("open")&&(e.trigger(b=a.Event("hide.bs.dropdown",f)),b.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger("hidden.bs.dropdown",f)))}))}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.2",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('