├── auth ├── LDAP │ ├── authenticationMethods │ │ └── LDAP.py │ └── readme.md └── readme.md ├── plugins ├── etc │ └── plugins.txt ├── plugins │ ├── MISP │ │ ├── etc │ │ │ └── plugins.ini │ │ ├── plugins │ │ │ └── MISP.py │ │ ├── readme.md │ │ └── requirements.txt │ ├── Reporting │ │ ├── plugins │ │ │ └── Reporting.py │ │ ├── readme.md │ │ └── web.templates.plugins │ │ │ └── reporting.html │ ├── bookmarks │ │ ├── plugins │ │ │ └── bookmark.py │ │ ├── readme.md │ │ └── web.templates.plugins │ │ │ └── bookmarks.html │ ├── cve-scan │ │ ├── plugins │ │ │ └── CVEScan.py │ │ ├── readme.md │ │ ├── requirements.txt │ │ └── web.templates.plugins │ │ │ ├── cve-scan.html │ │ │ ├── cve-scan_pdf.html │ │ │ ├── cve-scan_settings.html │ │ │ └── cve-scan_webdisplay.html │ ├── notes │ │ ├── plugins │ │ │ └── notes.py │ │ └── readme.md │ ├── seen │ │ ├── plugins │ │ │ └── seen.py │ │ ├── readme.md │ │ └── web.templates.plugins │ │ │ └── user_seen.html │ ├── sendMail │ │ ├── etc │ │ │ ├── plugins.ini │ │ │ └── template.txt │ │ ├── plugins │ │ │ └── sendMail.py │ │ └── readme.md │ └── team_collaboration │ │ ├── etc │ │ └── plugins.ini │ │ ├── plugins │ │ └── Collaboration.py │ │ ├── readme.md │ │ └── web.templates.plugins │ │ └── team_collaboration.html ├── readme.md └── template.py └── readme.md /auth/LDAP/authenticationMethods/LDAP.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # LDAP Authentication for CVE-Search 5 | # 6 | # Software is free software released under the "Modified BSD license" 7 | # 8 | # Copyright (c) 2016 Pieter-Jan Moreels - pieterjan.moreels@gmail.com 9 | 10 | # Necessary imports 11 | import ldap3 12 | import os 13 | import sys 14 | import __main__ 15 | callLocation = os.path.dirname(os.path.realpath(__main__.__file__)) 16 | sys.path.append(os.path.join(callLocation, "..")) 17 | 18 | from lib.Authentication import AuthenticationMethod 19 | import lib.Authentication as auth 20 | import lib.DatabaseLayer as db 21 | 22 | class LDAP(AuthenticationMethod): 23 | def __init__(self, domain="", server="", sync=False): 24 | if not (domain and server): raise(Exception) 25 | self.domain = domain 26 | self.server = server 27 | self.sync = sync 28 | 29 | def validateUser(self, user, pwd): 30 | domain = self.domain 31 | if user.count("\\") == 1: # Domain added 32 | domain, user = user.split("\\") 33 | elif user.count("\\") > 1: # Wrong creds 34 | return auth.WRONG_CREDS 35 | 36 | serv = ldap3.Server(self.server, use_ssl=True) 37 | try: 38 | conn = ldap3.Connection(serv, user="%s\\%s"%(domain, user), 39 | password=pwd, auto_bind=True) 40 | if self.sync: 41 | db.changePassword(user, pwd) 42 | except ldap3.core.exceptions.LDAPSocketOpenError: 43 | return auth.UNREACHABLE 44 | except ldap3.core.exceptions.LDAPBindError: 45 | return auth.WRONG_CREDS 46 | return auth.AUTHENTICATED 47 | -------------------------------------------------------------------------------- /auth/LDAP/readme.md: -------------------------------------------------------------------------------- 1 | # About this authentication method 2 | Validates the username and password against a specified server over 3 | LDAP. Usernames can be \ as well as just the 4 | username. A default domain has to be passed to the plug-in. 5 | 6 | # Installation and setup 7 | The installation of this module is similar to every other authentication 8 | module, but requires extra configuration, and relies on a third party 9 | library. 10 | 11 | ## Installing the external library 12 | The installation of this library is very easy and should be familiar: 13 | `sudo pip3 install ldap3` 14 | 15 | ## Configuration of the plug-in 16 | The module takes the following arguments: 17 | 18 | * `domain` - The (NT) Domain name **Required** 19 | * `server` - The server to authenticate against **Required** 20 | * `sync` - When set, syncs the password with the local database **Optional (default False)** 21 | 22 | #License 23 | cve-search and its modules are free software released under the "Modified BSD license" 24 | 25 | Copyright (c) 2016 Pieter-Jan Moreels - https://github.com/pidgeyl/ 26 | -------------------------------------------------------------------------------- /auth/readme.md: -------------------------------------------------------------------------------- 1 | # Authentication Module repository 2 | In this directory, we will maintain some of our CVE-Search 3 | authentication modules. These modules can be added and removed however 4 | you see fit. 5 | 6 | **The Authentication development of CVE-Search is still in progress!** 7 | 8 | **Note:** Development of the authentication feature is mostly done by 9 | [PidgeyL](https://github.com/PidgeyL), so new features will come from 10 | [his branch](https://github.com/PidgeyL/cve-search) first. If you 11 | encounter compatibility issues, please keep this in mind.
12 | **Note:** The username of the authenticating user has to be present in 13 | the local database.
14 | **Note:** `local_only` users will bypass authentication done by modules. 15 | 16 | # Installation 17 | The installation of these modules is fairly easy.
18 | Installing a module is as easy as dragging it into the 19 | `./lib/authenticationMethods` folder, and modifying the 20 | `./etc/auth.txt` file. We will use the LDAP module as an example 21 | throughout this readme. 22 | 23 | **Note:** Some plug-ins require third party libraries. Make sure to read 24 | the readme of the plug-in carefully. 25 | 26 | ## The auth file 27 | The `./etc/auth.txt` file is used sequentially. The first module gets 28 | used first, and so on. Lines starting with `#` are ignored. An example 29 | could be: 30 | 31 | ``` 32 | # Module required/sufficient args 33 | LDAP sufficient domain=CVESearch server=ldap.cve.search 34 | ``` 35 | 36 | ### Module 37 | Module is the name of the module, without the `.py` suffix. If for some 38 | reason you want to put the module in a subdirectory, use the path 39 | starting from the `./lib/authenticationMethods` folder, and replace 40 | every `/` by a `.` 41 | 42 | ### required/sufficient 43 | A module can return three states: 44 | * **AUTHENTICATED** - The user is validated against this module 45 | * **WRONG_CREDS** - The user is not validated against this module 46 | * **UNREACHABLE** - This module was unable to verify the user (e.g 47 | the server is not reachable) 48 | 49 | If a module is set to "required", authentication will only succeed if 50 | the module returns **AUTHENTICATED**. If it returns **WRONG_CREDS**, 51 | authentication will fail. If the module returns **UNREACHABLE**, the 52 | next module in the list is used. The last automatic fallback method is 53 | authentication against the local database.
54 | If a module is set to "sufficient", and the module returns 55 | **WRONG_CREDS**, authentication will not fail, but drop to the next 56 | module. The outcome of the authentication will depend on the first 57 | (responding) `required` module, or the database if all other modules 58 | fail. 59 | 60 | ### args 61 | The arguments are passed to the module when the module gets created. 62 | It is important you do not put a space between the key and the value 63 | (`domain` is the key, `CVESearch` is the value), but you do separate 64 | key/value groups with a space or tab. 65 | 66 | ## Users 67 | When authentication modules are used, all users will be subject to 68 | authentication against these modules. If you want to have a user who 69 | can bypass these authentication methods (e.g an emergency 70 | administrator), you can make him `local_only`. You do this by passing 71 | the `-l` flag to `./sbin/db_mgmt_admin.py` when creating the user. 72 | -------------------------------------------------------------------------------- /plugins/etc/plugins.txt: -------------------------------------------------------------------------------- 1 | # Plugin Location load/default/pass 2 | plugins/bookmark/bookmark default 3 | -------------------------------------------------------------------------------- /plugins/plugins/MISP/etc/plugins.ini: -------------------------------------------------------------------------------- 1 | [MISP] 2 | url: https://misp.istance.world 3 | key: api-key 4 | -------------------------------------------------------------------------------- /plugins/plugins/MISP/plugins/MISP.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3.3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # MISP connector plug-in for CVE-Search 5 | # 6 | # Software is free software released under the "Modified BSD license" 7 | # 8 | # Copyright (c) 2016 Pieter-Jan Moreels - pieterjan.moreels@gmail.com 9 | 10 | # Necessary imports 11 | import os 12 | import sys 13 | import __main__ 14 | callLocation = os.path.dirname(os.path.realpath(__main__.__file__)) 15 | sys.path.append(os.path.join(callLocation, "..")) 16 | 17 | import dateutil.parser 18 | import math 19 | import pytz 20 | 21 | from datetime import datetime 22 | from pymisp import PyMISP 23 | 24 | from lib.Plugins import Plugin, WebPlugin 25 | from lib.ProgressBar import progressbar 26 | import lib.DatabaseLayer as db 27 | 28 | class MISP(WebPlugin): 29 | def __init__(self): 30 | super().__init__() 31 | self.name = "Malware Information Sharing Platform" 32 | self.collectionName = "info_misp" 33 | self.url = None 34 | self.key = None 35 | 36 | def loadSettings(self, reader): 37 | self.url = reader.read("MISP", "url", "") 38 | self.key = reader.read("MISP", "key", "") 39 | 40 | def onDatabaseUpdate(self): 41 | lastUpdate = db.p_readSetting(self.collectionName, "last_update") 42 | now = datetime.utcnow().replace(tzinfo = pytz.utc) 43 | if lastUpdate: 44 | last = dateutil.parser.parse(lastUpdate) 45 | delta = now - last 46 | since = "%sm"%math.ceil(delta.total_seconds()/60) 47 | else: 48 | since = "" 49 | if self.url and self.key: 50 | try: 51 | # Misp interface 52 | misp = PyMISP(self.url, self.key, True, 'json') 53 | except: 54 | return "[-] Failed to connect to MISP. Wrong URL?" 55 | try: 56 | # Fetch data 57 | misp_last = misp.download_last(since) 58 | # Check data 59 | if 'message' in misp_last.keys(): 60 | if misp_last['message'].lower().startswith('no matches'): return "[+] MISP collection updated (0 updates)" 61 | elif misp_last['message'].startswith('Authentication failed.'): return "[-] MISP Authentication failed" 62 | if not 'response' in misp_last: print(misp_last); return "[-] Error occured while fetching MISP data" 63 | # Nothing wrong so far, so let's continue 64 | bulk =[] 65 | for entry in progressbar(misp_last['response']): 66 | # Get info 67 | attrs=entry['Event']['Attribute'] 68 | CVEs= [x['value'] for x in attrs if x['type'] == 'vulnerability'] 69 | if len(CVEs) == 0: continue 70 | threats= [x['value'] for x in attrs if x['category'] == 'Attribution' and x['type'] == 'threat-actor'] 71 | tags = [x['value'] for x in attrs if x['category'] == 'Other' and x['type'] == 'text'] 72 | tags.extend([x['value'] for x in attrs if x['category'] == 'External analysis' and x['type'] == 'text']) 73 | # Add info to each CVE 74 | for cve in CVEs: 75 | item={'id':cve} 76 | if len(threats) !=0: item['threats'] = threats 77 | if len(tags) !=0: item['tags'] = tags 78 | if len(item.keys())>1: bulk.append(item) # Avoid empty collections 79 | db.p_bulkUpdate(self.collectionName, "id", bulk) 80 | #update database info after successful program-run 81 | db.p_writeSetting(self.collectionName, "last_update", now.strftime("%a, %d %h %Y %H:%M:%S %Z")) 82 | return "[+] MISP collection updated (%s updates)"%len(bulk) 83 | except Exception as e: print(e);print(e);return "[-] Something went wrong..." 84 | else: return "[-] MISP credentials not specified" 85 | 86 | 87 | def cvePluginInfo(self, cve, **args): 88 | misp = db.p_queryOne(self.collectionName, {'id': cve}) 89 | if misp: 90 | misp.pop("id") 91 | data = "" 92 | for key in misp.keys(): 93 | data+="" 97 | data += "
%s "%key 94 | for value in misp[key]: 95 | data+="
%s
"%value 96 | data+="
" 98 | return {'title': "MISP", 'data': data} 99 | 100 | def search(self, text, **args): 101 | threat = [x["id"] for x in db.p_queryData(self.collectionName, {'threats': {"$regex": text, "$options": "-i"}})] 102 | misp_tag = [x["id"] for x in db.p_queryData(self.collectionName, {'tags': {"$regex": text, "$options": "-i"}})] 103 | return [{'n': 'Threat', 'd': threat}, {'n': 'MISP tag', 'd': misp_tag}] 104 | -------------------------------------------------------------------------------- /plugins/plugins/MISP/readme.md: -------------------------------------------------------------------------------- 1 | # About this plug-in 2 | This plug-in queries information from your specified MISP api and 3 | displays it in the CVE information. It looks for threat-actors and 4 | tags added by users, and is searchable with the search function. It 5 | queries this data on every database update 6 | 7 | # Installation and setup 8 | The installation of this plug-in is like the installation of any 9 | plug-in, but the setup requires some extra steps. It also requires an 10 | external library. 11 | 12 | ## Installing the external library 13 | The installation of this library is very easy and should be familiar: 14 | `sudo pip3 install -r requirements.txt` 15 | 16 | ## The plugins.ini file 17 | The plug-in has two settings in the plugins.ini file. An example could 18 | be: 19 | 20 | ``` 21 | [MISP] 22 | url: https://misp.istance.world 23 | key: api-key 24 | ``` 25 | * `url` is the url of the MISP API. **Required** 26 | * `key` is the API key for your MISP instance. **Required** 27 | -------------------------------------------------------------------------------- /plugins/plugins/MISP/requirements.txt: -------------------------------------------------------------------------------- 1 | pymisp 2 | -------------------------------------------------------------------------------- /plugins/plugins/Reporting/plugins/Reporting.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3.3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # SVF plug-in for CVE-Search 5 | # 6 | # Software is free software released under the "Modified BSD license" 7 | # 8 | # Copyright (c) 2016 Pieter-Jan Moreels - pieterjan.moreels@gmail.com 9 | 10 | # Necessary imports 11 | import csv 12 | import json 13 | import os 14 | import re 15 | import sys 16 | import __main__ 17 | callLocation = os.path.dirname(os.path.realpath(__main__.__file__)) 18 | sys.path.append(os.path.join(callLocation, "..")) 19 | 20 | import lib.DatabaseLayer as db 21 | from lib.Plugins import Plugin, WebPlugin 22 | from lib.Toolkit import mergeSearchResults 23 | 24 | from flask import Response 25 | from io import StringIO 26 | from dateutil.parser import parse as parse_datetime 27 | import urllib 28 | import traceback 29 | 30 | class Reporting(WebPlugin): 31 | def __init__(self): 32 | self.name = "Reporting" 33 | self.requiresAuth = False 34 | self.collectionName = "reporting" 35 | 36 | # Copied the filter logic from index.py. Changes made are: 37 | # > added self as function variable 38 | # > added self to whitelist_mark and blacklist_mark 39 | # > added plugManager as function variable 40 | # > changed **pluginArgs() to **args and add as function variable 41 | def filter_logic(self, f, limit, skip, plugManager, **args): 42 | query = [] 43 | # retrieving lists 44 | if f['blacklistSelect'] == "on": 45 | regexes = db.getRules('blacklist') 46 | if len(regexes) != 0: 47 | exp = "^(?!" + "|".join(regexes) + ")" 48 | query.append({'$or': [{'vulnerable_configuration': re.compile(exp)}, 49 | {'vulnerable_configuration': {'$exists': False}}, 50 | {'vulnerable_configuration': []} 51 | ]}) 52 | if f['whitelistSelect'] == "hide": 53 | regexes = db.getRules('whitelist') 54 | if len(regexes) != 0: 55 | exp = "^(?!" + "|".join(regexes) + ")" 56 | query.append({'$or': [{'vulnerable_configuration': re.compile(exp)}, 57 | {'vulnerable_configuration': {'$exists': False}}, 58 | {'vulnerable_configuration': []} 59 | ]}) 60 | if f['unlistedSelect'] == "hide": 61 | wlregexes = compile(db.getRules('whitelist')) 62 | blregexes = compile(db.getRules('blacklist')) 63 | query.append({'$or': [{'vulnerable_configuration': {'$in': wlregexes}}, 64 | {'vulnerable_configuration': {'$in': blregexes}}]}) 65 | if f['rejectedSelect'] == "hide": 66 | exp = "^(?!\*\* REJECT \*\*\s+DO NOT USE THIS CANDIDATE NUMBER.*)" 67 | query.append({'summary': re.compile(exp)}) 68 | 69 | # plugin filters 70 | query.extend(plugManager.doFilter(f, **args)) 71 | 72 | # cvss logic 73 | if f['cvssSelect'] == "above": query.append({'cvss': {'$gt': float(f['cvss'])}}) 74 | elif f['cvssSelect'] == "equals": query.append({'cvss': float(f['cvss'])}) 75 | elif f['cvssSelect'] == "below": query.append({'cvss': {'$lt': float(f['cvss'])}}) 76 | 77 | # date logic 78 | if f['timeSelect'] != "all": 79 | if f['startDate']: 80 | startDate = parse_datetime(f['startDate'], ignoretz=True, dayfirst=True) 81 | if f['endDate']: 82 | endDate = parse_datetime(f['endDate'], ignoretz=True, dayfirst=True) 83 | 84 | if f['timeSelect'] == "from": 85 | query.append({f['timeTypeSelect']: {'$gt': startDate}}) 86 | if f['timeSelect'] == "until": 87 | query.append({f['timeTypeSelect']: {'$lt': endDate}}) 88 | if f['timeSelect'] == "between": 89 | query.append({f['timeTypeSelect']: {'$gt': startDate, '$lt': endDate}}) 90 | if f['timeSelect'] == "outside": 91 | query.append({'$or': [{f['timeTypeSelect']: {'$lt': startDate}}, {f['timeTypeSelect']: {'$gt': endDate}}]}) 92 | cve=db.getCVEs(limit=limit, skip=skip, query=query) 93 | # marking relevant records 94 | if f['whitelistSelect'] == "on": cve = self.whitelist_mark(cve) 95 | if f['blacklistSelect'] == "mark": cve = self.blacklist_mark(cve) 96 | plugManager.mark(cve, **args) 97 | cve = list(cve) 98 | return cve 99 | 100 | # Copied from index.py. Changes made are: 101 | # > added self 102 | # > added self to compile 103 | def whitelist_mark(self, cve): 104 | whitelistitems = self.compile(db.getRules('whitelist')) 105 | # ensures we're working with a list object, in case we get a pymongo.cursor object 106 | cve = list(cve) 107 | # check the cpes (full or partially) in the whitelist 108 | for cveid in cve: 109 | cpes = cveid['vulnerable_configuration'] 110 | for c in cpes: 111 | if any(regex.match(c) for regex in whitelistitems): 112 | cve[cve.index(cveid)]['whitelisted'] = 'yes' 113 | return cve 114 | 115 | # Copied from index.py. Changes made are: 116 | # > added self 117 | # > added self to compile 118 | def blacklist_mark(self, cve): 119 | blacklistitems = self.compile(db.getRules('blacklist')) 120 | # ensures we're working with a list object, in case we get a pymongo.cursor object 121 | cve = list(cve) 122 | # check the cpes (full or partially) in the blacklist 123 | for cveid in cve: 124 | cpes = cveid['vulnerable_configuration'] 125 | for c in cpes: 126 | if any(regex.match(c) for regex in blacklistitems): 127 | cve[cve.index(cveid)]['blacklisted'] = 'yes' 128 | return cve 129 | 130 | # Copied from index.py. Changes made are: 131 | # > added self 132 | def compile(self, regexes): 133 | r=[] 134 | for rule in regexes: 135 | r.append(re.compile(rule)) 136 | return r 137 | 138 | def getPage(self, **args): 139 | page="reporting.html" 140 | plugManager = args["plugin_manager"] 141 | filters = plugManager.getFilters(**args) 142 | return (page, {'filters': filters, 'plug_id': self.getUID()}) 143 | 144 | def onCVEAction(self, cve, action, **args): 145 | if action == "filter": 146 | try: 147 | filters = {x.split("=")[0]: x.split("=")[1] for x in args["fields"]['filter'][0].split("&")} 148 | filters = {x: urllib.parse.unquote(y) for x,y in filters.items()} 149 | fields = json.loads(args["fields"]["fields"][0]) 150 | limit = 0 151 | skip = 0 152 | cves = self.filter_logic(filters, limit, skip, args["plugin_manager"], **args) 153 | return {'status': 'plugin_action_complete', 'data': self.generateCSV(cves, fields)} 154 | except Exception as e: 155 | traceback.print_exc() 156 | return False 157 | elif action == "textsearch": 158 | try: 159 | text = args["fields"]["text"][0] 160 | fields = json.loads(args["fields"]["fields"][0]) 161 | dbResults = db.getSearchResults(text) 162 | plugResults = args["plugin_manager"].getSearchResults(text, **args) 163 | result = mergeSearchResults(dbResults, plugResults) 164 | cves=result['data'] 165 | fields["reason"] = True 166 | return {'status': 'plugin_action_complete', 'data': self.generateCSV(cves, fields)} 167 | except Exception as e: 168 | traceback.print_exc() 169 | return False 170 | 171 | def generateCSV(self, cves, fields): 172 | fields = [x for x,y in fields.items() if y is True] 173 | memoryFile = StringIO() 174 | csv_file = csv.writer(memoryFile, delimiter=',', quotechar='"') 175 | csv_file.writerow(fields) 176 | for cve in cves: 177 | line = [] 178 | for field in fields: 179 | try: 180 | line.append(cve[field]) 181 | except: 182 | line.append("") 183 | csv_file.writerow(line) 184 | return memoryFile.getvalue() 185 | -------------------------------------------------------------------------------- /plugins/plugins/Reporting/readme.md: -------------------------------------------------------------------------------- 1 | # About this plug-in 2 | Pretty much like like the index page of the website, this plug-in 3 | allows users to filter through the CVE-Search data, but instead of 4 | displaying it in a webpage, it exports the data to a CSV file. It is 5 | also compatible with the filters from other plug-ins. 6 | 7 | #Installation and setup 8 | The installation of this plug-in is like the installation of any 9 | plug-in. 10 | -------------------------------------------------------------------------------- /plugins/plugins/Reporting/web.templates.plugins/reporting.html: -------------------------------------------------------------------------------- 1 | {% extends 'layouts/master-page' %} 2 | {% block title %}Reporting{% endblock %} 3 | {% block head %} 4 | 5 | 6 | 11 | 12 | 13 | 75 | {% endblock %} 76 | {% block content %} 77 | 78 |
79 |

Fields

80 | CVE ID 81 | CVSS 82 | Summary 83 | Last Major Update 84 | Published 85 | Last Modified 86 |
87 | 88 | {% include 'subpages/filters.html' %} 89 | 90 |
91 |

Or search by text

92 |
93 |
94 |
95 | 96 | 97 | 98 | 99 |
100 |
101 |
102 |
103 | {% endblock %} 104 | -------------------------------------------------------------------------------- /plugins/plugins/bookmarks/plugins/bookmark.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3.3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Bookmark plug-in for CVE-Search 5 | # 6 | # Software is free software released under the "Modified BSD license" 7 | # 8 | # Copyright (c) 2016 Pieter-Jan Moreels - pieterjan.moreels@gmail.com 9 | 10 | # Necessary imports 11 | import os 12 | import sys 13 | import __main__ 14 | callLocation = os.path.dirname(os.path.realpath(__main__.__file__)) 15 | sys.path.append(os.path.join(callLocation, "..")) 16 | 17 | from lib.Plugins import Plugin, WebPlugin 18 | import lib.CVEs as cves 19 | import lib.DatabaseLayer as db 20 | 21 | class bookmark(WebPlugin): 22 | def __init__(self): 23 | super().__init__() 24 | self.name = "Bookmarks" 25 | self.requiresAuth = True 26 | self.collectionName = "user_bookmarks" 27 | 28 | def getPage(self, **args): 29 | cvesp = cves.last(rankinglookup=True, namelookup=True, via4lookup=True, capeclookup=True,subscorelookup=True) 30 | data = db.p_queryOne(self.collectionName, {"user": args["current_user"].get_id()}) 31 | bookmarks = data.get("bookmarks", []) if data else [] 32 | cve=[cvesp.getcve(cveid=x) for x in bookmarks] 33 | page="bookmarks.html" 34 | return (page, {"cve": cve}) 35 | 36 | def getCVEActions(self, cve, **args): 37 | userdata = db.p_queryOne(self.collectionName, {'user': args["current_user"].get_id()}) 38 | if userdata and 'bookmarks' in userdata and cve in userdata['bookmarks']: 39 | return [{'text': 'Remove bookmark', 'action': 'unbookmark', 'icon': 'star'}] 40 | else: 41 | return [{'text': 'Bookmark', 'action': 'bookmark', 'icon': 'star-empty'}] 42 | 43 | def onCVEAction(self, cve, action, **args): 44 | try: 45 | query = {'user': args["current_user"].get_id()} 46 | if action == "bookmark": 47 | db.p_addToList(self.collectionName, query, "bookmarks", cve) 48 | elif action == "unbookmark": 49 | db.p_removeFromList(self.collectionName, query, "bookmarks", cve) 50 | return True 51 | except Exception as e: 52 | return False 53 | -------------------------------------------------------------------------------- /plugins/plugins/bookmarks/readme.md: -------------------------------------------------------------------------------- 1 | # About this plug-in 2 | This plug-in allows users to bookmark CVEs for further reference. 3 | Bookmarked CVEs can be found on the plug-in page. 4 | 5 | # Installation and setup 6 | The installation of this plug-in is like the installation of any 7 | plug-in. 8 | -------------------------------------------------------------------------------- /plugins/plugins/bookmarks/web.templates.plugins/bookmarks.html: -------------------------------------------------------------------------------- 1 | {% extends 'layouts/master-page' %} 2 | {% block title %}Bookmarked CVEs{% endblock %} 3 | {% block content %} 4 | 5 | 9 | {% include 'subpages/table.html' %} 10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /plugins/plugins/cve-scan/plugins/CVEScan.py: -------------------------------------------------------------------------------- 1 | ## Minimum required imports 2 | import base64 3 | import codecs 4 | import hashlib 5 | import json 6 | import os 7 | import queue 8 | import re 9 | import sys 10 | import time 11 | import threading 12 | import traceback 13 | import __main__ 14 | callLocation = os.path.dirname(os.path.realpath(__main__.__file__)) 15 | sys.path.append(os.path.join(callLocation, "..")) 16 | 17 | from bson import json_util 18 | from datetime import datetime 19 | from libnmap.parser import NmapParser 20 | from uuid import uuid4 21 | from jinja2 import Environment, FileSystemLoader 22 | from weasyprint import HTML 23 | from flask import Flask, render_template 24 | 25 | import lib.CVEs as cves 26 | import lib.DatabaseLayer as db 27 | from lib.Plugins import Plugin, WebPlugin 28 | from lib.Toolkit import toStringFormattedCPE 29 | 30 | def toBool(s): 31 | return True if s.lower() in ['true', 'yes'] else False 32 | 33 | class CVEScan(WebPlugin): 34 | def __init__(self): 35 | self.name = "CVE-Scan" 36 | self.requiresAuth = False 37 | self.tempPath = "/tmp/CVEScan-pdfs" 38 | 39 | env = Environment(loader=FileSystemLoader(callLocation)) 40 | self.pdf = env.get_template("templates/plugins/cve-scan_pdf.html") 41 | self.html = "plugins/cve-scan_webdisplay.html" 42 | self.collection = "CVEScan" 43 | self.reaper = Reaper() 44 | self._set_reaper_status() 45 | 46 | def handle_scan(self, scan, action, tags, notes, store=False): 47 | try: 48 | nmap = self._parseNMap(scan) 49 | enhanced = self._enhance(nmap) 50 | if store: self._store_in_db(nmap, tags=tags, notes=notes) 51 | if action == "json": 52 | returndata = json.dumps(enhanced, indent=2, default=json_util.default) 53 | elif action == "pdf": 54 | returndata = str(base64.b64encode(self._generatePDF(enhanced)), "utf-8") 55 | elif action == "webview": 56 | app = Flask(__name__, template_folder=os.path.join(callLocation, "templates")) 57 | with app.test_request_context("/"): 58 | returndata = render_template(self.html, scan=enhanced) 59 | return returndata 60 | except Exception as e: 61 | traceback.print_exc() 62 | 63 | 64 | def onCVEAction(self, cve, action, **args): 65 | if action in ["json", "pdf", "webview"]: 66 | data = args["fields"]['scan'][0] 67 | store = bool(args["fields"]['store'][0]) 68 | tags = args["fields"]['tags'][0].split(",") 69 | notes = args["fields"]['notes'][0] 70 | data = self.handle_scan(data, action, tags, notes, store) 71 | return {'status': 'plugin_action_complete', 'data': data} 72 | elif action in ["save_settings"]: 73 | try: 74 | data = {"reaper.enable": toBool(args["fields"]["reaper_enable"][0]), 75 | "reaper.folder": args["fields"]["reaper_folder"][0], 76 | "reaper.store": toBool(args["fields"]["reaper_store"][0]), 77 | "output.enable": toBool(args["fields"]["output_enable"][0]), 78 | "output.type": args["fields"]["output_type"][0], 79 | "output.folder": args["fields"]["output_folder"][0] } 80 | if data["output.type"] not in ["json", "pdf", "webview"]: return False 81 | if not data["reaper.folder"]: data["reaper.folder"] = "./cve-scan" 82 | if not data["output.folder"]: data["output.folder"] = "./cve-scan-output" 83 | 84 | for key, val in data.items(): 85 | db.p_writeSetting(self.collection, key, val) 86 | 87 | self._set_reaper_status() 88 | except Exception as e: 89 | print(e) 90 | return False 91 | return True 92 | return False 93 | 94 | 95 | def getPage(self, **args): 96 | return ("cve-scan.html", {'plug_id': self.getUID()}) 97 | 98 | def getSubpage(self, page, **args): 99 | if page.lower() == "settings": 100 | data = {'plug_id': self.getUID(), 101 | 'reaper_enable': self._getSetting("reaper.enable", False), 102 | 'reaper_folder': self._getSetting("reaper.folder", "./cve-scan"), 103 | 'reaper_store': self._getSetting("reaper.store", True), 104 | 'output_enable': self._getSetting("output.enable", True), 105 | 'output_type': self._getSetting("output.type", "json"), 106 | 'output_folder': self._getSetting("output.folder", "./cve-scan-output")} 107 | return ("cve-scan_settings.html", data) 108 | 109 | # CVE Scan code 110 | def _parseNMap(self, data): 111 | try: 112 | report = NmapParser.parse_fromstring(data) 113 | except Exception as e: 114 | print(e) 115 | raise(Exception) 116 | systems = [] 117 | for h in report.hosts: 118 | system = {'mac':h.mac, 'ip':h.address, 'status':h.status, 'hostnames': h.hostnames, 119 | 'vendor':h.vendor, 'distance':h.distance} 120 | cpeList = [] 121 | for c in h.os_match_probabilities(): 122 | for x in c.get_cpe(): 123 | cpeList.append(x) 124 | cpeList=list(set(cpeList)) 125 | if len(cpeList)>0: 126 | system['cpes']=cpeList 127 | services = [] 128 | for s in h.services: 129 | service={'port':s.port, 'banner':s.banner, 'protocol':s.protocol, 'name':s.service, 130 | 'state':s.state, 'reason':s.reason} 131 | if s.cpelist: 132 | service['cpe'] = s.cpelist[0].cpestring 133 | services.append(service) 134 | system['services']=services 135 | systems.append(system) 136 | scan={"systems":systems, "scan": {"time": report.endtime, 137 | "type": report._nmaprun["args"]}} 138 | return scan 139 | 140 | def _enhance(self, scan): 141 | cvesp = cves.last(rankinglookup=False, namelookup=False, via4lookup=True, capeclookup=False) 142 | for system in scan['systems']: 143 | cpe=system['cpes'] if 'cpes' in system else None 144 | 145 | if cpe: 146 | cpes=[] 147 | for c in cpe: 148 | c=c.lower() 149 | cpes.append({'cpe':c, 'cves':[cvesp.getcve(x['id']) 150 | for x in db.cvesForCPE(toStringFormattedCPE(c))]}) 151 | system['cpes']=cpes 152 | for service in system['services']: 153 | if 'cpe' in service: 154 | service['cves']=db.cvesForCPE(service['cpe']) 155 | scan['enhanced']={"time": int(datetime.now().strftime('%s'))} 156 | return scan 157 | 158 | def _generatePDF(self, parsed): 159 | try: 160 | filename = os.path.join(self.tempPath, "%s.pdf"%str(uuid4())) 161 | if not os.path.exists(self.tempPath): os.makedirs(self.tempPath) 162 | self._pdfify(parsed, filename) 163 | return open(filename, "rb").read() 164 | except Exception as e: 165 | print(e) 166 | raise(PDFGenerationException) 167 | 168 | def _pdfify(self, enhanced, output): 169 | enhanced["scan"]["time"] = self._fromEpoch(enhanced["scan"]["time"]) 170 | enhanced["enhanced"]["time"] = self._fromEpoch(enhanced["enhanced"]["time"]) 171 | appendixes=[] 172 | appendix = 1 173 | for system in enhanced["systems"]: 174 | if "cpes" in system: 175 | for cpe in system["cpes"]: 176 | cpe["cpe"] = self._toHuman(cpe["cpe"]) 177 | if "cves" in cpe and len(cpe["cves"])!=0: 178 | appendixes.append(cpe["cves"]) 179 | cpe.pop("cves") 180 | cpe["appendix"]=appendix 181 | appendix += 1 182 | for service in system["services"]: 183 | service["banner"]=self._product(service["banner"]) 184 | if "cves" in service and len(service["cves"])!=0: 185 | appendixes.append(service["cves"]) 186 | service["appendix"] = appendix 187 | appendix += 1 188 | if "cves" in service: service.pop("cves") 189 | enhanced["appendixes"]=appendixes 190 | html_out = self.pdf.render(enhanced) 191 | HTML(string=html_out).write_pdf(output) 192 | 193 | def _fromEpoch(self, epoch): 194 | return datetime.fromtimestamp(epoch).strftime('%a %d %h %Y at %H:%M:%S') 195 | 196 | def _toHuman(self, cpe): 197 | cpe = cpe[7:] 198 | result = cpe.split(':')[0] + " - " 199 | for c in cpe.split(':')[1:]: 200 | c = c.replace(':', ' ') 201 | c = c.replace('_', ' ') 202 | result += (" %s" %(c)) 203 | result = result.title() 204 | return result 205 | 206 | def _product(self, banner): 207 | if banner: 208 | r=self._make_dict(banner) 209 | return r['product'] if 'product' in r else 'unknown' 210 | else: 211 | return "unknown" 212 | 213 | def _make_dict(self, s): 214 | chunks = re.split('\s*(\w+\:)\s*',s) 215 | res={} 216 | args=[reversed(chunks)]*2 217 | for value,key in zip(*args): 218 | key=key.rstrip(':') 219 | if value: 220 | res[key]=value 221 | else: 222 | res={key:res} 223 | return res 224 | 225 | def _store_in_db(self, scan, reaper=False, notes=None, tags=None): 226 | if reaper and not self._getSetting("reaper.store", False): 227 | return 228 | # Hash calculation to prevent duplicates 229 | sha1=codecs.encode(hashlib.sha1(json.dumps(scan).encode('utf-8')).digest(), "hex").decode("utf-8") 230 | if not db.p_queryData(self.collection, {'sha1': sha1}): 231 | data={"scan": scan, "sha1": sha1} 232 | if type(notes) == str: data["notes"] = notes 233 | if type(tags) == list: data["tags"] = tags 234 | db.p_addEntry(self.collection, data) 235 | return True 236 | return False 237 | 238 | def _getSetting(self, setting, default): 239 | s = db.p_readSetting(self.collection, setting) 240 | if s is None: 241 | db.p_writeSetting(self.collection, setting, default) 242 | s = default 243 | return s 244 | 245 | 246 | def _set_reaper_status(self): 247 | if self._getSetting("reaper.enable", True): 248 | self.reaper.stop() 249 | _in = self._getSetting("reaper.folder", "./cve-scan") 250 | _in = _in if os.path.isabs(_in) else os.path.join(callLocation, '..', _in) 251 | self.reaper._reap_folder = _in 252 | self.reaper._on_file = self.scan_from_file 253 | self.reaper.start() 254 | else: 255 | self.reaper.stop() 256 | 257 | def scan_from_file(self, path): 258 | try: 259 | action = self._getSetting("output.type", "json") 260 | store = self._getSetting("reaper.store", False) 261 | _out = self._getSetting('output.folder', "./cve-scan-output") 262 | name = '.'.join(os.path.basename(path).split('.')[:-1])+'.'+action 263 | 264 | data = self.handle_scan(open(path).read(), action, ['auto'], '', store) 265 | if self._getSetting("output.enable", False): 266 | if action == 'pdf': data = base64.b64decode(data.encode('utf-8')) 267 | _out = _out if os.path.isabs(_out) else os.path.join(callLocation, '..', _out) 268 | 269 | if type(data) is str: data = data.encode('utf-8') 270 | try: 271 | if not os.path.exists(_out): os.path.os.makedirs(_out) 272 | open(os.path.join(_out, name), 'wb').write(data) 273 | except Exception as e: 274 | print("Couldn't write file") 275 | print(e) 276 | except Exception as e: 277 | traceback.print_exc() 278 | 279 | 280 | class PDFGenerationException(Exception): pass 281 | 282 | 283 | class Reaper(): 284 | def __init__(self, **kwargs): 285 | rate = kwargs.get('rate') 286 | path = kwargs.get('path') 287 | action = kwargs.get('on_file') 288 | 289 | self._refreshrate = rate if rate else 1 # In seconds 290 | self._reap_folder = path if path else "." 291 | self._on_file = action if action else None 292 | 293 | self.reaping = False 294 | self._filequeue = queue.Queue() 295 | self._last_state = [] 296 | self._threads = [] 297 | 298 | def __del__(): 299 | self.stop_reap() 300 | 301 | def start(self): 302 | self._threads = [threading.Thread(target=self._reap)] 303 | if self._on_file: 304 | self._threads.append(threading.Thread(target=self._file_handler)) 305 | for t in self._threads: 306 | t.daemon = True 307 | t.start() 308 | 309 | def _reap(self): 310 | self.reaping = True 311 | while self.reaping: 312 | files = self._get_new_files() 313 | for f in files: 314 | self._filequeue.put(f) 315 | time.sleep(self._refreshrate) 316 | 317 | 318 | def _file_handler(self): 319 | while self.reaping: 320 | f = self._filequeue.get() 321 | self._on_file(f) 322 | 323 | def stop(self): 324 | self.reaping = False 325 | 326 | def new_files(self): 327 | try: 328 | while not self._filequeue.empty(): 329 | yield self._filequeue.get() 330 | except: 331 | return [] 332 | 333 | def _get_new_files(self): 334 | new = [] 335 | state = self._get_files() 336 | for f in state: 337 | if f not in self._last_state: 338 | new.append(f) 339 | self._last_state = state 340 | return new 341 | 342 | def _get_files(self): 343 | try: 344 | f= [os.path.abspath(os.path.join(self._reap_folder, x)) 345 | for x in os.listdir(self._reap_folder)] 346 | return [x for x in f if os.path.isfile(x)] 347 | except: 348 | return [] 349 | -------------------------------------------------------------------------------- /plugins/plugins/cve-scan/readme.md: -------------------------------------------------------------------------------- 1 | # About this plug-in 2 | This plug-in allows users to parse NMAP scans to a list of 3 | vulnerabilities for the scanned systems and services. It is based 4 | off the [CVE-Scan](https://github.com/NorthernSec/CVE-Scan) Project 5 | by [NorthernSec](http://northernsec.eu). You can check out the 6 | [online API](http://northernsec.eu/cve-scan) before installing it, 7 | though the plug-in will be faster, due to direct database access. 8 | 9 | # Installation and setup 10 | The installation of this plug-in is like the installation of any 11 | plug-in, however, extra packages are required. You can install them 12 | with `sudo pip3 install -r requirements.txt`. Weasyprint has a set 13 | of sub-requirements. Please find the details 14 | [here](http://weasyprint.readthedocs.org/en/latest/install.html) 15 | -------------------------------------------------------------------------------- /plugins/plugins/cve-scan/requirements.txt: -------------------------------------------------------------------------------- 1 | git+git://github.com/savon-noir/python-libnmap 2 | weasyprint 3 | -------------------------------------------------------------------------------- /plugins/plugins/cve-scan/web.templates.plugins/cve-scan.html: -------------------------------------------------------------------------------- 1 | {% extends 'layouts/master-page' %} 2 | {% block title %}CVE-Scan{% endblock %} 3 | {% block head %} 4 | 38 | 57 | {% endblock %} 58 | {% block content %} 59 | 60 | 64 |
65 | 69 |

CVE-Scan

70 |

71 | CVE-Scan transforms XML output of NMAP into an overview of vulnerabilities in the scanned system. 72 |

    73 |
  1. Scan the system(s) with NMAP, using the -A and/or -O flag, and paste the XML output below.
  2. 74 |
  3. Select the output method (JSON, PDF or the built-in display viewer)
  4. 75 |
  5. Hit the "Analyze and output" button, and wait for the output.
  6. 76 |
77 |

78 |

CVE-Search Plugin

79 |
80 |

81 | [1] Input the nmap xml below:
82 | 83 |

84 |

85 | [2] Select output method
86 | 91 |

92 |

93 | [optional] Add tags and/or notes
94 | 95 | 96 | 97 | 98 | 99 |
Notes: Tags:
100 |

101 |

102 | [3] Go
103 | Store in the database:
104 | 105 |

106 |

107 | [4] Output: 108 |

No output yet.
109 |

110 |
111 |
112 | {% endblock %} 113 | -------------------------------------------------------------------------------- /plugins/plugins/cve-scan/web.templates.plugins/cve-scan_pdf.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Vulnerabilities for scanned systems 5 | 6 | 7 | 8 | 9 | 10 | 242 | 243 | 244 | Scan Info 245 |
246 | 247 | 248 | 249 | 250 |
Scanned on {{scan['time']}}
Scan Type {{scan['type']}}
Enhanced on{{enhanced['time']}}
251 |
252 | System List 253 | {% if systems|length == 0 %} 254 |
No hosts were scanned.
255 | {% else %} 256 | {% for sys in systems %} 257 | 258 | 259 | 260 | 261 | 262 | 276 | 277 | {% if sys['status']=='up'%} 278 | 279 | 280 | 285 | 286 | 287 | 288 | 310 | 311 | {% endif %} 312 |
IP {{sys['ip']}}
MAC {{sys['mac']}}
Status {{sys['status']}}
Possible CPEs 263 | {% if sys['cpes']|length == 0 %} 264 | System not recognized 265 | {% else %} 266 |
    267 | {%for cpe in sys['cpes'] %} 268 |
  • {{ cpe['cpe'] }} 269 | {% if "appendix" in cpe %} 270 | * Vulns in Appendix {{ cpe["appendix"] }} 271 | {% endif %}
  • 272 | {% endfor %} 273 |
274 | {% endif %} 275 |
Vendor {{sys['vendor']}}
Hostnames 281 |
    282 | {% for h in sys['hostnames'] %}
  • {{h}}
  • {% endfor %} 283 |
284 |
Distance {{sys['distance']}}
Services 289 | {% if sys['services']|length == 0 %} 290 | No services found for this host 291 | {% else %} 292 | 293 | 294 | 295 | 296 | {% for s in sys['services'] %} 297 | 298 | 299 | 300 | 301 | 302 | 305 | 306 | {% endfor %} 307 |
ServiceProductPort/ProtocolStatusAppendix
{{s['name']}}{{s['banner']}}port {{s['port']}}/{{s['protocol']}}{{s['state']}} 303 | {% if "appendix" in s %} {{ s["appendix"] }} {% endif %} 304 |
308 | {% endif %} 309 |
313 | {% endfor %} 314 | {% if appendixes %} 315 | {% for appendix in appendixes %} 316 |

317 |

Appendix {{ loop.index }}

318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | {% for cve in appendix %} 331 | 332 | 333 | {% if "impact" in cve %} 334 | 335 | 336 | 337 | {% else %} 338 | 339 | {% endif %} 340 | {% if "access" in cve %} 341 | 342 | 343 | 344 | {% else %} 345 | 346 | {% endif %} 347 | {% if "map_cve_exploitdb" in cve %} {% else %}{% endif %} 348 | {% if "map_cve_msf" in cve %} {% else %}{% endif %} 349 | 350 | {% endfor %} 351 |
CVE
Confidentiality
Integrity
Availability
Complexity
Authentication
Vector
Exploit DB
MSF
{{ cve["id"] }}{{ cve["impact"]["confidentiality"][0] }}{{ cve["impact"]["integrity"][0] }}{{ cve["impact"]["availability"][0] }}???{{ cve["access"]["complexity"][0] }}{{ cve["access"]["authentication"][0] }}{{ cve["access"]["vector"][0] }}???xx
352 |
353 | 354 | 355 | 356 | 364 | 365 | 366 | 386 | 387 |
Impact 357 |
358 | Confidentiality, Integrity & Availability
359 |
[N]
None
360 |
[P]
Partial
361 |
[C]
Complete
362 |
363 |
Access 367 |
368 | Complexity
369 |
[L]
Low
370 |
[M]
Medium
371 |
[H]
High
372 |
373 |
374 | Authentication
375 |
[N]
None
376 |
[S]
Single Instance
377 |
[M]
Multiple Instances
378 |
379 |
380 | Vector
381 |
[L]
Local
382 |
[A]
Ajecent Network
383 |
[N]
Network
384 |
385 |
388 |
389 | {% endfor %} 390 | {% endif %} 391 | {% endif %} 392 | 393 | 394 | -------------------------------------------------------------------------------- /plugins/plugins/cve-scan/web.templates.plugins/cve-scan_settings.html: -------------------------------------------------------------------------------- 1 | {% extends 'layouts/master-page' %} 2 | {% block title %}CVE-Scan{% endblock %} 3 | {% block head %} 4 | 22 | 38 | {% endblock %} 39 | {% block content %} 40 | 41 | 42 | 47 |
48 | 52 |
53 |

Reaper

54 | 55 |

56 | Reaper folder
57 | Store scans in database 58 |

59 |

60 |

Output generation

61 | Auto output generation
62 | Output type 63 |
68 | Output location 69 |

70 | 71 |
72 |
73 | {% endblock %} 74 | -------------------------------------------------------------------------------- /plugins/plugins/cve-scan/web.templates.plugins/cve-scan_webdisplay.html: -------------------------------------------------------------------------------- 1 |
2 | Scanned on {{scan['scan']['time']}} and enhanced 3 | on {{scan['enhanced']['time']}} 4 |
5 |
6 | {% if scan['systems']|length == 0 %} 7 |
8 | No hosts were scanned. 9 |
10 | {% else %} 11 | {% for sys in scan['systems'] %} 12 | {% set outer_loop = loop %} 13 |
14 | 15 | 16 | 17 | 18 | 19 | 70 | {% if sys['status']=='up'%} 71 | 72 | 73 | 80 | 81 | 82 | 83 | 150 | 151 | {% endif %} 152 |
IP {{sys['ip']}}
MAC {{sys['mac']}}
Status {{sys['status']}}
Possible CPEs 20 | {% if sys['cpes']|length == 0 %} 21 | System not recognized 22 | {% else %} 23 | {%for cpe in sys['cpes'] %} 24 |
25 | {{ cpe['cpe'] }} 26 | {% if cpe['cves']|length!=0 %} 27 | 28 | 29 | 30 | {% endif %} 31 |
32 | {% for v in cpe['cves'] %} 33 | {{v['id']}} 34 | {% if 'impact' in v %} - 35 | 36 | C 37 | I 38 | A 39 | 40 | {% endif %} 41 | {% if 'access' in v %} 42 | {% if v['access']['vector']|lower == 'local'%} 43 | 44 | {% elif v['access']['vector']|lower == 'network'%} 45 | 46 | {% elif v['access']['vector']|lower == 'adjacent_network'%} 47 | 48 | {% endif %} 49 | {% if v['access']['complexity']|lower == 'low'%} 50 | 51 | {% elif v['access']['complexity']|lower == 'medium'%} 52 | 53 | {% elif v['access']['complexity']|lower == 'high'%} 54 | 55 | {% endif %} 56 | {% endif %} 57 | {% if 'map_cve_exploitdb' in v %} 58 | 59 | {% endif %} 60 | {% if 'map_cve_msf' in v %} 61 | 62 | {% endif %} 63 |
64 | {% endfor %} 65 |
66 |
67 | {% endfor %} 68 | {% endif %} 69 |
Vendor {{sys['vendor']}}
Hostnames 74 |
    75 | {% for h in sys['hostnames'] %} 76 |
  • {{h}}
  • 77 | {% endfor %} 78 |
79 |
Distance {{sys['distance']}}
Services 84 | {% if sys['services']|length == 0 %} 85 | No services found for this host 86 | {% else %} 87 | 88 | 89 | 90 | 91 | 92 | {% for s in sys['services'] %} 93 | 94 | 95 | 99 | 100 | 101 | 108 | 109 | 110 | 145 | 146 | {% endfor %} 147 |
ServiceProductPort/ProtocolStatus
{{s['name']}} 96 | 97 | {{s['banner']}} 98 | port {{s['port']}}/{{s['protocol']}}{{s['state']}} 102 | {% if s['cves']|length!=0 %} 103 | 104 | 105 | 106 | {% endif %} 107 |
111 | {% for v in s['cves'] %} 112 | {{v['id']}} 113 | {% if 'impact' in v %} - 114 | 115 | C 116 | I 117 | A 118 | 119 | {% endif %} 120 | {% if 'access' in v %} 121 | {% if v['access']['vector']|lower == 'local'%} 122 | 123 | {% elif v['access']['vector']|lower == 'network'%} 124 | 125 | {% elif v['access']['vector']|lower == 'adjacent_network'%} 126 | 127 | {% endif %} 128 | {% if v['access']['complexity']|lower == 'low'%} 129 | 130 | {% elif v['access']['complexity']|lower == 'medium'%} 131 | 132 | {% elif v['access']['complexity']|lower == 'high'%} 133 | 134 | {% endif %} 135 | {% endif %} 136 | {% if 'map_cve_exploitdb' in v %} 137 | 138 | {% endif %} 139 | {% if 'map_cve_msf' in v %} 140 | 141 | {% endif %} 142 |
143 | {% endfor %} 144 |
148 | {% endif %} 149 |
153 |
154 | {% endfor %} 155 | {% endif %} 156 | 157 |
158 | 159 | -------------------------------------------------------------------------------- /plugins/plugins/notes/plugins/notes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3.3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # notes plug-in for CVE-Search 5 | # 6 | # Software is free software released under the "Modified BSD license" 7 | # 8 | # Copyright (c) 2016 Pieter-Jan Moreels - pieterjan.moreels@gmail.com 9 | 10 | # Necessary imports 11 | import os 12 | import sys 13 | import __main__ 14 | callLocation = os.path.dirname(os.path.realpath(__main__.__file__)) 15 | sys.path.append(os.path.join(callLocation, "..")) 16 | 17 | import lib.DatabaseLayer as db 18 | from lib.Plugins import Plugin, WebPlugin 19 | 20 | class notes(WebPlugin): 21 | def __init__(self): 22 | self.name = "Notes" 23 | self.requiresAuth = True 24 | self.collectionName = "notes" 25 | self.noteText=''' 26 | 27 | %s 28 | 29 | ''' 30 | self.noteRemove=''' 31 | 32 | ''' 33 | # Ensure the database settings exist 34 | nid = db.p_readSetting(self.collectionName, "last_note") 35 | if not nid: db.p_writeSetting(self.collectionName, "last_note", 0) 36 | 37 | def search(self, text, **args): 38 | if not args["current_user"].is_authenticated(): return 39 | data = [x["cve"] for x in db.p_queryData(self.collectionName, {"notes.notes":{"$regex": text, "$options": "-i"}, 40 | "notes.user": args["current_user"].get_id()})] 41 | return [{'n': 'Notes', 'd': data}] 42 | 43 | def _getNotesFor(self, cve, user): 44 | data = db.p_queryOne(self.collectionName, {'cve': cve}) 45 | notes = [] 46 | if data and 'notes' in data and user in [x["user"] for x in data['notes'] if 'user' in x]: 47 | notes = [x for x in data['notes'] if x.get('user') == user] 48 | return notes 49 | 50 | def _deleteIfExists(self, cve, user, noteID): 51 | note = [x for x in self._getNotesFor(cve, user) if x["id"] == noteID] 52 | if note: 53 | db.p_removeFromList(self.collectionName, {'cve': cve}, "notes", note[0]) 54 | 55 | def cvePluginInfo(self, cve, **args): 56 | if not args["current_user"].is_authenticated(): return 57 | returnData = "" 58 | for note in self._getNotesFor(cve, args["current_user"].get_id()): 59 | try: 60 | nid = note["id"] 61 | returnData += self.noteText%(nid, note["notes"], self.noteRemove%(self.getUID(), cve, nid, cve), self.getUID(), cve, nid, nid, cve) 62 | except: 63 | pass 64 | returnData += self.noteText%(0, "", "", self.getUID(), cve, 0, 0, cve) 65 | return {'title': "Notes", 'data': returnData} 66 | 67 | def onCVEAction(self, cve, action, **args): 68 | if args["current_user"].is_authenticated(): 69 | if action == "save": 70 | data = db.p_queryOne(self.collectionName, {'cve': cve}) 71 | user = args["current_user"].get_id() 72 | # Ensure the entry exists 73 | if not data: db.p_addEntry(self.collectionName, {"cve": cve, "notes": []}) 74 | # Get note if exists: 75 | self._deleteIfExists(cve, user, int(args["fields"]["id"][0])) 76 | # Add note 77 | nid = db.p_readSetting(self.collectionName, "last_note") + 1 78 | db.p_addToList(self.collectionName, {'cve': cve}, "notes", {'id': nid, 'user': user, 'notes': args["fields"]["text"][0]}) 79 | # Update last note id 80 | db.p_writeSetting(self.collectionName, "last_note", nid) 81 | return True 82 | elif action == "delete": 83 | user = args["current_user"].get_id() 84 | self._deleteIfExists(cve, user, int(args["fields"]["id"][0])) 85 | return True 86 | -------------------------------------------------------------------------------- /plugins/plugins/notes/readme.md: -------------------------------------------------------------------------------- 1 | # About this plug-in 2 | This plug-in allows users to make notes on a CVE info page. Multiple 3 | notes can be left on a page, and are currently limited to a use only. 4 | 5 | # Installation and setup 6 | The installation of this plug-in is like the installation of any 7 | plug-in. 8 | -------------------------------------------------------------------------------- /plugins/plugins/seen/plugins/seen.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3.3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Seen plug-in for CVE-Search 5 | # 6 | # Software is free software released under the "Modified BSD license" 7 | # 8 | # Copyright (c) 2016 Pieter-Jan Moreels - pieterjan.moreels@gmail.com 9 | 10 | # Necessary imports 11 | import os 12 | import re 13 | import sys 14 | import __main__ 15 | callLocation = os.path.dirname(os.path.realpath(__main__.__file__)) 16 | sys.path.append(os.path.join(callLocation, "..")) 17 | 18 | from lib.Plugins import Plugin, WebPlugin 19 | import lib.CVEs as cves 20 | import lib.DatabaseLayer as db 21 | 22 | class seen(WebPlugin): 23 | def __init__(self): 24 | super().__init__() 25 | self.name = "Seen CVEs" 26 | self.requiresAuth = True 27 | self.collectionName = "user_seen" 28 | 29 | def getCVEActions(self, cve, **args): 30 | if db.p_readUserSetting(self.collectionName, args["current_user"].get_id(), "buttons") == "show": 31 | userdata = db.p_queryOne(self.collectionName, {'user': args["current_user"].get_id()}) 32 | if userdata and 'cves' in userdata and cve in userdata['cves']: 33 | return [{'text': 'Unsee', 'action': 'unsee', 'icon': 'eye-close'}] 34 | else: 35 | return [{'text': 'See', 'action': 'see', 'icon': 'eye-open'}] 36 | 37 | def onCVEOpen(self, cve, **args): 38 | if args["current_user"].is_authenticated(): 39 | if db.p_readUserSetting(self.collectionName, args["current_user"].get_id(), "mode") == "auto": 40 | query = {'user': args["current_user"].get_id()} 41 | db.p_addToList(self.collectionName, query, "cves", cve) 42 | 43 | def onCVEAction(self, cve, action, **args): 44 | try: 45 | if args["current_user"].is_authenticated(): 46 | query = {'user': args["current_user"].get_id()} 47 | if action == "see": 48 | db.p_addToList(self.collectionName, query, "cves", cve) 49 | elif action == "unsee": 50 | db.p_removeFromList(self.collectionName, query, "cves", cve) 51 | elif action == "save_settings": 52 | mode = args["fields"]["mode"][0] 53 | buttons = args["fields"]["buttons"][0] 54 | mark = args["fields"]["mark"][0] 55 | filters = args["fields"]["filters"][0] 56 | markcolor = args["fields"]["markcolor"][0] 57 | if (mode in ["auto", "manual"] and buttons in ["show", "hide"] and 58 | mark in ["show", "hide"] and filters in ["show", "hide"] and 59 | re.match("^#[0-9A-Fa-f]{6}$", markcolor)): 60 | db.p_writeUserSetting(self.collectionName, args["current_user"].get_id(), "mode", mode) 61 | db.p_writeUserSetting(self.collectionName, args["current_user"].get_id(), "buttons", buttons) 62 | db.p_writeUserSetting(self.collectionName, args["current_user"].get_id(), "mark", mark) 63 | db.p_writeUserSetting(self.collectionName, args["current_user"].get_id(), "filters", filters) 64 | db.p_writeUserSetting(self.collectionName, args["current_user"].get_id(), "markcolor", markcolor) 65 | else: return False 66 | return True 67 | return False 68 | except Exception as e: 69 | print(e) 70 | return False 71 | 72 | def getFilters(self, **args): 73 | if db.p_readUserSetting(self.collectionName, args["current_user"].get_id(), "filters") == "show": 74 | return [{'id': 'Seen CVEs', 'filters': [{'id': 'hideSeen', 'type': 'select', 'values':[{'id':'show', 'text': 'Show'}, 75 | {'id':'hide', 'text': 'Hide'}]}]}] 76 | 77 | def doFilter(self, filters, **args): 78 | for fil in filters.keys(): 79 | if fil == "hideSeen": 80 | if args["current_user"].is_authenticated(): 81 | if filters[fil] == "hide": 82 | cves = db.p_queryOne(self.collectionName, {'user': args["current_user"].get_id()}) 83 | cves = cves["cves"] if cves and 'cves' in cves else [] 84 | return {'id': {"$nin": cves}} 85 | return {} 86 | 87 | def mark(self, cve, **args): 88 | user = args["current_user"].get_id() 89 | if db.p_readUserSetting(self.collectionName, user, "mark") == "show": 90 | color = db.p_readUserSetting(self.collectionName, user, "markcolor") 91 | userdata = db.p_queryOne(self.collectionName, {'user': user}) 92 | if userdata and 'cves' in userdata and cve in userdata['cves']: 93 | return (None, color) 94 | 95 | def _getUserSetting(self, user, setting, default): 96 | s = db.p_readUserSetting(self.collectionName, user, setting) 97 | if not s: 98 | db.p_writeUserSetting(self.collectionName, user, setting, default) 99 | s = default 100 | return s 101 | 102 | def getPage(self, **args): 103 | if args["current_user"].is_authenticated(): 104 | mode = self._getUserSetting(args["current_user"].get_id(), "mode", "auto") 105 | buttons = self._getUserSetting(args["current_user"].get_id(), "buttons", "show") 106 | mark = self._getUserSetting(args["current_user"].get_id(), "mark", "show") 107 | filters = self._getUserSetting(args["current_user"].get_id(), "filters", "show") 108 | markcolor = self._getUserSetting(args["current_user"].get_id(), "markcolor", "#778899") 109 | page="user_seen.html" 110 | return (page, {"mode": mode, "buttons": buttons, "mark": mark, "filters": filters, 111 | "markcolor": markcolor, "uid": self.uid}) 112 | -------------------------------------------------------------------------------- /plugins/plugins/seen/readme.md: -------------------------------------------------------------------------------- 1 | # About this plug-in 2 | This plug-in keeps track of which CVEs a user has seen before. It adds 3 | a button to the CVE page to 'see' or 'unsee' it and allows filtering 4 | through these CVEs. The plug-in has several user settings that can be 5 | modified to the users taste. 6 | 7 | #Installation and setup 8 | The installation of this plug-in is like the installation of any 9 | plug-in. 10 | -------------------------------------------------------------------------------- /plugins/plugins/seen/web.templates.plugins/user_seen.html: -------------------------------------------------------------------------------- 1 | {% extends 'layouts/master-page' %} 2 | {% block head %} 3 | 22 | {% endblock %} 23 | {% block title %}Seen CVEs settings{% endblock %} 24 | {% block content %} 25 | 26 | 30 | 31 |
32 | Settings
33 | 34 | 35 | 36 | 42 | 43 | 44 | 45 | 51 | 52 | 53 | 54 | 60 | 61 | 62 | 63 | 66 | 67 | 68 | 69 | 75 | 76 |
Mode 37 | 41 |
Buttons 46 | 50 |
Mark CVEs 55 | 59 |
Mark color 64 | 65 |
Filters 70 | 74 |
77 | 78 |
79 | 80 | {% endblock %} 81 | -------------------------------------------------------------------------------- /plugins/plugins/sendMail/etc/plugins.ini: -------------------------------------------------------------------------------- 1 | [Mailer] 2 | Server: smtp-mail.server.com 3 | Port: 587 4 | SendUser: sender@cveInstance.com 5 | SendPass: badpassword123 6 | RecvUser: techteam@mycompany.com 7 | Subject: Vulnerability to review 8 | Template: ./etc/template.txt 9 | -------------------------------------------------------------------------------- /plugins/plugins/sendMail/etc/template.txt: -------------------------------------------------------------------------------- 1 | Dear Technical Team, 2 | 3 | Could you kindly assess the following vulnerability? 4 | More information can be found at https://cve.circl.lu/cve/<> 5 | Thanks in advance 6 | 7 | <> <> 8 | <> 9 | 10 | <> 11 | 12 | <> 13 | -------------------------------------------------------------------------------- /plugins/plugins/sendMail/plugins/sendMail.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3.3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # SendMail plug-in for CVE-Search 5 | # 6 | # Software is free software released under the "Modified BSD license" 7 | # 8 | # Copyright (c) 2016 Pieter-Jan Moreels - pieterjan.moreels@gmail.com 9 | 10 | # Necessary imports 11 | import os 12 | import smtplib 13 | import sys 14 | import __main__ 15 | callLocation = os.path.dirname(os.path.realpath(__main__.__file__)) 16 | sys.path.append(os.path.join(callLocation, "..")) 17 | 18 | from lib.Plugins import Plugin, WebPlugin 19 | import lib.CVEs as cves 20 | import lib.DatabaseLayer as db 21 | 22 | class sendMail(WebPlugin): 23 | def __init__(self): 24 | super().__init__() 25 | self.name = "Forward CVE" 26 | self.requiresAuth = True 27 | 28 | self.serverCreds = (None, None) 29 | self.senderCreds = (None, None) 30 | self.techTeam = "" 31 | self.subject = "" 32 | self.template = "" 33 | 34 | def loadSettings(self, reader): 35 | self.serverCreds = (reader.read("Mailer", "Server", "localhost"), 36 | reader.read("Mailer", "Port", 587)) 37 | self.senderCreds = (reader.read("Mailer", "SendUser", ""), 38 | reader.read("Mailer", "SendPass", "")) 39 | self.techTeam = reader.read("Mailer", "RecvUser", "") 40 | self.subject = reader.read("Mailer", "Subject", "Vulnerability to review") 41 | template = reader.read("Mailer", "Template", "./etc/template.txt") 42 | template = os.path.join(callLocation, "..", template) 43 | try: 44 | self.template = open(template, "r").read() 45 | except: 46 | raise ValueError('Could not open the template! %s'%template) 47 | 48 | def getCVEActions(self, cve, **args): 49 | var = [self.serverCreds[0], self.techTeam, self.senderCreds[0], self.senderCreds[1]] 50 | if False in [type(x) == str for x in var]: return 51 | return [{'text': 'Send CVE to the tech team', 'action': 'sendMail', 'icon': 'envelope'}] 52 | 53 | def onCVEAction(self, cve, action, **args): 54 | if action == "sendMail": 55 | server=smtplib.SMTP('%s:%s'%(self.serverCreds)) 56 | server.starttls() 57 | server.login(self.senderCreds[0], self.senderCreds[1]) 58 | subject = self.subject 59 | template = self.template 60 | cveInfo = db.getCVE(cve) 61 | cvss = cveInfo.get("cvss") 62 | if not cvss: cvss= "N/A" 63 | if type(cvss) == float: cvss=str(cvss) 64 | template = template.replace("<>", cveInfo.get("id")) 65 | template = template.replace("<>", cvss) 66 | template = template.replace("<>", cveInfo.get("summary")) 67 | template = template.replace("<>", "\n".join(cveInfo.get("references"))) 68 | cwe = "CWE:\n * " + cveInfo.get("cwe") if cveInfo.get("cwe") else "" 69 | template = template.replace("<>", cwe) 70 | 71 | body="Subject: %s\n\n%s"%(subject, template) 72 | server.sendmail(self.senderCreds[0], self.techTeam, body) 73 | server.quit() 74 | return True 75 | -------------------------------------------------------------------------------- /plugins/plugins/sendMail/readme.md: -------------------------------------------------------------------------------- 1 | # About this plug-in 2 | This plug-in allows users send an e-mail with CVE info to a specified 3 | e-mail account, using specified credentials. This could be used to 4 | send CVE information to teams that have to patch vulnerabilities. 5 | 6 | # Installation and setup 7 | The installation of this plug-in is like the installation of any 8 | plug-in, but the setup requires some extra steps. 9 | 10 | ## The plugins.ini file 11 | The plug-in has three settings in the plugins.ini file. An example could 12 | be: 13 | 14 | ``` 15 | [Mailer] 16 | Server: smtp-mail.server.com 17 | Port: 587 18 | SendUser: sender@cveInstance.com 19 | SendPass: badpassword123 20 | RecvUser: techteam@mycompany.com 21 | Subject: Vulnerability to review 22 | Template: ./etc/template.txt 23 | ``` 24 | * `server` will be the mailserver an email is sent from. 25 | **Default:** localhost 26 | * `port` is the port of the mailservice on said mailserver. 27 | **Default:** 587 28 | * `senduser` is the email address the information is sent from. 29 | **Required** 30 | * `sendpass` is the password to the email address the information is 31 | sent from. **Required** 32 | * `recvuser` is the email address the information is sent to. 33 | **Required** 34 | * `subject` is the subject of the email. **Default:** Vulnerability to 35 | review 36 | * `template` is the location of the for the email. **Default:** 37 | `./etc/template.txt` 38 | 39 | **NOTE:** The password is stored in plain text. We will look for a 40 | better method to store the password in the future, but for now, we 41 | recommend to restrict user access to the plugins.ini file, and to use 42 | a separate mail account for CVE-Search 43 | 44 | ## The template file 45 | The template will be sent as the body of the e-mail. Certain tags can be 46 | used in this body: 47 | 48 | * **<<CVE>>**" - The CVE ID 49 | * **<<CVSS>>**" - The CVSS 50 | * **<<Subject>>**" - The summary of the CVE 51 | * **<<sources>>**" - The sources of the CVE 52 | * **<<CWE>>**" - The weaknesses targetted by the CVE 53 | -------------------------------------------------------------------------------- /plugins/plugins/team_collaboration/etc/plugins.ini: -------------------------------------------------------------------------------- 1 | [Colaboration] 2 | name: The A team 3 | short name: A team 4 | [Colaboration_] 5 | name: SOC 6 | collection: soc 7 | -------------------------------------------------------------------------------- /plugins/plugins/team_collaboration/plugins/Collaboration.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3.3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Seen plug-in for CVE-Search 5 | # 6 | # Software is free software released under the "Modified BSD license" 7 | # 8 | # Copyright (c) 2016 Pieter-Jan Moreels - pieterjan.moreels@gmail.com 9 | 10 | # Necessary imports 11 | import os 12 | import re 13 | import sys 14 | import __main__ 15 | callLocation = os.path.dirname(os.path.realpath(__main__.__file__)) 16 | if __name__ == '__main__': 17 | sys.path.append(os.path.join(callLocation, "..", "..")) 18 | else: 19 | sys.path.append(os.path.join(callLocation, "..")) 20 | 21 | from lib.Plugins import Plugin, WebPlugin 22 | import lib.CVEs as cves 23 | import lib.DatabaseLayer as db 24 | 25 | class Collaboration(WebPlugin): 26 | def __init__(self): 27 | super().__init__() 28 | self.name = "Team Collaboration" 29 | self.shortName = None 30 | self.requiresAuth = True 31 | self.collectionName = self._createCollection() 32 | 33 | def loadSettings(self, reader): 34 | uid = "_"*(len(self.uid)-len(self.uid.strip("_"))) 35 | self.name = reader.read("Collaboration"+uid, "name", "Team Collaboration") 36 | self.shortName = reader.read("Collaboration"+uid, "short name", "") 37 | collection = reader.read("Collaboration"+uid, "collection", "").replace(" ", "_") 38 | self.collectionName = self._createCollection(collection) 39 | 40 | def _getUserSetting(self, user, setting, default): 41 | s = db.p_readUserSetting(self.collectionName, user, setting) 42 | if not s: 43 | db.p_writeUserSetting(self.collectionName, user, setting, default) 44 | s = default 45 | return s 46 | 47 | def _createCollection(self, subset=None): 48 | collection = "team_collab" 49 | if subset: collection = collection + "_" + subset 50 | return collection 51 | 52 | def _userAlowed(self, user): 53 | if user.is_authenticated(): 54 | group = db.p_readSetting(self.collectionName, "group") 55 | if not group: 56 | db.p_writeSetting(self.collectionName, "group", []) 57 | group = [] 58 | if user.get_id() in group: 59 | return True 60 | return False 61 | 62 | def getCVEActions(self, cve, **args): 63 | if self._userAlowed(args["current_user"]): 64 | if db.p_readUserSetting(self.collectionName, args["current_user"].get_id(), "buttons") == "show": 65 | userdata = db.p_queryOne(self.collectionName, {}) 66 | shortname = self.shortName + " " if self.shortName else "" 67 | if userdata and 'cves' in userdata and cve in userdata['cves']: 68 | return [{'text': shortname+'Uncheck', 'action': 'uncheck', 'icon': 'check'}] 69 | else: 70 | return [{'text': shortname+'Check', 'action': 'check', 'icon': 'unchecked'}] 71 | 72 | def onCVEOpen(self, cve, **args): 73 | if self._userAlowed(args["current_user"]): 74 | if db.p_readUserSetting(self.collectionName, args["current_user"].get_id(), "mode") == "auto": 75 | db.p_addToList(self.collectionName, {}, "cves", cve) 76 | 77 | def onCVEAction(self, cve, action, **args): 78 | try: 79 | if args["current_user"].is_authenticated(): 80 | if action == "check": 81 | db.p_addToList(self.collectionName, {}, "cves", cve) 82 | elif action == "uncheck": 83 | db.p_removeFromList(self.collectionName, {}, "cves", cve) 84 | elif action == "save_settings": 85 | mode = args["fields"]["mode"][0] 86 | buttons = args["fields"]["buttons"][0] 87 | mark = args["fields"]["mark"][0] 88 | filters = args["fields"]["filters"][0] 89 | markcolor = args["fields"]["markcolor"][0] 90 | if (mode in ["auto", "manual"] and buttons in ["show", "hide"] and 91 | mark in ["show", "hide"] and filters in ["show", "hide"] and 92 | re.match("^#[0-9A-Fa-f]{6}$", markcolor)): 93 | db.p_writeUserSetting(self.collectionName, args["current_user"].get_id(), "mode", mode) 94 | db.p_writeUserSetting(self.collectionName, args["current_user"].get_id(), "buttons", buttons) 95 | db.p_writeUserSetting(self.collectionName, args["current_user"].get_id(), "mark", mark) 96 | db.p_writeUserSetting(self.collectionName, args["current_user"].get_id(), "filters", filters) 97 | db.p_writeUserSetting(self.collectionName, args["current_user"].get_id(), "markcolor", markcolor) 98 | else: return False 99 | return True 100 | return False 101 | except Exception as e: 102 | print(e) 103 | return False 104 | 105 | def getFilters(self, **args): 106 | if self._userAlowed(args["current_user"]): 107 | if db.p_readUserSetting(self.collectionName, args["current_user"].get_id(), "filters") == "show": 108 | shortname = self.shortName + " " if self.shortName else "" 109 | return [{'id': shortname+'Checked', 'filters': [{'id': self.uid+"_"+'hidechecked', 'type': 'select', 110 | 'values':[{'id':'show', 'text': 'Show'}, 111 | {'id':'hide', 'text': 'Hide'}]}]}] 112 | return [] 113 | 114 | def doFilter(self, filters, **args): 115 | for fil in filters.keys(): 116 | if fil == self.uid+"_"+"hidechecked": 117 | if self._userAlowed(args["current_user"]): 118 | if filters[fil] == "hide": 119 | cves = db.p_queryOne(self.collectionName, {}) 120 | cves = cves["cves"] if cves and 'cves' in cves else [] 121 | return {'id': {"$nin": cves}} 122 | return {} 123 | 124 | def mark(self, cve, **args): 125 | if self._userAlowed(args["current_user"]): 126 | user = args["current_user"].get_id() 127 | if db.p_readUserSetting(self.collectionName, user, "mark") == "show": 128 | color = db.p_readUserSetting(self.collectionName, user, "markcolor") 129 | userdata = db.p_queryOne(self.collectionName, {}) 130 | if userdata and 'cves' in userdata and cve in userdata['cves']: 131 | return (None, color) 132 | 133 | def getPage(self, **args): 134 | if self._userAlowed(args["current_user"]): 135 | mode = self._getUserSetting(args["current_user"].get_id(), "mode", "auto") 136 | buttons = self._getUserSetting(args["current_user"].get_id(), "buttons", "show") 137 | mark = self._getUserSetting(args["current_user"].get_id(), "mark", "show") 138 | filters = self._getUserSetting(args["current_user"].get_id(), "filters", "show") 139 | markcolor = self._getUserSetting(args["current_user"].get_id(), "markcolor", "#345678") 140 | page="team_collaboration.html" 141 | return (page, {"mode": mode, "buttons": buttons, "mark": mark, "filters": filters, 142 | "markcolor": markcolor, "uid": self.uid, "name": self.name}) 143 | 144 | if __name__ == '__main__': 145 | import argparse 146 | argParser = argparse.ArgumentParser(description='Management interface for adding and deleting users from collaboration groups') 147 | argParser.add_argument('-a', type=str, action='append', help='Append user') 148 | argParser.add_argument('-d', type=str, action='append', help='Delete user') 149 | argParser.add_argument('-c', type=str, help='Collection to manipulate') 150 | argParser.add_argument('--drop', action="store_true", help='Drop the collection specified') 151 | args = argParser.parse_args() 152 | 153 | if args.a or args.d: 154 | # Get collection to manipulate 155 | wd = Collaboration() 156 | collection = wd._createCollection(args.c) 157 | # Get list of users 158 | users = db.p_readSetting(collection, "group") 159 | if not users: users = [] 160 | if type(users) is not list: users = [users] 161 | a = args.a if args.a else [] 162 | d = args.d if args.d else [] 163 | for user in a: 164 | if user not in users: 165 | users.append(user) 166 | for user in d: 167 | if user in users: 168 | users.remove(user) 169 | db.p_writeSetting(collection, "group", users) 170 | elif args.drop: 171 | # Get collection to manipulate 172 | wd = Collaboration() 173 | collection = wd._createCollection(args.c) 174 | print("You are manipulating %s"%collection) 175 | confirm = input("Do you want to drop the user list? [y/N]") 176 | if confirm.lower() in ["y", "yes"]: db.p_deleteSettings(collection) 177 | confirm = input("Do you want to drop the data? [y/N]") 178 | if confirm.lower() in ["y", "yes"]: db.p_drop(collection) 179 | -------------------------------------------------------------------------------- /plugins/plugins/team_collaboration/readme.md: -------------------------------------------------------------------------------- 1 | # About this plug-in 2 | This plug-in allows users to work together on tasks where actions have 3 | to be taken for CVEs. It adds a button to the CVE page to check or 4 | uncheck it and allows filtering through these CVEs. The plug-in has 5 | several user settings that can be modified to the users taste. 6 | 7 | Multiple instances of this plug-in can be run, provided you set up the 8 | plugins.ini file correctly. More information below. 9 | 10 | # Installation and setup 11 | The installation of this plug-in is like the installation of any 12 | plug-in, but the setup requires some extra steps. By default, you will 13 | have one team, named "Team Colaboration", and the collection 14 | "team_collab". 15 | 16 | ## The plugins.ini file 17 | The plug-in has three settings in the plugins.ini file. An example could 18 | be: 19 | 20 | ``` 21 | [Collaboration] 22 | name: The A team 23 | short name: A team 24 | [Colaboration_] 25 | name: SOC 26 | collection: soc 27 | ``` 28 | * `name` will be the name displayed in the plug-in info. 29 | **Default:** Team Colaboration 30 | * `short name` will be the name displayed in buttons and filters. 31 | **Default:** <blank> (nothing) 32 | * `collection` is the collection it will use in the database. 33 | **Default:** team_collab 34 | 35 | ## Managing users 36 | You can manage the users in each collection by running the plug-in 37 | directly with python3. This assumes the plug-in is located two 38 | directories deeper than the root directory of CVE-Search (for example 39 | in `./plugins/Team-Collaboration/Collaboration.py`). The reason is 40 | relative imports. 41 | 42 | The usage is pretty selfexplanatory: 43 | * **-h** prints the help 44 | * **-a** adds a user to the specified collection 45 | * **-d** deletes a user from the specified collection 46 | * **-c** specifies the collection (if not specified, uses the default) 47 | * **--drop** drops the specified collection settings and/or data 48 | -------------------------------------------------------------------------------- /plugins/plugins/team_collaboration/web.templates.plugins/team_collaboration.html: -------------------------------------------------------------------------------- 1 | {% extends 'layouts/master-page' %} 2 | {% block head %} 3 | 22 | {% endblock %} 23 | {% block title %}Watchduty settings{% endblock %} 24 | {% block content %} 25 | 26 | 30 | 31 |
32 | Settings
33 | 34 | 35 | 36 | 42 | 43 | 44 | 45 | 51 | 52 | 53 | 54 | 60 | 61 | 62 | 63 | 66 | 67 | 68 | 69 | 75 | 76 |
Mode 37 | 41 |
Buttons 46 | 50 |
Mark CVEs 55 | 59 |
Mark color 64 | 65 |
Filters 70 | 74 |
77 | 78 |
79 | 80 | {% endblock %} 81 | -------------------------------------------------------------------------------- /plugins/readme.md: -------------------------------------------------------------------------------- 1 | # Plug-in Repository 2 | In this directory, we will maintain some of our CVE-Search plug-ins. 3 | These plug-ins can be added and removed however you see fit. 4 | 5 | **The plug-in development of CVE-Search is still in progress!** 6 | 7 | **Update:** In the last update, the plug-in manager is supported in the 8 | official CVE-Search release. 9 | 10 | **Note:** Development of the plug-in feature is mostly done by 11 | [PidgeyL](https://github.com/PidgeyL), so new features will come from 12 | [his branch](https://github.com/PidgeyL/cve-search) first. If you 13 | encounter compatibility issues, please keep this in mind. 14 | 15 | # Installation 16 | The installation of plugins is fairly easy.
17 | As stated earlier, there are two types of plug-ins: Normal plug-ins and web plug-ins. The installation process is almost the same, the web plug-ins just take one more step.
18 | In the installation guide, we will use the bookmark plug-in as an example. 19 | ## Steps 20 | * Open the bookmarks folder. 21 | * Copy the content from the `plugins` folder to your CVE-Search's plug-in folder (We suggest ./plugins/, but you can place it anywhere). 22 | * **For web plug-ins:** copy the html page (in the `web.templates.plugins` folder) to the `./web/templates/plugins` folder of your CVE-Search 23 | * Edit the ./etc/plugins.txt file of your CVE-Search. An example can be found in this repository's etc folder. 24 | 25 | Some plug-ins can load configuration settings. If this is the case, a sample should be provided in the 26 | `etc` folder. 27 | 28 | # Developer information 29 | ## Error codes 30 | * **011** Plug-in page missing - Your plug-in did not respond to `getPage()` 31 | * **012** Plug-in page corrupt - The page your plug-in returned cannot be parsed correctly 32 | * **013** Plug-in page not found - The page your plug-in refers to cannot be found 33 | 34 | ## Variables 35 | When programming a plug-in for CVE-Search, there are a few required and recommended variables. 36 | 37 | * Required 38 | * self.name - **the full name of the plug-in** 39 | * Defaults to be overridden if needed 40 | * self.requiresAuth - **is authentication require to use the plug-in?** - *default: False* 41 | * Recommended 42 | * self.collectionName - **The name of the collection in the database** - We recommend this to ensure you use the same collection accross your plug-in 43 | 44 | ## Functions 45 | There are a few functions you should and should not override. Here is a list 46 | 47 | * Do not override: 48 | * getName() 49 | * getUID() 50 | * setUID(uid) 51 | * isWebPlugin() 52 | * To override (when applicable) - All plug-ins: 53 | * loadSettings(reader) - **loads specified settings from the plug-in settings file** 54 | * onDatabaseUpdate() - **gets triggered when the database gets updated** 55 | * search(text) - **gets triggered when a database search is requested and should be used to search plug-in collections** 56 | * To override (when applicable) - Web plug-ins: 57 | * getPage(\*\*args) - __return a tupel of the file location of the HTML and a dictionary of the args to fill it in. *Example: return ("bookmarks.html", {"cve": cve})*__ 58 | * getCVEActions(\*\*args) - __returns a list of dictionaries with action information *Example: return [{'text': 'Bookmark', 'action': 'bookmark', 'icon': 'star-empty'}]*__ 59 | * onCVEAction(action \*\*args) - __gets triggered when an action button is pressed on the CVE information page__ 60 | * cvePluginInfo(cve, \*\*args) - __gets the HTML of the plug-in information of the CVE *Example: return {'title': "Bookmarks", 'data': "<b> Bookmarked </b>"}*__ 61 | 62 | -------------------------------------------------------------------------------- /plugins/template.py: -------------------------------------------------------------------------------- 1 | ## Minimum required imports 2 | import os 3 | import sys 4 | import __main__ 5 | callLocation = os.path.dirname(os.path.realpath(__main__.__file__)) 6 | sys.path.append(os.path.join(callLocation, "..")) 7 | 8 | import lib.DatabaseLayer as db 9 | from lib.Plugins import Plugin, WebPlugin 10 | 11 | ## The class name should be the same as the file name 12 | class template(WebPlugin): 13 | ## You may also wish to inherit from Plug-in, if your plug-in is not related to the web interface 14 | def __init__(self): 15 | self.name = "template" ## What is the name of this plug-in? 16 | self.requiresAuth = False ## Do users have to log in to use the plug-in? 17 | self.collectionName = "template" ## Not required, but highly recommended 18 | # self.text = "default text" ## Just a variable for this example 19 | 20 | ## Do not override the following functions. 21 | ## You may however wish to use them within your plug-in 22 | def getName(self): return self.name ## Used by the plug-in manager 23 | def getUID(self): return self.uid ## The UID is set by the plug-in manager. Editing the UID might result in plug-in failure. 24 | def setUID(self, uid): self.uid = uid ## Also used by the plug-in manager. 25 | def isWebPlugin(self): return True ## Distinguish the difference between web plug-ins and regular plug-ins. 26 | 27 | ## These functions may be overridden if you wish to use them 28 | ## When overridden, these functions should not return anything: 29 | 30 | ## Read the settings from ./etc/plugins.ini 31 | def loadSettings(self, reader): 32 | # self.text = reader.read("Template", "text", "Default text") 33 | pass 34 | 35 | ## This function gets triggered when the database is being updated. 36 | def onDatabaseUpdate(self): 37 | pass 38 | 39 | ## When overridden, these functions should have a return value: 40 | 41 | ## This function gets triggered when the database is being queried 42 | def search(self, text): 43 | # return {'n': 'reason', 'd': db.p_queryData(self.collectionName, {'field': {"$regex": text, "$options": "-i"}})} 44 | ## the key 'n' is the reason the results match the search criteria, 'd' is the data 45 | pass 46 | 47 | ## 48 | ## These functions are specific for web plug-ins only. 49 | ## 50 | 51 | ## To override with returns 52 | 53 | ## Return the page you see when surfing to /plugins/ 54 | def getPage(self, **args): 55 | # return ("template.html", {'title': "Default title", 'text': self.text}) 56 | ## The root of the html files is ./web/templates/plugins/ 57 | return (None, None) 58 | 59 | ## Return subpages from the /plugins// 60 | def getSubpage(self, page, **args): 61 | # text = "default" 62 | # if page == "test": 63 | # text = "test" 64 | # return ("template.html", {'title': "Subpage %s"%page, 'text': text}) 65 | return (None, None) 66 | 67 | ## Get list of available actions for CVEs 68 | def getCVEActions(self, cve, **args): 69 | # return [{'text': 'template action', 'action': 'pointless_action', 'icon': 'ice-lolly'}] 70 | ## You should have at least 'text' or 'icon' and 'action' 71 | return [] 72 | 73 | ## Get filters you can use to filter the data on 74 | def getFilters(self, **args): 75 | # return [{'id': 'template', 'filters': [{'id': 'tSelect', 'type': 'select', 'values':[{'id':'show', 'text': 'Show'}, 76 | # {'id':'hide', 'text': 'Hide'}]}, 77 | # {'id': 'tText', 'type': 'text'} ]}] 78 | return [] 79 | 80 | ## Transform web-filters to database filters 81 | def doFilter(self, filters, **args): 82 | # for fil in filters.keys(): 83 | # if fil == "tSelect": 84 | # text = filters.get("tText", None) 85 | # if filters[fil] == "hide" and text: 86 | # return {"summary":{"$not": {"$regex":text}}} 87 | # return {} 88 | return [] 89 | 90 | ## Get the information related to the plug-in 91 | def cvePluginInfo(self, cve, **args): 92 | # return {'title': "Template", 'data': 'just some random data'} 93 | pass 94 | 95 | ## Mark CVEs with specific colors or icons 96 | def mark(self, cve, **args): 97 | # end = int(cve[-5:].strip("-")) 98 | # if end % 4 == 0 99 | # return (None, "#880000") 100 | # elif end % 5 == 0 101 | # return ("ice-lolly", None) 102 | ## These are some pointless markings, but they work as an example :) 103 | return (None, None) 104 | 105 | ## To override without returns 106 | 107 | ## Gets triggered when a user clicks on an action button within the CVE page 108 | def onCVEAction(self, cve, action, **args): 109 | # if action == "pointless_action": 110 | # print("It's a template, don't expect anything fancy ;)") 111 | pass 112 | 113 | ## Gets triggered when a CVE gets opened 114 | def onCVEOpen(self, cve, **args): 115 | # print("The template opened %s"%cve) 116 | pass 117 | 118 | ## Database functions for plug-ins are: 119 | # db.p_writeSetting(collection, setting, value) 120 | # db.p_readSetting(collection, setting) 121 | ## Query Data 122 | # db.p_queryData(collection, query) 123 | # db.p_queryOne(collection, query) 124 | ## Record manipulation 125 | # db.p_addEntry(collection, data) 126 | # db.p_removeEntry(collection, query) 127 | # db.p_bulkUpdate(collection, query, data) 128 | ## List manipulation 129 | # db.p_addToList(collection, query, listname, data) 130 | # db.p_removeFromList(collection, query, listname, data) 131 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Modules 2 | This repository contains all the modules developed and maintained by the 3 | [**CVE-Search**](https://github.com/cve-search/cve-search) team, that 4 | can be used to customize your CVE-Search instance. Below you can find a 5 | list of the type of customizations you can do, as well as the current 6 | modules we support: 7 | 8 | ## Plug-ins 9 | * **bookmarks** - Bookmark certain CVE's for later reference 10 | * **MISP** - Enrich your CVE-Search instance with MISP info 11 | * **notes** - Allow users to add notes to a CVE 12 | * **Reporting** - Make queries on the data and export them to a CSV 13 | file 14 | * **seen** - Keep track of all the CVEs you've already seen in the past 15 | * **sendMail** - Easily send a mail with the CVE info to a specified 16 | mail address 17 | * **team_collaboration** - Similar to `seen`, but on group level 18 | 19 | ## Authentication modules 20 | * **LDAP** - Authenticate users over LDAP 21 | 22 | # Support 23 | If you wish to share your plug-ins and modules with us, you can always 24 | create a pull request. However, please: 25 | * **Maintain** your plug-in, as CVE-Search grows and changes 26 | * Make sure you submit plug-ins for the **latest version** of 27 | CVE-Search 28 | * You document your plug-ins and their behavior well, by adding a 29 | **descriptive readme file** 30 | 31 | #License 32 | cve-search and its modules are free software released under the "Modified BSD license" 33 | 34 | Copyright (c) 2016 Pieter-Jan Moreels - https://github.com/pidgeyl/ 35 | --------------------------------------------------------------------------------