Compare commits
107 Commits
Author | SHA1 | Date | |
---|---|---|---|
39c2f8df2d | |||
393ec25003 | |||
dfc4403779 | |||
bf2d2f86cd | |||
d05296f516 | |||
fe3b5b272c | |||
a3a35502ec | |||
e19dfd000c | |||
927bbe618f | |||
4386001111 | |||
ba3cef409d | |||
3f129f8c76 | |||
969c4ae8e5 | |||
32b54391e5 | |||
aa9fa5b652 | |||
a432491e7e | |||
3ce6065b38 | |||
6b8e699eb6 | |||
a8ca4153fc | |||
5ebe05e584 | |||
f600d0b88c | |||
488bd09dad | |||
836515f6d1 | |||
155c3ba163 | |||
c350ec73c2 | |||
44d75c397f | |||
fb76b0a1ce | |||
a1671a953f | |||
ff6fec3a38 | |||
f441f4f992 | |||
5df4d127ca | |||
1597ad13e8 | |||
26bcb10409 | |||
74dd2d7ae7 | |||
9c27f5d4a2 | |||
98a84af5d0 | |||
e2c2b47d4d | |||
5b6a51f02c | |||
ea864d09ac | |||
be06f4811a | |||
762734d6a6 | |||
e752655990 | |||
eb9695605b | |||
47409ba0b9 | |||
1a0406fdb2 | |||
b31d1b8098 | |||
b49dfbde89 | |||
92cad06b2b | |||
e8340e6b67 | |||
f922e396de | |||
7919d3e569 | |||
5264aea802 | |||
c320fc8c63 | |||
afffef306a | |||
5539dd4ed8 | |||
97ed4c7838 | |||
ac364f149d | |||
abf6b9f048 | |||
d41011a5ed | |||
24cf2ca623 | |||
73ae99f054 | |||
51fa81cb05 | |||
7999810d28 | |||
50f4d76fec | |||
85cca87e58 | |||
0c3200567e | |||
8d59831855 | |||
7c2aa54156 | |||
2b6afe31c2 | |||
e6c397397b | |||
a2e27d816b | |||
e3907940bc | |||
08334973be | |||
50f6c43a2f | |||
9c7600b264 | |||
eea417a0d9 | |||
e8d0c71bfb | |||
5dd20a74e9 | |||
ca5500add4 | |||
982d51a465 | |||
ee327874e5 | |||
dd66414fe7 | |||
97d2a2606c | |||
74ebf43492 | |||
00f9b7659b | |||
96e7211e77 | |||
29a91cbfb2 | |||
275b89c94d | |||
603050e7b3 | |||
71c090d0f3 | |||
367930c5c8 | |||
952c934b9c | |||
edbae986b3 | |||
5b45da69a8 | |||
bd1430ebbf | |||
ad184689f8 | |||
496fd23121 | |||
778a07535f | |||
2547873bd2 | |||
8f9932e56b | |||
0710b38de3 | |||
5671b70bdd | |||
d6980d8229 | |||
76d54abdc6 | |||
7240fd9028 | |||
db0d7a0491 | |||
3bd76e0217 |
1
.gitmodules
vendored
1
.gitmodules
vendored
@ -1,3 +1,4 @@
|
|||||||
[submodule "tests/data"]
|
[submodule "tests/data"]
|
||||||
path = tests/data
|
path = tests/data
|
||||||
url = https://github.com/HASecuritySolutions/VulnWhisperer-tests.git
|
url = https://github.com/HASecuritySolutions/VulnWhisperer-tests.git
|
||||||
|
branch = master
|
||||||
|
@ -22,13 +22,13 @@ before_install:
|
|||||||
install:
|
install:
|
||||||
- pip install -r requirements.txt
|
- pip install -r requirements.txt
|
||||||
- pip install flake8 # pytest # add another testing frameworks later
|
- pip install flake8 # pytest # add another testing frameworks later
|
||||||
|
- python setup.py install
|
||||||
before_script:
|
before_script:
|
||||||
# stop the build if there are Python syntax errors or undefined names
|
# stop the build if there are Python syntax errors or undefined names
|
||||||
- flake8 . --count --exclude=deps/qualysapi --select=E901,E999,F821,F822,F823 --show-source --statistics
|
- flake8 . --count --exclude=deps/qualysapi --select=E901,E999,F821,F822,F823 --show-source --statistics
|
||||||
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
|
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
|
||||||
- flake8 . --count --exit-zero --exclude=deps/qualysapi --max-complexity=10 --max-line-length=127 --statistics
|
- flake8 . --count --exit-zero --exclude=deps/qualysapi --max-complexity=10 --max-line-length=127 --statistics
|
||||||
script:
|
script:
|
||||||
- python setup.py install
|
|
||||||
- bash tests/test-vuln_whisperer.sh
|
- bash tests/test-vuln_whisperer.sh
|
||||||
- bash tests/test-docker.sh
|
- bash tests/test-docker.sh
|
||||||
notifications:
|
notifications:
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
FROM centos:7
|
FROM centos:latest
|
||||||
|
|
||||||
MAINTAINER Justin Henderson justin@hasecuritysolutions.com
|
MAINTAINER Justin Henderson justin@hasecuritysolutions.com
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ Currently Supports
|
|||||||
|
|
||||||
### Reporting Frameworks
|
### Reporting Frameworks
|
||||||
|
|
||||||
- [X] [ELK (**v6**/**v7**)](https://www.elastic.co/elk-stack)
|
- [X] [ELK](https://www.elastic.co/elk-stack)
|
||||||
- [X] [Jira](https://www.atlassian.com/software/jira)
|
- [X] [Jira](https://www.atlassian.com/software/jira)
|
||||||
- [ ] [Splunk](https://www.splunk.com/)
|
- [ ] [Splunk](https://www.splunk.com/)
|
||||||
|
|
||||||
|
@ -3,13 +3,14 @@
|
|||||||
__author__ = 'Austin Taylor'
|
__author__ = 'Austin Taylor'
|
||||||
|
|
||||||
|
|
||||||
from vulnwhisp.vulnwhisp import vulnWhisperer
|
import argparse
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
from vulnwhisp.base.config import vwConfig
|
from vulnwhisp.base.config import vwConfig
|
||||||
from vulnwhisp.test.mock import mockAPI
|
from vulnwhisp.test.mock import mockAPI
|
||||||
import os
|
from vulnwhisp.vulnwhisp import vulnWhisperer
|
||||||
import argparse
|
|
||||||
import sys
|
|
||||||
import logging
|
|
||||||
|
|
||||||
|
|
||||||
def isFileValid(parser, arg):
|
def isFileValid(parser, arg):
|
||||||
@ -27,16 +28,18 @@ def main():
|
|||||||
help='Path of config file', type=lambda x: isFileValid(parser, x.strip()))
|
help='Path of config file', type=lambda x: isFileValid(parser, x.strip()))
|
||||||
parser.add_argument('-s', '--section', dest='section', required=False,
|
parser.add_argument('-s', '--section', dest='section', required=False,
|
||||||
help='Section in config')
|
help='Section in config')
|
||||||
|
parser.add_argument('-f', '--filter', dest='scan_filter', required=False,
|
||||||
|
help='Retrieve scans matching this regex pattern')
|
||||||
|
parser.add_argument('--days', dest='days', type=int, required=False,
|
||||||
|
help='Retrieve scans from this many days ago to now')
|
||||||
|
parser.add_argument('-l', '--list', dest='list_scans', required=False, action="store_true",
|
||||||
|
help='List available scans')
|
||||||
parser.add_argument('--source', dest='source', required=False,
|
parser.add_argument('--source', dest='source', required=False,
|
||||||
help='JIRA required only! Source scanner to report')
|
help='JIRA required only! Source scanner to report')
|
||||||
parser.add_argument('-n', '--scanname', dest='scanname', required=False,
|
parser.add_argument('-n', '--scanname', dest='scanname', required=False,
|
||||||
help='JIRA required only! Scan name from scan to report')
|
help='JIRA required only! Scan name from scan to report')
|
||||||
parser.add_argument('-v', '--verbose', dest='verbose', action='store_true', default=True,
|
parser.add_argument('-v', '--verbose', dest='verbose', action='store_true',
|
||||||
help='Prints status out to screen (defaults to True)')
|
help='Prints status out to screen (defaults to True)')
|
||||||
parser.add_argument('-u', '--username', dest='username', required=False, default=None,
|
|
||||||
help='The NESSUS username', type=lambda x: x.strip())
|
|
||||||
parser.add_argument('-p', '--password', dest='password', required=False, default=None,
|
|
||||||
help='The NESSUS password', type=lambda x: x.strip())
|
|
||||||
parser.add_argument('-F', '--fancy', action='store_true',
|
parser.add_argument('-F', '--fancy', action='store_true',
|
||||||
help='Enable colourful logging output')
|
help='Enable colourful logging output')
|
||||||
parser.add_argument('-d', '--debug', action='store_true',
|
parser.add_argument('-d', '--debug', action='store_true',
|
||||||
@ -52,12 +55,12 @@ def main():
|
|||||||
stream=sys.stdout,
|
stream=sys.stdout,
|
||||||
# format only applies when not using -F flag for colouring
|
# format only applies when not using -F flag for colouring
|
||||||
format='%(levelname)s:%(name)s:%(funcName)s:%(message)s',
|
format='%(levelname)s:%(name)s:%(funcName)s:%(message)s',
|
||||||
level=logging.DEBUG if args.debug else logging.INFO
|
level=logging.DEBUG if args.debug else logging.INFO if args.verbose else logging.WARNING
|
||||||
)
|
)
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
# we set up the logger to log as well to file
|
# we set up the logger to log as well to file
|
||||||
fh = logging.FileHandler('vulnwhisperer.log')
|
fh = logging.FileHandler('vulnwhisperer.log')
|
||||||
fh.setLevel(logging.DEBUG if args.debug else logging.INFO)
|
fh.setLevel(logging.DEBUG if args.debug else logging.INFO if args.verbose else logging.WARNING)
|
||||||
fh.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(name)s - %(funcName)s:%(message)s", "%Y-%m-%d %H:%M:%S"))
|
fh.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(name)s - %(funcName)s:%(message)s", "%Y-%m-%d %H:%M:%S"))
|
||||||
logger.addHandler(fh)
|
logger.addHandler(fh)
|
||||||
|
|
||||||
@ -74,43 +77,44 @@ def main():
|
|||||||
try:
|
try:
|
||||||
if args.config and not args.section:
|
if args.config and not args.section:
|
||||||
# this remains a print since we are in the main binary
|
# this remains a print since we are in the main binary
|
||||||
print('WARNING: {warning}'.format(warning='No section was specified, vulnwhisperer will scrape enabled modules from config file. \
|
print(
|
||||||
|
"WARNING: No section was specified, vulnwhisperer will scrape enabled modules from config file. \
|
||||||
\nPlease specify a section using -s. \
|
\nPlease specify a section using -s. \
|
||||||
\nExample vuln_whisperer -c config.ini -s nessus'))
|
\nExample vuln_whisperer -c config.ini -s nessus"
|
||||||
|
)
|
||||||
logger.info('No section was specified, vulnwhisperer will scrape enabled modules from the config file.')
|
logger.info('No section was specified, vulnwhisperer will scrape enabled modules from the config file.')
|
||||||
|
|
||||||
config = vwConfig(config_in=args.config)
|
config = vwConfig(config_in=args.config)
|
||||||
enabled_sections = config.get_sections_with_attribute('enabled')
|
enabled_sections = config.get_sections_with_attribute('enabled')
|
||||||
|
|
||||||
for section in enabled_sections:
|
for section in enabled_sections:
|
||||||
try:
|
|
||||||
vw = vulnWhisperer(config=args.config,
|
vw = vulnWhisperer(config=args.config,
|
||||||
profile=section,
|
profile=section,
|
||||||
verbose=args.verbose,
|
verbose=args.verbose,
|
||||||
username=args.username,
|
debug=args.debug,
|
||||||
password=args.password,
|
|
||||||
source=args.source,
|
source=args.source,
|
||||||
scanname=args.scanname)
|
scan_filter=args.scan_filter,
|
||||||
|
days=args.days,
|
||||||
|
scanname=args.scanname,
|
||||||
|
list_scans=args.list_scans)
|
||||||
exit_code += vw.whisper_vulnerabilities()
|
exit_code += vw.whisper_vulnerabilities()
|
||||||
except Exception as e:
|
|
||||||
logger.error("VulnWhisperer was unable to perform the processing on '{}'".format(section))
|
|
||||||
else:
|
else:
|
||||||
logger.info('Running vulnwhisperer for section {}'.format(args.section))
|
logger.info('Running vulnwhisperer for section {}'.format(args.section))
|
||||||
vw = vulnWhisperer(config=args.config,
|
vw = vulnWhisperer(config=args.config,
|
||||||
profile=args.section,
|
profile=args.section,
|
||||||
verbose=args.verbose,
|
verbose=args.verbose,
|
||||||
username=args.username,
|
debug=args.debug,
|
||||||
password=args.password,
|
|
||||||
source=args.source,
|
source=args.source,
|
||||||
scanname=args.scanname)
|
scan_filter=args.scan_filter,
|
||||||
|
days=args.days,
|
||||||
|
scanname=args.scanname,
|
||||||
|
list_scans=args.list_scans)
|
||||||
exit_code += vw.whisper_vulnerabilities()
|
exit_code += vw.whisper_vulnerabilities()
|
||||||
|
|
||||||
close_logging_handlers(logger)
|
close_logging_handlers(logger)
|
||||||
sys.exit(exit_code)
|
sys.exit(exit_code)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if args.verbose:
|
|
||||||
# this will remain a print since we are in the main binary
|
|
||||||
logger.error('{}'.format(str(e)))
|
logger.error('{}'.format(str(e)))
|
||||||
print('ERROR: {error}'.format(error=e))
|
print('ERROR: {error}'.format(error=e))
|
||||||
# TODO: fix this to NOT be exit 2 unless in error
|
# TODO: fix this to NOT be exit 2 unless in error
|
||||||
|
@ -9,7 +9,8 @@ password=nessus_password
|
|||||||
write_path=/opt/VulnWhisperer/data/nessus/
|
write_path=/opt/VulnWhisperer/data/nessus/
|
||||||
db_path=/opt/VulnWhisperer/data/database
|
db_path=/opt/VulnWhisperer/data/database
|
||||||
trash=false
|
trash=false
|
||||||
verbose=true
|
verbose=false
|
||||||
|
scan_filter=
|
||||||
|
|
||||||
[tenable]
|
[tenable]
|
||||||
enabled=true
|
enabled=true
|
||||||
@ -22,7 +23,8 @@ password=tenable.io_password
|
|||||||
write_path=/opt/VulnWhisperer/data/tenable/
|
write_path=/opt/VulnWhisperer/data/tenable/
|
||||||
db_path=/opt/VulnWhisperer/data/database
|
db_path=/opt/VulnWhisperer/data/database
|
||||||
trash=false
|
trash=false
|
||||||
verbose=true
|
verbose=false
|
||||||
|
scan_filter=
|
||||||
|
|
||||||
[qualys_web]
|
[qualys_web]
|
||||||
#Reference https://www.qualys.com/docs/qualys-was-api-user-guide.pdf to find your API
|
#Reference https://www.qualys.com/docs/qualys-was-api-user-guide.pdf to find your API
|
||||||
@ -33,6 +35,7 @@ password = examplepass
|
|||||||
write_path=/opt/VulnWhisperer/data/qualys_web/
|
write_path=/opt/VulnWhisperer/data/qualys_web/
|
||||||
db_path=/opt/VulnWhisperer/data/database
|
db_path=/opt/VulnWhisperer/data/database
|
||||||
verbose=true
|
verbose=true
|
||||||
|
scan_filter=
|
||||||
|
|
||||||
# Set the maximum number of retries each connection should attempt.
|
# Set the maximum number of retries each connection should attempt.
|
||||||
#Note, this applies only to failed connections and timeouts, never to requests where the server returns a response.
|
#Note, this applies only to failed connections and timeouts, never to requests where the server returns a response.
|
||||||
@ -41,14 +44,15 @@ max_retries = 10
|
|||||||
template_id = 126024
|
template_id = 126024
|
||||||
|
|
||||||
[qualys_vuln]
|
[qualys_vuln]
|
||||||
#Reference https://www.qualys.com/docs/qualys-api-vmpc-user-guide.pdf to find your API
|
#Reference https://www.qualys.com/docs/qualys-was-api-user-guide.pdf to find your API
|
||||||
enabled = true
|
enabled = true
|
||||||
hostname = qualysapi.qg2.apps.qualys.com
|
hostname = qualysapi.qg2.apps.qualys.com
|
||||||
username = exampleuser
|
username = exampleuser
|
||||||
password = examplepass
|
password = examplepass
|
||||||
write_path=/opt/VulnWhisperer/data/qualys_vuln/
|
write_path=/opt/VulnWhisperer/data/qualys_vuln/
|
||||||
db_path=/opt/VulnWhisperer/data/database
|
db_path=/opt/VulnWhisperer/data/database
|
||||||
verbose=true
|
verbose=false
|
||||||
|
scan_filter=
|
||||||
|
|
||||||
[detectify]
|
[detectify]
|
||||||
#Reference https://developer.detectify.com/
|
#Reference https://developer.detectify.com/
|
||||||
@ -61,6 +65,7 @@ password = examplepass
|
|||||||
write_path =/opt/VulnWhisperer/data/detectify/
|
write_path =/opt/VulnWhisperer/data/detectify/
|
||||||
db_path = /opt/VulnWhisperer/data/database
|
db_path = /opt/VulnWhisperer/data/database
|
||||||
verbose = true
|
verbose = true
|
||||||
|
scan_filter=
|
||||||
|
|
||||||
[openvas]
|
[openvas]
|
||||||
enabled = false
|
enabled = false
|
||||||
@ -70,7 +75,8 @@ username = exampleuser
|
|||||||
password = examplepass
|
password = examplepass
|
||||||
write_path=/opt/VulnWhisperer/data/openvas/
|
write_path=/opt/VulnWhisperer/data/openvas/
|
||||||
db_path=/opt/VulnWhisperer/data/database
|
db_path=/opt/VulnWhisperer/data/database
|
||||||
verbose=true
|
verbose=false
|
||||||
|
scan_filter=
|
||||||
|
|
||||||
[jira]
|
[jira]
|
||||||
enabled = false
|
enabled = false
|
||||||
|
@ -6,10 +6,11 @@ access_key=
|
|||||||
secret_key=
|
secret_key=
|
||||||
username=nessus_username
|
username=nessus_username
|
||||||
password=nessus_password
|
password=nessus_password
|
||||||
write_path=/tmp/VulnWhisperer/data/nessus/
|
write_path=/opt/VulnWhisperer/data/nessus/
|
||||||
db_path=/tmp/VulnWhisperer/data/database
|
db_path=/opt/VulnWhisperer/data/database
|
||||||
trash=false
|
trash=false
|
||||||
verbose=true
|
verbose=false
|
||||||
|
scan_filter=
|
||||||
|
|
||||||
[tenable]
|
[tenable]
|
||||||
enabled=true
|
enabled=true
|
||||||
@ -19,36 +20,39 @@ access_key=
|
|||||||
secret_key=
|
secret_key=
|
||||||
username=tenable.io_username
|
username=tenable.io_username
|
||||||
password=tenable.io_password
|
password=tenable.io_password
|
||||||
write_path=/tmp/VulnWhisperer/data/tenable/
|
write_path=/opt/VulnWhisperer/data/tenable/
|
||||||
db_path=/tmp/VulnWhisperer/data/database
|
db_path=/opt/VulnWhisperer/data/database
|
||||||
trash=false
|
trash=false
|
||||||
verbose=true
|
verbose=false
|
||||||
|
scan_filter=
|
||||||
|
|
||||||
[qualys_web]
|
[qualys_was]
|
||||||
#Reference https://www.qualys.com/docs/qualys-was-api-user-guide.pdf to find your API
|
#Reference https://www.qualys.com/docs/qualys-was-api-user-guide.pdf to find your API
|
||||||
enabled = false
|
enabled=true
|
||||||
hostname = qualys_web
|
hostname=qualys_was
|
||||||
username=exampleuser
|
username=exampleuser
|
||||||
password=examplepass
|
password=examplepass
|
||||||
write_path=/tmp/VulnWhisperer/data/qualys_web/
|
write_path=/opt/VulnWhisperer/data/qualys_was/
|
||||||
db_path=/tmp/VulnWhisperer/data/database
|
db_path=/opt/VulnWhisperer/data/database
|
||||||
verbose=true
|
verbose=false
|
||||||
|
scan_filter=
|
||||||
|
|
||||||
# Set the maximum number of retries each connection should attempt.
|
# Set the maximum number of retries each connection should attempt.
|
||||||
#Note, this applies only to failed connections and timeouts, never to requests where the server returns a response.
|
#Note, this applies only to failed connections and timeouts, never to requests where the server returns a response.
|
||||||
max_retries=10
|
max_retries=10
|
||||||
# Template ID will need to be retrieved for each document. Please follow the reference guide above for instructions on how to get your template ID.
|
# Template ID will need to be retrieved for each document. Please follow the reference guide above for instructions on how to get your template ID.
|
||||||
template_id = 126024
|
template_id=289109
|
||||||
|
|
||||||
[qualys_vuln]
|
[qualys_vm]
|
||||||
#Reference https://www.qualys.com/docs/qualys-was-api-user-guide.pdf to find your API
|
#Reference https://www.qualys.com/docs/qualys-was-api-user-guide.pdf to find your API
|
||||||
enabled=true
|
enabled=true
|
||||||
hostname = qualys_vuln
|
hostname=qualys_vm
|
||||||
username=exampleuser
|
username=exampleuser
|
||||||
password=examplepass
|
password=examplepass
|
||||||
write_path=/tmp/VulnWhisperer/data/qualys_vuln/
|
write_path=/opt/VulnWhisperer/data/qualys_vm/
|
||||||
db_path=/tmp/VulnWhisperer/data/database
|
db_path=/opt/VulnWhisperer/data/database
|
||||||
verbose=true
|
verbose=false
|
||||||
|
scan_filter=
|
||||||
|
|
||||||
[detectify]
|
[detectify]
|
||||||
#Reference https://developer.detectify.com/
|
#Reference https://developer.detectify.com/
|
||||||
@ -58,33 +62,35 @@ hostname = detectify
|
|||||||
username=exampleuser
|
username=exampleuser
|
||||||
#password variable used as secretKey
|
#password variable used as secretKey
|
||||||
password=examplepass
|
password=examplepass
|
||||||
write_path =/tmp/VulnWhisperer/data/detectify/
|
write_path =/opt/VulnWhisperer/data/detectify/
|
||||||
db_path = /tmp/VulnWhisperer/data/database
|
db_path=/opt/VulnWhisperer/data/database
|
||||||
verbose = true
|
verbose=false
|
||||||
|
scan_filter=
|
||||||
|
|
||||||
[openvas]
|
[openvas]
|
||||||
enabled = false
|
enabled=true
|
||||||
hostname=openvas
|
hostname=openvas
|
||||||
port=4000
|
port=4000
|
||||||
username=exampleuser
|
username=exampleuser
|
||||||
password=examplepass
|
password=examplepass
|
||||||
write_path=/tmp/VulnWhisperer/data/openvas/
|
write_path=/opt/VulnWhisperer/data/openvas/
|
||||||
db_path=/tmp/VulnWhisperer/data/database
|
db_path=/opt/VulnWhisperer/data/database
|
||||||
verbose=true
|
verbose=false
|
||||||
|
scan_filter=
|
||||||
|
|
||||||
[jira]
|
[jira]
|
||||||
enabled=false
|
enabled=false
|
||||||
hostname=jira-host
|
hostname=jira-host
|
||||||
username=username
|
username=username
|
||||||
password=password
|
password=password
|
||||||
write_path = /tmp/VulnWhisperer/data/jira/
|
write_path=/opt/VulnWhisperer/data/jira/
|
||||||
db_path = /tmp/VulnWhisperer/data/database
|
db_path=/opt/VulnWhisperer/data/database
|
||||||
verbose = true
|
verbose=false
|
||||||
dns_resolv=False
|
dns_resolv=False
|
||||||
|
|
||||||
#Sample jira report scan, will automatically be created for existent scans
|
#Sample jira report scan, will automatically be created for existent scans
|
||||||
#[jira.qualys_vuln.test_scan]
|
#[jira.qualys_vm.test_scan]
|
||||||
#source = qualys_vuln
|
#source=qualys_vm
|
||||||
#scan_name=Test Scan
|
#scan_name=Test Scan
|
||||||
#jira_project=PROJECT
|
#jira_project=PROJECT
|
||||||
; if multiple components, separate by ","=None
|
; if multiple components, separate by ","=None
|
||||||
|
@ -9,6 +9,7 @@ services:
|
|||||||
- "ES_JAVA_OPTS=-Xms1g -Xmx1g"
|
- "ES_JAVA_OPTS=-Xms1g -Xmx1g"
|
||||||
- xpack.security.enabled=false
|
- xpack.security.enabled=false
|
||||||
- cluster.routing.allocation.disk.threshold_enabled=false
|
- cluster.routing.allocation.disk.threshold_enabled=false
|
||||||
|
- path.repo=/snapshots
|
||||||
ulimits:
|
ulimits:
|
||||||
memlock:
|
memlock:
|
||||||
soft: -1
|
soft: -1
|
||||||
@ -80,6 +81,7 @@ services:
|
|||||||
entrypoint: [
|
entrypoint: [
|
||||||
"vuln_whisperer",
|
"vuln_whisperer",
|
||||||
"-F",
|
"-F",
|
||||||
|
"-v",
|
||||||
"-c",
|
"-c",
|
||||||
"/opt/VulnWhisperer/vulnwhisperer.ini",
|
"/opt/VulnWhisperer/vulnwhisperer.ini",
|
||||||
"--mock",
|
"--mock",
|
||||||
|
@ -8,6 +8,7 @@ services:
|
|||||||
- bootstrap.memory_lock=true
|
- bootstrap.memory_lock=true
|
||||||
- "ES_JAVA_OPTS=-Xms1g -Xmx1g"
|
- "ES_JAVA_OPTS=-Xms1g -Xmx1g"
|
||||||
- xpack.security.enabled=false
|
- xpack.security.enabled=false
|
||||||
|
- path.repo=/snapshots
|
||||||
ulimits:
|
ulimits:
|
||||||
memlock:
|
memlock:
|
||||||
soft: -1
|
soft: -1
|
||||||
@ -18,6 +19,7 @@ services:
|
|||||||
mem_limit: 8g
|
mem_limit: 8g
|
||||||
volumes:
|
volumes:
|
||||||
- esdata1:/usr/share/elasticsearch/data
|
- esdata1:/usr/share/elasticsearch/data
|
||||||
|
- ./data/es_snapshots:/snapshots
|
||||||
ports:
|
ports:
|
||||||
- 9200:9200
|
- 9200:9200
|
||||||
#restart: always
|
#restart: always
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
pandas==0.20.3
|
pandas==0.20.3
|
||||||
setuptools==40.4.3
|
setuptools==40.4.3
|
||||||
pytz==2017.2
|
pytz==2017.2
|
||||||
Requests==2.20.0
|
Requests==2.18.3
|
||||||
lxml==4.1.1
|
lxml==4.1.1
|
||||||
future-fstrings
|
future-fstrings
|
||||||
bs4
|
bs4
|
||||||
jira
|
jira
|
||||||
bottle
|
bottle
|
||||||
coloredlogs
|
coloredlogs
|
||||||
qualysapi==6.0.0
|
qualysapi>=5.1.0
|
||||||
httpretty
|
httpretty
|
@ -1,72 +0,0 @@
|
|||||||
version: '2'
|
|
||||||
services:
|
|
||||||
vulnwhisp-es1:
|
|
||||||
image: docker.elastic.co/elasticsearch/elasticsearch:5.6.2
|
|
||||||
container_name: vulnwhisp-es1
|
|
||||||
environment:
|
|
||||||
- cluster.name=vulnwhisperer
|
|
||||||
- bootstrap.memory_lock=true
|
|
||||||
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
|
|
||||||
ulimits:
|
|
||||||
memlock:
|
|
||||||
soft: -1
|
|
||||||
hard: -1
|
|
||||||
nofile:
|
|
||||||
soft: 65536
|
|
||||||
hard: 65536
|
|
||||||
mem_limit: 8g
|
|
||||||
volumes:
|
|
||||||
- esdata1:/usr/share/elasticsearch/data
|
|
||||||
ports:
|
|
||||||
- 9200:9200
|
|
||||||
environment:
|
|
||||||
- xpack.security.enabled=false
|
|
||||||
#restart: always
|
|
||||||
networks:
|
|
||||||
esnet:
|
|
||||||
aliases:
|
|
||||||
- vulnwhisp-es1.local
|
|
||||||
vulnwhisp-ks1:
|
|
||||||
image: docker.elastic.co/kibana/kibana:5.6.2
|
|
||||||
environment:
|
|
||||||
SERVER_NAME: vulnwhisp-ks1
|
|
||||||
ELASTICSEARCH_URL: http://vulnwhisp-es1:9200
|
|
||||||
ports:
|
|
||||||
- 5601:5601
|
|
||||||
depends_on:
|
|
||||||
- vulnwhisp-es1
|
|
||||||
networks:
|
|
||||||
esnet:
|
|
||||||
aliases:
|
|
||||||
- vulnwhisp-ks1.local
|
|
||||||
vulnwhisp-ls1:
|
|
||||||
image: docker.elastic.co/logstash/logstash:5.6.2
|
|
||||||
container_name: vulnwhisp-ls1
|
|
||||||
volumes:
|
|
||||||
- ./docker/1000_nessus_process_file.conf:/usr/share/logstash/pipeline/1000_nessus_process_file.conf
|
|
||||||
- ./docker/2000_qualys_web_scans.conf:/usr/share/logstash/pipeline/2000_qualys_web_scans.conf
|
|
||||||
- ./docker/3000_openvas.conf:/usr/share/logstash/pipeline/3000_openvas.conf
|
|
||||||
- ./docker/4000_jira.conf:/usr/share/logstash/pipeline/4000_jira.conf
|
|
||||||
- ./docker/logstash.yml:/usr/share/logstash/config/logstash.yml
|
|
||||||
- ./data/:/opt/VulnWhisperer/data
|
|
||||||
environment:
|
|
||||||
- xpack.monitoring.enabled=false
|
|
||||||
depends_on:
|
|
||||||
- vulnwhisp-es1
|
|
||||||
networks:
|
|
||||||
esnet:
|
|
||||||
aliases:
|
|
||||||
- vulnwhisp-ls1.local
|
|
||||||
vulnwhisp-vulnwhisperer:
|
|
||||||
image: hasecuritysolutions/vulnwhisperer:latest
|
|
||||||
container_name: vulnwhisp-vulnwhisperer
|
|
||||||
volumes:
|
|
||||||
- ./data/:/opt/VulnWhisperer/data
|
|
||||||
- ./configs/frameworks_example.ini:/opt/VulnWhisperer/frameworks_example.ini
|
|
||||||
network_mode: host
|
|
||||||
volumes:
|
|
||||||
esdata1:
|
|
||||||
driver: local
|
|
||||||
|
|
||||||
networks:
|
|
||||||
esnet:
|
|
@ -1,220 +0,0 @@
|
|||||||
# Author: Austin Taylor and Justin Henderson
|
|
||||||
# Email: email@austintaylor.io
|
|
||||||
# Last Update: 12/20/2017
|
|
||||||
# Version 0.3
|
|
||||||
# Description: Take in nessus reports from vulnWhisperer and pumps into logstash
|
|
||||||
|
|
||||||
|
|
||||||
input {
|
|
||||||
file {
|
|
||||||
path => "/opt/VulnWhisperer/data/nessus/**/*"
|
|
||||||
start_position => "beginning"
|
|
||||||
tags => "nessus"
|
|
||||||
type => "nessus"
|
|
||||||
}
|
|
||||||
file {
|
|
||||||
path => "/opt/VulnWhisperer/data/tenable/*.csv"
|
|
||||||
start_position => "beginning"
|
|
||||||
tags => "tenable"
|
|
||||||
type => "tenable"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
filter {
|
|
||||||
if "nessus" in [tags] or "tenable" in [tags] {
|
|
||||||
# Drop the header column
|
|
||||||
if [message] =~ "^Plugin ID" { drop {} }
|
|
||||||
|
|
||||||
csv {
|
|
||||||
# columns => ["plugin_id", "cve", "cvss", "risk", "asset", "protocol", "port", "plugin_name", "synopsis", "description", "solution", "see_also", "plugin_output"]
|
|
||||||
columns => ["plugin_id", "cve", "cvss", "risk", "asset", "protocol", "port", "plugin_name", "synopsis", "description", "solution", "see_also", "plugin_output", "asset_uuid", "vulnerability_state", "ip", "fqdn", "netbios", "operating_system", "mac_address", "plugin_family", "cvss_base", "cvss_temporal", "cvss_temporal_vector", "cvss_vector", "cvss3_base", "cvss3_temporal", "cvss3_temporal_vector", "cvss_vector", "system_type", "host_start", "host_end"]
|
|
||||||
separator => ","
|
|
||||||
source => "message"
|
|
||||||
}
|
|
||||||
|
|
||||||
ruby {
|
|
||||||
code => "if event.get('description')
|
|
||||||
event.set('description', event.get('description').gsub(92.chr + 'n', 10.chr).gsub(92.chr + 'r', 13.chr))
|
|
||||||
end
|
|
||||||
if event.get('synopsis')
|
|
||||||
event.set('synopsis', event.get('synopsis').gsub(92.chr + 'n', 10.chr).gsub(92.chr + 'r', 13.chr))
|
|
||||||
end
|
|
||||||
if event.get('solution')
|
|
||||||
event.set('solution', event.get('solution').gsub(92.chr + 'n', 10.chr).gsub(92.chr + 'r', 13.chr))
|
|
||||||
end
|
|
||||||
if event.get('see_also')
|
|
||||||
event.set('see_also', event.get('see_also').gsub(92.chr + 'n', 10.chr).gsub(92.chr + 'r', 13.chr))
|
|
||||||
end
|
|
||||||
if event.get('plugin_output')
|
|
||||||
event.set('plugin_output', event.get('plugin_output').gsub(92.chr + 'n', 10.chr).gsub(92.chr + 'r', 13.chr))
|
|
||||||
end"
|
|
||||||
}
|
|
||||||
|
|
||||||
#If using filebeats as your source, you will need to replace the "path" field to "source"
|
|
||||||
grok {
|
|
||||||
match => { "path" => "(?<scan_name>[a-zA-Z0-9_.\-]+)_%{INT:scan_id}_%{INT:history_id}_%{INT:last_updated}.csv$" }
|
|
||||||
tag_on_failure => []
|
|
||||||
}
|
|
||||||
|
|
||||||
date {
|
|
||||||
match => [ "last_updated", "UNIX" ]
|
|
||||||
target => "@timestamp"
|
|
||||||
remove_field => ["last_updated"]
|
|
||||||
}
|
|
||||||
|
|
||||||
if [risk] == "None" {
|
|
||||||
mutate { add_field => { "risk_number" => 0 }}
|
|
||||||
}
|
|
||||||
if [risk] == "Low" {
|
|
||||||
mutate { add_field => { "risk_number" => 1 }}
|
|
||||||
}
|
|
||||||
if [risk] == "Medium" {
|
|
||||||
mutate { add_field => { "risk_number" => 2 }}
|
|
||||||
}
|
|
||||||
if [risk] == "High" {
|
|
||||||
mutate { add_field => { "risk_number" => 3 }}
|
|
||||||
}
|
|
||||||
if [risk] == "Critical" {
|
|
||||||
mutate { add_field => { "risk_number" => 4 }}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ![cve] or [cve] == "nan" {
|
|
||||||
mutate { remove_field => [ "cve" ] }
|
|
||||||
}
|
|
||||||
if ![cvss] or [cvss] == "nan" {
|
|
||||||
mutate { remove_field => [ "cvss" ] }
|
|
||||||
}
|
|
||||||
if ![cvss_base] or [cvss_base] == "nan" {
|
|
||||||
mutate { remove_field => [ "cvss_base" ] }
|
|
||||||
}
|
|
||||||
if ![cvss_temporal] or [cvss_temporal] == "nan" {
|
|
||||||
mutate { remove_field => [ "cvss_temporal" ] }
|
|
||||||
}
|
|
||||||
if ![cvss_temporal_vector] or [cvss_temporal_vector] == "nan" {
|
|
||||||
mutate { remove_field => [ "cvss_temporal_vector" ] }
|
|
||||||
}
|
|
||||||
if ![cvss_vector] or [cvss_vector] == "nan" {
|
|
||||||
mutate { remove_field => [ "cvss_vector" ] }
|
|
||||||
}
|
|
||||||
if ![cvss3_base] or [cvss3_base] == "nan" {
|
|
||||||
mutate { remove_field => [ "cvss3_base" ] }
|
|
||||||
}
|
|
||||||
if ![cvss3_temporal] or [cvss3_temporal] == "nan" {
|
|
||||||
mutate { remove_field => [ "cvss3_temporal" ] }
|
|
||||||
}
|
|
||||||
if ![cvss3_temporal_vector] or [cvss3_temporal_vector] == "nan" {
|
|
||||||
mutate { remove_field => [ "cvss3_temporal_vector" ] }
|
|
||||||
}
|
|
||||||
if ![description] or [description] == "nan" {
|
|
||||||
mutate { remove_field => [ "description" ] }
|
|
||||||
}
|
|
||||||
if ![mac_address] or [mac_address] == "nan" {
|
|
||||||
mutate { remove_field => [ "mac_address" ] }
|
|
||||||
}
|
|
||||||
if ![netbios] or [netbios] == "nan" {
|
|
||||||
mutate { remove_field => [ "netbios" ] }
|
|
||||||
}
|
|
||||||
if ![operating_system] or [operating_system] == "nan" {
|
|
||||||
mutate { remove_field => [ "operating_system" ] }
|
|
||||||
}
|
|
||||||
if ![plugin_output] or [plugin_output] == "nan" {
|
|
||||||
mutate { remove_field => [ "plugin_output" ] }
|
|
||||||
}
|
|
||||||
if ![see_also] or [see_also] == "nan" {
|
|
||||||
mutate { remove_field => [ "see_also" ] }
|
|
||||||
}
|
|
||||||
if ![synopsis] or [synopsis] == "nan" {
|
|
||||||
mutate { remove_field => [ "synopsis" ] }
|
|
||||||
}
|
|
||||||
if ![system_type] or [system_type] == "nan" {
|
|
||||||
mutate { remove_field => [ "system_type" ] }
|
|
||||||
}
|
|
||||||
|
|
||||||
mutate {
|
|
||||||
remove_field => [ "message" ]
|
|
||||||
add_field => { "risk_score" => "%{cvss}" }
|
|
||||||
}
|
|
||||||
mutate {
|
|
||||||
convert => { "risk_score" => "float" }
|
|
||||||
}
|
|
||||||
if [risk_score] == 0 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "info" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [risk_score] > 0 and [risk_score] < 3 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "low" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [risk_score] >= 3 and [risk_score] < 6 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "medium" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [risk_score] >=6 and [risk_score] < 9 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "high" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [risk_score] >= 9 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "critical" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Compensating controls - adjust risk_score
|
|
||||||
# Adobe and Java are not allowed to run in browser unless whitelisted
|
|
||||||
# Therefore, lower score by dividing by 3 (score is subjective to risk)
|
|
||||||
|
|
||||||
#Modify and uncomment when ready to use
|
|
||||||
#if [risk_score] != 0 {
|
|
||||||
# if [plugin_name] =~ "Adobe" and [risk_score] > 6 or [plugin_name] =~ "Java" and [risk_score] > 6 {
|
|
||||||
# ruby {
|
|
||||||
# code => "event.set('risk_score', event.get('risk_score') / 3)"
|
|
||||||
# }
|
|
||||||
# mutate {
|
|
||||||
# add_field => { "compensating_control" => "Adobe and Flash removed from browsers unless whitelisted site." }
|
|
||||||
# }
|
|
||||||
# }
|
|
||||||
#}
|
|
||||||
|
|
||||||
# Add tags for reporting based on assets or criticality
|
|
||||||
|
|
||||||
if [asset] == "dc01" or [asset] == "dc02" or [asset] == "pki01" or [asset] == "192.168.0.54" or [asset] =~ "^192\.168\.0\." or [asset] =~ "^42.42.42." {
|
|
||||||
mutate {
|
|
||||||
add_tag => [ "critical_asset" ]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#if [asset] =~ "^192\.168\.[45][0-9][0-9]\.1$" or [asset] =~ "^192.168\.[50]\.[0-9]{1,2}\.1$"{
|
|
||||||
# mutate {
|
|
||||||
# add_tag => [ "has_hipaa_data" ]
|
|
||||||
# }
|
|
||||||
#}
|
|
||||||
#if [asset] =~ "^192\.168\.[45][0-9][0-9]\." {
|
|
||||||
# mutate {
|
|
||||||
# add_tag => [ "hipaa_asset" ]
|
|
||||||
# }
|
|
||||||
#}
|
|
||||||
if [asset] =~ "^hr" {
|
|
||||||
mutate {
|
|
||||||
add_tag => [ "pci_asset" ]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#if [asset] =~ "^10\.0\.50\." {
|
|
||||||
# mutate {
|
|
||||||
# add_tag => [ "web_servers" ]
|
|
||||||
# }
|
|
||||||
#}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
output {
|
|
||||||
if "nessus" in [tags] or "tenable" in [tags] or [type] in [ "nessus", "tenable" ] {
|
|
||||||
# stdout { codec => rubydebug }
|
|
||||||
elasticsearch {
|
|
||||||
hosts => [ "vulnwhisp-es1.local:9200" ]
|
|
||||||
index => "logstash-vulnwhisperer-%{+YYYY.MM}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,153 +0,0 @@
|
|||||||
# Author: Austin Taylor and Justin Henderson
|
|
||||||
# Email: austin@hasecuritysolutions.com
|
|
||||||
# Last Update: 12/30/2017
|
|
||||||
# Version 0.3
|
|
||||||
# Description: Take in qualys web scan reports from vulnWhisperer and pumps into logstash
|
|
||||||
|
|
||||||
input {
|
|
||||||
file {
|
|
||||||
path => "/opt/VulnWhisperer/data/qualys/*.json"
|
|
||||||
type => json
|
|
||||||
codec => json
|
|
||||||
start_position => "beginning"
|
|
||||||
tags => [ "qualys" ]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
filter {
|
|
||||||
if "qualys" in [tags] {
|
|
||||||
grok {
|
|
||||||
match => { "path" => [ "(?<tags>qualys_vuln)_scan_%{DATA}_%{INT:last_updated}.json$", "(?<tags>qualys_web)_%{INT:app_id}_%{INT:last_updated}.json$" ] }
|
|
||||||
tag_on_failure => []
|
|
||||||
}
|
|
||||||
|
|
||||||
mutate {
|
|
||||||
replace => [ "message", "%{message}" ]
|
|
||||||
#gsub => [
|
|
||||||
# "message", "\|\|\|", " ",
|
|
||||||
# "message", "\t\t", " ",
|
|
||||||
# "message", " ", " ",
|
|
||||||
# "message", " ", " ",
|
|
||||||
# "message", " ", " ",
|
|
||||||
# "message", "nan", " ",
|
|
||||||
# "message",'\n',''
|
|
||||||
#]
|
|
||||||
}
|
|
||||||
|
|
||||||
if "qualys_web" in [tags] {
|
|
||||||
mutate {
|
|
||||||
add_field => { "asset" => "%{web_application_name}" }
|
|
||||||
add_field => { "risk_score" => "%{cvss}" }
|
|
||||||
}
|
|
||||||
} else if "qualys_vuln" in [tags] {
|
|
||||||
mutate {
|
|
||||||
add_field => { "asset" => "%{ip}" }
|
|
||||||
add_field => { "risk_score" => "%{cvss}" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if [risk] == "1" {
|
|
||||||
mutate { add_field => { "risk_number" => 0 }}
|
|
||||||
mutate { replace => { "risk" => "info" }}
|
|
||||||
}
|
|
||||||
if [risk] == "2" {
|
|
||||||
mutate { add_field => { "risk_number" => 1 }}
|
|
||||||
mutate { replace => { "risk" => "low" }}
|
|
||||||
}
|
|
||||||
if [risk] == "3" {
|
|
||||||
mutate { add_field => { "risk_number" => 2 }}
|
|
||||||
mutate { replace => { "risk" => "medium" }}
|
|
||||||
}
|
|
||||||
if [risk] == "4" {
|
|
||||||
mutate { add_field => { "risk_number" => 3 }}
|
|
||||||
mutate { replace => { "risk" => "high" }}
|
|
||||||
}
|
|
||||||
if [risk] == "5" {
|
|
||||||
mutate { add_field => { "risk_number" => 4 }}
|
|
||||||
mutate { replace => { "risk" => "critical" }}
|
|
||||||
}
|
|
||||||
|
|
||||||
mutate {
|
|
||||||
remove_field => "message"
|
|
||||||
}
|
|
||||||
|
|
||||||
if [first_time_detected] {
|
|
||||||
date {
|
|
||||||
match => [ "first_time_detected", "dd MMM yyyy HH:mma 'GMT'ZZ", "dd MMM yyyy HH:mma 'GMT'" ]
|
|
||||||
target => "first_time_detected"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [first_time_tested] {
|
|
||||||
date {
|
|
||||||
match => [ "first_time_tested", "dd MMM yyyy HH:mma 'GMT'ZZ", "dd MMM yyyy HH:mma 'GMT'" ]
|
|
||||||
target => "first_time_tested"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [last_time_detected] {
|
|
||||||
date {
|
|
||||||
match => [ "last_time_detected", "dd MMM yyyy HH:mma 'GMT'ZZ", "dd MMM yyyy HH:mma 'GMT'" ]
|
|
||||||
target => "last_time_detected"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [last_time_tested] {
|
|
||||||
date {
|
|
||||||
match => [ "last_time_tested", "dd MMM yyyy HH:mma 'GMT'ZZ", "dd MMM yyyy HH:mma 'GMT'" ]
|
|
||||||
target => "last_time_tested"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
date {
|
|
||||||
match => [ "last_updated", "UNIX" ]
|
|
||||||
target => "@timestamp"
|
|
||||||
remove_field => "last_updated"
|
|
||||||
}
|
|
||||||
mutate {
|
|
||||||
convert => { "plugin_id" => "integer"}
|
|
||||||
convert => { "id" => "integer"}
|
|
||||||
convert => { "risk_number" => "integer"}
|
|
||||||
convert => { "risk_score" => "float"}
|
|
||||||
convert => { "total_times_detected" => "integer"}
|
|
||||||
convert => { "cvss_temporal" => "float"}
|
|
||||||
convert => { "cvss" => "float"}
|
|
||||||
}
|
|
||||||
if [risk_score] == 0 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "info" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [risk_score] > 0 and [risk_score] < 3 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "low" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [risk_score] >= 3 and [risk_score] < 6 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "medium" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [risk_score] >=6 and [risk_score] < 9 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "high" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [risk_score] >= 9 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "critical" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if [asset] =~ "\.yourdomain\.(com|net)$" {
|
|
||||||
mutate {
|
|
||||||
add_tag => [ "critical_asset" ]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
output {
|
|
||||||
if "qualys" in [tags] {
|
|
||||||
stdout { codec => rubydebug }
|
|
||||||
elasticsearch {
|
|
||||||
hosts => [ "vulnwhisp-es1.local:9200" ]
|
|
||||||
index => "logstash-vulnwhisperer-%{+YYYY.MM}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,146 +0,0 @@
|
|||||||
# Author: Austin Taylor and Justin Henderson
|
|
||||||
# Email: austin@hasecuritysolutions.com
|
|
||||||
# Last Update: 03/04/2018
|
|
||||||
# Version 0.3
|
|
||||||
# Description: Take in qualys web scan reports from vulnWhisperer and pumps into logstash
|
|
||||||
|
|
||||||
input {
|
|
||||||
file {
|
|
||||||
path => "/opt/VulnWhisperer/data/openvas/*.json"
|
|
||||||
type => json
|
|
||||||
codec => json
|
|
||||||
start_position => "beginning"
|
|
||||||
tags => [ "openvas_scan", "openvas" ]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
filter {
|
|
||||||
if "openvas_scan" in [tags] {
|
|
||||||
mutate {
|
|
||||||
replace => [ "message", "%{message}" ]
|
|
||||||
gsub => [
|
|
||||||
"message", "\|\|\|", " ",
|
|
||||||
"message", "\t\t", " ",
|
|
||||||
"message", " ", " ",
|
|
||||||
"message", " ", " ",
|
|
||||||
"message", " ", " ",
|
|
||||||
"message", "nan", " ",
|
|
||||||
"message",'\n',''
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
grok {
|
|
||||||
match => { "path" => "openvas_scan_%{DATA:scan_id}_%{INT:last_updated}.json$" }
|
|
||||||
tag_on_failure => []
|
|
||||||
}
|
|
||||||
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score" => "%{cvss}" }
|
|
||||||
}
|
|
||||||
|
|
||||||
if [risk] == "1" {
|
|
||||||
mutate { add_field => { "risk_number" => 0 }}
|
|
||||||
mutate { replace => { "risk" => "info" }}
|
|
||||||
}
|
|
||||||
if [risk] == "2" {
|
|
||||||
mutate { add_field => { "risk_number" => 1 }}
|
|
||||||
mutate { replace => { "risk" => "low" }}
|
|
||||||
}
|
|
||||||
if [risk] == "3" {
|
|
||||||
mutate { add_field => { "risk_number" => 2 }}
|
|
||||||
mutate { replace => { "risk" => "medium" }}
|
|
||||||
}
|
|
||||||
if [risk] == "4" {
|
|
||||||
mutate { add_field => { "risk_number" => 3 }}
|
|
||||||
mutate { replace => { "risk" => "high" }}
|
|
||||||
}
|
|
||||||
if [risk] == "5" {
|
|
||||||
mutate { add_field => { "risk_number" => 4 }}
|
|
||||||
mutate { replace => { "risk" => "critical" }}
|
|
||||||
}
|
|
||||||
|
|
||||||
mutate {
|
|
||||||
remove_field => "message"
|
|
||||||
}
|
|
||||||
|
|
||||||
if [first_time_detected] {
|
|
||||||
date {
|
|
||||||
match => [ "first_time_detected", "dd MMM yyyy HH:mma 'GMT'ZZ", "dd MMM yyyy HH:mma 'GMT'" ]
|
|
||||||
target => "first_time_detected"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [first_time_tested] {
|
|
||||||
date {
|
|
||||||
match => [ "first_time_tested", "dd MMM yyyy HH:mma 'GMT'ZZ", "dd MMM yyyy HH:mma 'GMT'" ]
|
|
||||||
target => "first_time_tested"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [last_time_detected] {
|
|
||||||
date {
|
|
||||||
match => [ "last_time_detected", "dd MMM yyyy HH:mma 'GMT'ZZ", "dd MMM yyyy HH:mma 'GMT'" ]
|
|
||||||
target => "last_time_detected"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [last_time_tested] {
|
|
||||||
date {
|
|
||||||
match => [ "last_time_tested", "dd MMM yyyy HH:mma 'GMT'ZZ", "dd MMM yyyy HH:mma 'GMT'" ]
|
|
||||||
target => "last_time_tested"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
date {
|
|
||||||
match => [ "last_updated", "UNIX" ]
|
|
||||||
target => "@timestamp"
|
|
||||||
remove_field => "last_updated"
|
|
||||||
}
|
|
||||||
mutate {
|
|
||||||
convert => { "plugin_id" => "integer"}
|
|
||||||
convert => { "id" => "integer"}
|
|
||||||
convert => { "risk_number" => "integer"}
|
|
||||||
convert => { "risk_score" => "float"}
|
|
||||||
convert => { "total_times_detected" => "integer"}
|
|
||||||
convert => { "cvss_temporal" => "float"}
|
|
||||||
convert => { "cvss" => "float"}
|
|
||||||
}
|
|
||||||
if [risk_score] == 0 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "info" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [risk_score] > 0 and [risk_score] < 3 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "low" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [risk_score] >= 3 and [risk_score] < 6 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "medium" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [risk_score] >=6 and [risk_score] < 9 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "high" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [risk_score] >= 9 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "critical" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
# Add your critical assets by subnet or by hostname. Comment this field out if you don't want to tag any, but the asset panel will break.
|
|
||||||
if [asset] =~ "^10\.0\.100\." {
|
|
||||||
mutate {
|
|
||||||
add_tag => [ "critical_asset" ]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
output {
|
|
||||||
if "openvas" in [tags] {
|
|
||||||
stdout { codec => rubydebug }
|
|
||||||
elasticsearch {
|
|
||||||
hosts => [ "vulnwhisp-es1.local:9200" ]
|
|
||||||
index => "logstash-vulnwhisperer-%{+YYYY.MM}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
# Description: Take in jira tickets from vulnWhisperer and pumps into logstash
|
|
||||||
|
|
||||||
input {
|
|
||||||
file {
|
|
||||||
path => "/opt/Vulnwhisperer/jira/*.json"
|
|
||||||
type => json
|
|
||||||
codec => json
|
|
||||||
start_position => "beginning"
|
|
||||||
tags => [ "jira" ]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
output {
|
|
||||||
if "jira" in [tags] {
|
|
||||||
stdout { codec => rubydebug }
|
|
||||||
elasticsearch {
|
|
||||||
hosts => [ "vulnwhisp-es1.local:9200" ]
|
|
||||||
index => "logstash-vulnwhisperer-%{+YYYY.MM}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
path.config: /usr/share/logstash/pipeline/
|
|
||||||
xpack.monitoring.elasticsearch.password: changeme
|
|
||||||
xpack.monitoring.elasticsearch.url: vulnwhisp-es1.local:9200
|
|
||||||
xpack.monitoring.elasticsearch.username: elastic
|
|
||||||
xpack.monitoring.enabled: false
|
|
@ -1,122 +0,0 @@
|
|||||||
{
|
|
||||||
"order": 0,
|
|
||||||
"template": "logstash-vulnwhisperer-*",
|
|
||||||
"settings": {
|
|
||||||
"index": {
|
|
||||||
"routing": {
|
|
||||||
"allocation": {
|
|
||||||
"total_shards_per_node": "2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"mapping": {
|
|
||||||
"total_fields": {
|
|
||||||
"limit": "3000"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"refresh_interval": "5s",
|
|
||||||
"number_of_shards": "1",
|
|
||||||
"number_of_replicas": "0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"mappings": {
|
|
||||||
"_default_": {
|
|
||||||
"_all": {
|
|
||||||
"enabled": false
|
|
||||||
},
|
|
||||||
"dynamic_templates": [
|
|
||||||
{
|
|
||||||
"message_field": {
|
|
||||||
"path_match": "message",
|
|
||||||
"match_mapping_type": "string",
|
|
||||||
"mapping": {
|
|
||||||
"type": "text",
|
|
||||||
"norms": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"string_fields": {
|
|
||||||
"match": "*",
|
|
||||||
"match_mapping_type": "string",
|
|
||||||
"mapping": {
|
|
||||||
"type": "text",
|
|
||||||
"norms": false,
|
|
||||||
"fields": {
|
|
||||||
"keyword": {
|
|
||||||
"type": "keyword",
|
|
||||||
"ignore_above": 256
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"plugin_id": {
|
|
||||||
"type": "float"
|
|
||||||
},
|
|
||||||
"last_updated": {
|
|
||||||
"type": "date"
|
|
||||||
},
|
|
||||||
"geoip": {
|
|
||||||
"dynamic": true,
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"ip": {
|
|
||||||
"type": "ip"
|
|
||||||
},
|
|
||||||
"latitude": {
|
|
||||||
"type": "float"
|
|
||||||
},
|
|
||||||
"location": {
|
|
||||||
"type": "geo_point"
|
|
||||||
},
|
|
||||||
"longitude": {
|
|
||||||
"type": "float"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"risk_score": {
|
|
||||||
"type": "float"
|
|
||||||
},
|
|
||||||
"source": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"synopsis": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"see_also": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"@timestamp": {
|
|
||||||
"type": "date"
|
|
||||||
},
|
|
||||||
"cve": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"solution": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"port": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"host": {
|
|
||||||
"type": "text"
|
|
||||||
},
|
|
||||||
"@version": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"risk": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"assign_ip": {
|
|
||||||
"type": "ip"
|
|
||||||
},
|
|
||||||
"cvss": {
|
|
||||||
"type": "float"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"aliases": {}
|
|
||||||
}
|
|
@ -1,116 +0,0 @@
|
|||||||
###################### Filebeat Configuration Example #########################
|
|
||||||
|
|
||||||
# This file is an example configuration file highlighting only the most common
|
|
||||||
# options. The filebeat.full.yml file from the same directory contains all the
|
|
||||||
# supported options with more comments. You can use it as a reference.
|
|
||||||
#
|
|
||||||
# You can find the full configuration reference here:
|
|
||||||
# https://www.elastic.co/guide/en/beats/filebeat/index.html
|
|
||||||
|
|
||||||
#=========================== Filebeat prospectors =============================
|
|
||||||
|
|
||||||
filebeat.prospectors:
|
|
||||||
|
|
||||||
# Each - is a prospector. Most options can be set at the prospector level, so
|
|
||||||
# you can use different prospectors for various configurations.
|
|
||||||
# Below are the prospector specific configurations.
|
|
||||||
|
|
||||||
- input_type: log
|
|
||||||
# Paths that should be crawled and fetched. Glob based paths.
|
|
||||||
paths:
|
|
||||||
# Linux Example
|
|
||||||
#- /var/log/*.log
|
|
||||||
|
|
||||||
#Windows Example
|
|
||||||
- c:\nessus\My Scans\*
|
|
||||||
|
|
||||||
# Exclude lines. A list of regular expressions to match. It drops the lines that are
|
|
||||||
# matching any regular expression from the list.
|
|
||||||
#exclude_lines: ["^DBG"]
|
|
||||||
|
|
||||||
# Include lines. A list of regular expressions to match. It exports the lines that are
|
|
||||||
# matching any regular expression from the list.
|
|
||||||
#include_lines: ["^ERR", "^WARN"]
|
|
||||||
|
|
||||||
# Exclude files. A list of regular expressions to match. Filebeat drops the files that
|
|
||||||
# are matching any regular expression from the list. By default, no files are dropped.
|
|
||||||
#exclude_files: [".gz$"]
|
|
||||||
|
|
||||||
# Optional additional fields. These field can be freely picked
|
|
||||||
# to add additional information to the crawled log files for filtering
|
|
||||||
#fields:
|
|
||||||
# level: debug
|
|
||||||
# review: 1
|
|
||||||
|
|
||||||
### Multiline options
|
|
||||||
|
|
||||||
# Mutiline can be used for log messages spanning multiple lines. This is common
|
|
||||||
# for Java Stack Traces or C-Line Continuation
|
|
||||||
|
|
||||||
# The regexp Pattern that has to be matched. The example pattern matches all lines starting with [
|
|
||||||
#multiline.pattern: ^\[
|
|
||||||
|
|
||||||
# Defines if the pattern set under pattern should be negated or not. Default is false.
|
|
||||||
#multiline.negate: false
|
|
||||||
|
|
||||||
# Match can be set to "after" or "before". It is used to define if lines should be append to a pattern
|
|
||||||
# that was (not) matched before or after or as long as a pattern is not matched based on negate.
|
|
||||||
# Note: After is the equivalent to previous and before is the equivalent to to next in Logstash
|
|
||||||
#multiline.match: after
|
|
||||||
|
|
||||||
|
|
||||||
#================================ General =====================================
|
|
||||||
|
|
||||||
# The name of the shipper that publishes the network data. It can be used to group
|
|
||||||
# all the transactions sent by a single shipper in the web interface.
|
|
||||||
#name:
|
|
||||||
|
|
||||||
# The tags of the shipper are included in their own field with each
|
|
||||||
# transaction published.
|
|
||||||
#tags: ["service-X", "web-tier"]
|
|
||||||
|
|
||||||
# Optional fields that you can specify to add additional information to the
|
|
||||||
# output.
|
|
||||||
#fields:
|
|
||||||
# env: staging
|
|
||||||
|
|
||||||
#================================ Outputs =====================================
|
|
||||||
|
|
||||||
# Configure what outputs to use when sending the data collected by the beat.
|
|
||||||
# Multiple outputs may be used.
|
|
||||||
|
|
||||||
#-------------------------- Elasticsearch output ------------------------------
|
|
||||||
#output.elasticsearch:
|
|
||||||
# Array of hosts to connect to.
|
|
||||||
# hosts: ["logstash01:9200"]
|
|
||||||
|
|
||||||
# Optional protocol and basic auth credentials.
|
|
||||||
#protocol: "https"
|
|
||||||
#username: "elastic"
|
|
||||||
#password: "changeme"
|
|
||||||
|
|
||||||
#----------------------------- Logstash output --------------------------------
|
|
||||||
output.logstash:
|
|
||||||
# The Logstash hosts
|
|
||||||
hosts: ["logstashserver1:5044", "logstashserver2:5044", "logstashserver3:5044"]
|
|
||||||
|
|
||||||
# Optional SSL. By default is off.
|
|
||||||
# List of root certificates for HTTPS server verifications
|
|
||||||
#ssl.certificate_authorities: ["/etc/pki/root/ca.pem"]
|
|
||||||
|
|
||||||
# Certificate for SSL client authentication
|
|
||||||
#ssl.certificate: "/etc/pki/client/cert.pem"
|
|
||||||
|
|
||||||
# Client Certificate Key
|
|
||||||
#ssl.key: "/etc/pki/client/cert.key"
|
|
||||||
|
|
||||||
#================================ Logging =====================================
|
|
||||||
|
|
||||||
# Sets log level. The default log level is info.
|
|
||||||
# Available log levels are: critical, error, warning, info, debug
|
|
||||||
#logging.level: debug
|
|
||||||
|
|
||||||
# At debug level, you can selectively enable logging only for some components.
|
|
||||||
# To enable all selectors use ["*"]. Examples of other selectors are "beat",
|
|
||||||
# "publish", "service".
|
|
||||||
#logging.selectors: ["*"]
|
|
@ -1,450 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"_id": "80158c90-57c1-11e7-b484-a970fc9d150a",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer - HIPAA TL",
|
|
||||||
"visState": "{\"type\":\"timelion\",\"title\":\"VulnWhisperer - HIPAA TL\",\"params\":{\"expression\":\".es(index=logstash-vulnwhisperer-*,q='risk_score:>9 AND tags:pci_asset').label(\\\"PCI Assets\\\"),.es(index=logstash-vulnwhisperer-*,q='risk_score:>9 AND tags:has_hipaa_data').label(\\\"Has HIPAA Data\\\"),.es(index=logstash-vulnwhisperer-*,q='risk_score:>9 AND tags:hipaa_asset').label(\\\"HIPAA Assets\\\")\",\"interval\":\"auto\"}}",
|
|
||||||
"uiStateJSON": "{}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "479deab0-8a39-11e7-a58a-9bfcb3761a3d",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer - TL - TaggedAssetsPluginNames",
|
|
||||||
"visState": "{\"title\":\"VulnWhisperer - TL - TaggedAssetsPluginNames\",\"type\":\"timelion\",\"params\":{\"expression\":\".es(index='logstash-vulnwhisperer-*', q='tags:critical_asset OR tags:hipaa_asset OR tags:pci_asset', split=\\\"plugin_name.keyword:10\\\").bars(width=4).label(regex=\\\".*:(.+)>.*\\\",label=\\\"$1\\\")\",\"interval\":\"auto\"},\"aggs\":[],\"listeners\":{}}",
|
|
||||||
"uiStateJSON": "{}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "84f5c370-8a38-11e7-a58a-9bfcb3761a3d",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer - TL - CriticalAssetsPluginNames",
|
|
||||||
"visState": "{\"title\":\"VulnWhisperer - TL - CriticalAssetsPluginNames\",\"type\":\"timelion\",\"params\":{\"expression\":\".es(index='logstash-vulnwhisperer-*', q='tags:critical_asset', split=\\\"plugin_name.keyword:10\\\").bars(width=4).label(regex=\\\".*:(.+)>.*\\\",label=\\\"$1\\\")\",\"interval\":\"auto\"},\"aggs\":[],\"listeners\":{}}",
|
|
||||||
"uiStateJSON": "{}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "307cdae0-8a38-11e7-a58a-9bfcb3761a3d",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer - TL - PluginNames",
|
|
||||||
"visState": "{\"title\":\"VulnWhisperer - TL - PluginNames\",\"type\":\"timelion\",\"params\":{\"expression\":\".es(index='logstash-vulnwhisperer-*', split=\\\"plugin_name.keyword:25\\\").bars(width=4).label(regex=\\\".*:(.+)>.*\\\",label=\\\"$1\\\")\",\"interval\":\"auto\"},\"aggs\":[],\"listeners\":{}}",
|
|
||||||
"uiStateJSON": "{}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "5093c620-44e9-11e7-8014-ede06a7e69f8",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer - Mitigation Readme",
|
|
||||||
"visState": "{\"title\":\"VulnWhisperer - Mitigation Readme\",\"type\":\"markdown\",\"params\":{\"markdown\":\"** Legend **\\n\\n* [Common Vulnerability Scoring System (CVSS)](https://nvd.nist.gov/vuln-metrics/cvss) is the NIST vulnerability scoring system\\n* Risk Number is residual risk score calculated from CVSS, which is adjusted to be specific to the netowrk owner, which accounts for services not in use such as Java and Flash\\n* Vulnerabilities by Tag are systems tagged with HIPAA and PCI identification.\\n\\n\\n** Workflow **\\n* Select 10.0 under Risk Number to identify Critical Vulnerabilities. \\n* For more information about a CVE, scroll down and click the CVE link.\\n* To filter by tags, use one of the following filters:\\n** tags:has_hipaa_data, tags:pci_asset, tags:hipaa_asset, tags:critical_asset**\"},\"aggs\":[],\"listeners\":{}}",
|
|
||||||
"uiStateJSON": "{}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "7e7fbc90-3df2-11e7-a44e-c79ca8efb780",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer-PluginID",
|
|
||||||
"visState": "{\"title\":\"VulnWhisperer-PluginID\",\"type\":\"table\",\"params\":{\"perPage\":10,\"showMeticsAtAllLevels\":false,\"showPartialRows\":false,\"showTotal\":false,\"sort\":{\"columnIndex\":null,\"direction\":null},\"totalFunc\":\"sum\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"bucket\",\"params\":{\"field\":\"plugin_id\",\"size\":50,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}",
|
|
||||||
"uiStateJSON": "{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"index\":\"logstash-vulnwhisperer-*\",\"query\":{\"query_string\":{\"analyze_wildcard\":true,\"query\":\"*\"}},\"filter\":[]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "5a3c0340-3eb3-11e7-a192-93f36fbd9d05",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer-CVSSHeatmap",
|
|
||||||
"visState": "{\"title\":\"VulnWhisperer-CVSSHeatmap\",\"type\":\"heatmap\",\"params\":{\"addTooltip\":true,\"addLegend\":true,\"enableHover\":false,\"legendPosition\":\"right\",\"times\":[],\"colorsNumber\":4,\"colorSchema\":\"Yellow to Red\",\"setColorRange\":false,\"colorsRange\":[],\"invertColors\":false,\"percentageMode\":false,\"valueAxes\":[{\"show\":false,\"id\":\"ValueAxis-1\",\"type\":\"value\",\"scale\":{\"type\":\"linear\",\"defaultYExtents\":false},\"labels\":{\"show\":false,\"rotate\":0,\"color\":\"#555\"}}]},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"segment\",\"params\":{\"field\":\"host.keyword\",\"size\":50,\"order\":\"desc\",\"orderBy\":\"1\"}},{\"id\":\"3\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"group\",\"params\":{\"field\":\"cvss.keyword\",\"size\":50,\"order\":\"desc\",\"orderBy\":\"_term\"}}],\"listeners\":{}}",
|
|
||||||
"uiStateJSON": "{\"vis\":{\"defaultColors\":{\"0 - 3500\":\"rgb(255,255,204)\",\"3500 - 7000\":\"rgb(254,217,118)\",\"7000 - 10500\":\"rgb(253,141,60)\",\"10500 - 14000\":\"rgb(227,27,28)\"}}}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"index\":\"logstash-vulnwhisperer-*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "1de9e550-3df1-11e7-a44e-c79ca8efb780",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer-Description",
|
|
||||||
"visState": "{\"title\":\"VulnWhisperer-Description\",\"type\":\"table\",\"params\":{\"perPage\":10,\"showPartialRows\":false,\"showMeticsAtAllLevels\":false,\"sort\":{\"columnIndex\":null,\"direction\":null},\"showTotal\":false,\"totalFunc\":\"sum\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"bucket\",\"params\":{\"field\":\"description.keyword\",\"size\":50,\"order\":\"desc\",\"orderBy\":\"1\",\"customLabel\":\"Description\"}}],\"listeners\":{}}",
|
|
||||||
"uiStateJSON": "{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"index\":\"logstash-vulnwhisperer-*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "13c7d4e0-3df3-11e7-a44e-c79ca8efb780",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer-Solution",
|
|
||||||
"visState": "{\"title\":\"VulnWhisperer-Solution\",\"type\":\"table\",\"params\":{\"perPage\":10,\"showMeticsAtAllLevels\":false,\"showPartialRows\":false,\"showTotal\":false,\"sort\":{\"columnIndex\":null,\"direction\":null},\"totalFunc\":\"sum\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"bucket\",\"params\":{\"field\":\"solution.keyword\",\"size\":50,\"order\":\"desc\",\"orderBy\":\"1\",\"customLabel\":\"Solution\"}}],\"listeners\":{}}",
|
|
||||||
"uiStateJSON": "{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"index\":\"logstash-vulnwhisperer-*\",\"query\":{\"query_string\":{\"analyze_wildcard\":true,\"query\":\"*\"}},\"filter\":[]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "297df800-3f7e-11e7-bd24-6903e3283192",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer - Plugin Name",
|
|
||||||
"visState": "{\"title\":\"VulnWhisperer - Plugin Name\",\"type\":\"table\",\"params\":{\"perPage\":10,\"showPartialRows\":false,\"showMeticsAtAllLevels\":false,\"sort\":{\"columnIndex\":null,\"direction\":null},\"showTotal\":false,\"totalFunc\":\"sum\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"bucket\",\"params\":{\"field\":\"plugin_name.keyword\",\"size\":10,\"order\":\"desc\",\"orderBy\":\"1\",\"customLabel\":\"Plugin Name\"}}],\"listeners\":{}}",
|
|
||||||
"uiStateJSON": "{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"index\":\"logstash-vulnwhisperer-*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "de1a5f40-3f85-11e7-97f9-3777d794626d",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer - ScanName",
|
|
||||||
"visState": "{\"title\":\"VulnWhisperer - ScanName\",\"type\":\"table\",\"params\":{\"perPage\":10,\"showPartialRows\":false,\"showMeticsAtAllLevels\":false,\"sort\":{\"columnIndex\":null,\"direction\":null},\"showTotal\":false,\"totalFunc\":\"sum\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"bucket\",\"params\":{\"field\":\"scan_name.keyword\",\"size\":20,\"order\":\"desc\",\"orderBy\":\"1\",\"customLabel\":\"Scan Name\"}}],\"listeners\":{}}",
|
|
||||||
"uiStateJSON": "{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"index\":\"logstash-vulnwhisperer-*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "ecbb99c0-3f84-11e7-97f9-3777d794626d",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer - Total",
|
|
||||||
"visState": "{\"title\":\"VulnWhisperer - Total\",\"type\":\"metric\",\"params\":{\"handleNoResults\":true,\"fontSize\":60},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{\"customLabel\":\"Total\"}}],\"listeners\":{}}",
|
|
||||||
"uiStateJSON": "{}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"index\":\"logstash-vulnwhisperer-*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "471a3580-3f6b-11e7-88e7-df1abe6547fb",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer - Vulnerabilities by Tag",
|
|
||||||
"visState": "{\"title\":\"VulnWhisperer - Vulnerabilities by Tag\",\"type\":\"table\",\"params\":{\"perPage\":3,\"showMeticsAtAllLevels\":false,\"showPartialRows\":false,\"showTotal\":false,\"sort\":{\"columnIndex\":null,\"direction\":null},\"totalFunc\":\"sum\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"3\",\"enabled\":true,\"type\":\"filters\",\"schema\":\"bucket\",\"params\":{\"filters\":[{\"input\":{\"query\":{\"query_string\":{\"query\":\"tags:has_hipaa_data\",\"analyze_wildcard\":true}}},\"label\":\"Systems with HIPAA data\"},{\"input\":{\"query\":{\"query_string\":{\"query\":\"tags:pci_asset\",\"analyze_wildcard\":true}}},\"label\":\"PCI Systems\"},{\"input\":{\"query\":{\"query_string\":{\"query\":\"tags:hipaa_asset\",\"analyze_wildcard\":true}}},\"label\":\"HIPAA Systems\"}]}}],\"listeners\":{}}",
|
|
||||||
"uiStateJSON": "{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"index\":\"logstash-vulnwhisperer-*\",\"query\":{\"query_string\":{\"analyze_wildcard\":true,\"query\":\"*\"}},\"filter\":[]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "35b6d320-3f7f-11e7-bd24-6903e3283192",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer - Residual Risk",
|
|
||||||
"visState": "{\"title\":\"VulnWhisperer - Residual Risk\",\"type\":\"table\",\"params\":{\"perPage\":15,\"showPartialRows\":false,\"showMeticsAtAllLevels\":false,\"sort\":{\"columnIndex\":0,\"direction\":\"desc\"},\"showTotal\":false,\"totalFunc\":\"sum\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"bucket\",\"params\":{\"field\":\"risk_score\",\"size\":50,\"order\":\"desc\",\"orderBy\":\"1\",\"customLabel\":\"Risk Number\"}}],\"listeners\":{}}",
|
|
||||||
"uiStateJSON": "{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":0,\"direction\":\"desc\"}}}}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"index\":\"logstash-vulnwhisperer-*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "a9225930-3df2-11e7-a44e-c79ca8efb780",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer-Risk",
|
|
||||||
"visState": "{\"title\":\"VulnWhisperer-Risk\",\"type\":\"table\",\"params\":{\"perPage\":4,\"showMeticsAtAllLevels\":false,\"showPartialRows\":false,\"showTotal\":false,\"sort\":{\"columnIndex\":null,\"direction\":null},\"totalFunc\":\"sum\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"bucket\",\"params\":{\"field\":\"risk\",\"size\":10,\"order\":\"desc\",\"orderBy\":\"1\",\"customLabel\":\"Risk Severity\"}}],\"listeners\":{}}",
|
|
||||||
"uiStateJSON": "{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"index\":\"logstash-vulnwhisperer-*\",\"query\":{\"query_string\":{\"analyze_wildcard\":true,\"query\":\"*\"}},\"filter\":[]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "2f979030-44b9-11e7-a818-f5f80dfc3590",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer - ScanBarChart",
|
|
||||||
"visState": "{\"aggs\":[{\"enabled\":true,\"id\":\"1\",\"params\":{},\"schema\":\"metric\",\"type\":\"count\"},{\"enabled\":true,\"id\":\"2\",\"params\":{\"customLabel\":\"Scan Name\",\"field\":\"plugin_name.keyword\",\"order\":\"desc\",\"orderBy\":\"1\",\"size\":10},\"schema\":\"segment\",\"type\":\"terms\"}],\"listeners\":{},\"params\":{\"addLegend\":true,\"addTimeMarker\":false,\"addTooltip\":true,\"defaultYExtents\":false,\"legendPosition\":\"right\",\"mode\":\"stacked\",\"scale\":\"linear\",\"setYExtents\":false,\"times\":[]},\"title\":\"VulnWhisperer - ScanBarChart\",\"type\":\"histogram\"}",
|
|
||||||
"uiStateJSON": "{}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"index\":\"logstash-vulnwhisperer-*\",\"query\":{\"query_string\":{\"analyze_wildcard\":true,\"query\":\"*\"}},\"filter\":[]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "a6508640-897a-11e7-bbc0-33592ce0be1e",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer - Critical Assets Aggregated",
|
|
||||||
"visState": "{\"title\":\"VulnWhisperer - Critical Assets Aggregated\",\"type\":\"heatmap\",\"params\":{\"addTooltip\":true,\"addLegend\":true,\"enableHover\":true,\"legendPosition\":\"right\",\"times\":[],\"colorsNumber\":4,\"colorSchema\":\"Green to Red\",\"setColorRange\":true,\"colorsRange\":[{\"from\":0,\"to\":3},{\"from\":3,\"to\":7},{\"from\":7,\"to\":9},{\"from\":9,\"to\":11}],\"invertColors\":false,\"percentageMode\":false,\"valueAxes\":[{\"show\":false,\"id\":\"ValueAxis-1\",\"type\":\"value\",\"scale\":{\"type\":\"linear\",\"defaultYExtents\":false},\"labels\":{\"show\":true,\"rotate\":0,\"color\":\"white\"}}]},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"max\",\"schema\":\"metric\",\"params\":{\"field\":\"risk_score\",\"customLabel\":\"Residual Risk Score\"}},{\"id\":\"3\",\"enabled\":true,\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{},\"customLabel\":\"Date\"}},{\"id\":\"4\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"group\",\"params\":{\"field\":\"host\",\"size\":10,\"order\":\"desc\",\"orderBy\":\"1\",\"customLabel\":\"Critical Asset IP\"}},{\"id\":\"5\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"split\",\"params\":{\"field\":\"plugin_name.keyword\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\",\"row\":true}}],\"listeners\":{}}",
|
|
||||||
"uiStateJSON": "{\"vis\":{\"colors\":{\"0 - 3\":\"#7EB26D\",\"3 - 7\":\"#EAB839\",\"7 - 9\":\"#EF843C\",\"8 - 10\":\"#BF1B00\",\"9 - 11\":\"#BF1B00\"},\"defaultColors\":{\"0 - 3\":\"rgb(0,104,55)\",\"3 - 7\":\"rgb(135,203,103)\",\"7 - 9\":\"rgb(255,255,190)\",\"9 - 11\":\"rgb(249,142,82)\"},\"legendOpen\":false}}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"index\":\"logstash-vulnwhisperer-*\",\"query\":{\"query_string\":{\"analyze_wildcard\":true,\"query\":\"*\"}},\"filter\":[{\"$state\":{\"store\":\"appState\"},\"meta\":{\"alias\":\"Critical Asset\",\"disabled\":false,\"index\":\"logstash-vulnwhisperer-*\",\"key\":\"tags\",\"negate\":false,\"type\":\"phrase\",\"value\":\"critical_asset\"},\"query\":{\"match\":{\"tags\":{\"query\":\"critical_asset\",\"type\":\"phrase\"}}}}]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "099a3820-3f68-11e7-a6bd-e764d950e506",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "Timelion VulnWhisperer Example",
|
|
||||||
"visState": "{\"type\":\"timelion\",\"title\":\"Timelion VulnWhisperer Example\",\"params\":{\"expression\":\".es(index=logstash-vulnwhisperer-*,q=risk:high).label(\\\"Current High Risk\\\"),.es(index=logstash-vulnwhisperer-*,q=risk:high,offset=-1y).label(\\\"Last 1 Year High Risk\\\"),.es(index=logstash-vulnwhisperer-*,q=risk:medium).label(\\\"Current Medium Risk\\\"),.es(index=logstash-vulnwhisperer-*,q=risk:medium,offset=-1y).label(\\\"Last 1 Year Medium Risk\\\")\",\"interval\":\"auto\"}}",
|
|
||||||
"uiStateJSON": "{}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "67d432e0-44ec-11e7-a05f-d9719b331a27",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer - TL-Critical Risk",
|
|
||||||
"visState": "{\"title\":\"VulnWhisperer - TL-Critical Risk\",\"type\":\"timelion\",\"params\":{\"expression\":\".es(index='logstash-vulnwhisperer-*',q='(risk_score:>=9 AND risk_score:<=10)').label(\\\"Original\\\"),.es(index='logstash-vulnwhisperer-*',q='(risk_score:>=9 AND risk_score:<=10)',offset=-1w).label(\\\"One week offset\\\"),.es(index='logstash-vulnwhisperer-*',q='(risk_score:>=9 AND risk_score:<=10)').subtract(.es(index='logstash-vulnwhisperer-*',q='(risk_score:>=9 AND risk_score:<=10)',offset=-1w)).label(\\\"Difference\\\").lines(steps=3,fill=2,width=1)\",\"interval\":\"auto\"},\"aggs\":[],\"listeners\":{}}",
|
|
||||||
"uiStateJSON": "{}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "a91b9fe0-44ec-11e7-a05f-d9719b331a27",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer - TL-Medium Risk",
|
|
||||||
"visState": "{\"title\":\"VulnWhisperer - TL-Medium Risk\",\"type\":\"timelion\",\"params\":{\"expression\":\".es(index='logstash-vulnwhisperer-*',q='(risk_score:>=4 AND risk_score:<7)').label(\\\"Original\\\"),.es(index='logstash-vulnwhisperer-*',q='(risk_score:>=4 AND risk_score:<7)',offset=-1w).label(\\\"One week offset\\\"),.es(index='logstash-vulnwhisperer-*',q='(risk_score:>=4 AND risk_score:<7)').subtract(.es(index='logstash-vulnwhisperer-*',q='(risk_score:>=4 AND risk_score:<7)',offset=-1w)).label(\\\"Difference\\\").lines(steps=3,fill=2,width=1)\",\"interval\":\"auto\"},\"aggs\":[],\"listeners\":{}}",
|
|
||||||
"uiStateJSON": "{}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "8d9592d0-44ec-11e7-a05f-d9719b331a27",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer - TL-High Risk",
|
|
||||||
"visState": "{\"title\":\"VulnWhisperer - TL-High Risk\",\"type\":\"timelion\",\"params\":{\"expression\":\".es(index='logstash-vulnwhisperer-*',q='(risk_score:>=7 AND risk_score:<9)').label(\\\"Original\\\"),.es(index='logstash-vulnwhisperer-*',q='(risk_score:>=7 AND risk_score:<9)',offset=-1w).label(\\\"One week offset\\\"),.es(index='logstash-vulnwhisperer-*',q='(risk_score:>=7 AND risk_score:<9)').subtract(.es(index='logstash-vulnwhisperer-*',q='(risk_score:>=7 AND risk_score:<9)',offset=-1w)).label(\\\"Difference\\\").lines(steps=3,fill=2,width=1)\",\"interval\":\"auto\"},\"aggs\":[],\"listeners\":{}}",
|
|
||||||
"uiStateJSON": "{}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "a2d66660-44ec-11e7-a05f-d9719b331a27",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer - TL-Low Risk",
|
|
||||||
"visState": "{\"title\":\"VulnWhisperer - TL-Low Risk\",\"type\":\"timelion\",\"params\":{\"expression\":\".es(index='logstash-vulnwhisperer-*',q='(risk_score:>0 AND risk_score:<4)').label(\\\"Original\\\"),.es(index='logstash-vulnwhisperer-*',q='(risk_score:>0 AND risk_score:<4)',offset=-1w).label(\\\"One week offset\\\"),.es(index='logstash-vulnwhisperer-*',q='(risk_score:>0 AND risk_score:<4)').subtract(.es(index='logstash-vulnwhisperer-*',q='(risk_score:>0 AND risk_score:<4)',offset=-1w)).label(\\\"Difference\\\").lines(steps=3,fill=2,width=1)\",\"interval\":\"auto\"},\"aggs\":[],\"listeners\":{}}",
|
|
||||||
"uiStateJSON": "{}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "fb6eb020-49ab-11e7-8f8c-57ad64ec48a6",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer - Critical Risk Score for Tagged Assets",
|
|
||||||
"visState": "{\"title\":\"VulnWhisperer - Critical Risk Score for Tagged Assets\",\"type\":\"timelion\",\"params\":{\"expression\":\".es(index=logstash-vulnwhisperer-*,q='risk_score:>9 AND tags:hipaa_asset').label(\\\"HIPAA Assets\\\"),.es(index=logstash-vulnwhisperer-*,q='risk_score:>9 AND tags:pci_asset').label(\\\"PCI Systems\\\"),.es(index=logstash-vulnwhisperer-*,q='risk_score:>9 AND tags:has_hipaa_data').label(\\\"Has HIPAA Data\\\")\",\"interval\":\"auto\"},\"aggs\":[],\"listeners\":{}}",
|
|
||||||
"uiStateJSON": "{}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "b2f2adb0-897f-11e7-a2d2-c57bca21b3aa",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer - Risk: Total",
|
|
||||||
"visState": "{\"title\":\"VulnWhisperer - Risk: Total\",\"type\":\"goal\",\"params\":{\"addLegend\":true,\"addTooltip\":true,\"gauge\":{\"autoExtend\":false,\"backStyle\":\"Full\",\"colorSchema\":\"Green to Red\",\"colorsRange\":[{\"from\":0,\"to\":10000}],\"gaugeColorMode\":\"Background\",\"gaugeStyle\":\"Full\",\"gaugeType\":\"Metric\",\"invertColors\":false,\"labels\":{\"color\":\"black\",\"show\":false},\"orientation\":\"vertical\",\"percentageMode\":false,\"scale\":{\"color\":\"#333\",\"labels\":false,\"show\":true,\"width\":2},\"style\":{\"bgColor\":true,\"bgFill\":\"white\",\"fontSize\":\"34\",\"labelColor\":false,\"subText\":\"Risk\"},\"type\":\"simple\",\"useRanges\":false,\"verticalSplit\":false},\"type\":\"gauge\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{\"customLabel\":\"Total\"}},{\"id\":\"2\",\"enabled\":true,\"type\":\"filters\",\"schema\":\"group\",\"params\":{\"filters\":[{\"input\":{\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}}},\"label\":\"Critical\"}]}}],\"listeners\":{}}",
|
|
||||||
"uiStateJSON": "{\"vis\":{\"colors\":{\"0 - 10000\":\"#64B0C8\"},\"defaultColors\":{\"0 - 10000\":\"rgb(0,104,55)\"},\"legendOpen\":false}}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"index\":\"logstash-vulnwhisperer-*\",\"query\":{\"query_string\":{\"analyze_wildcard\":true,\"query\":\"*\"}},\"filter\":[]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "465c5820-8977-11e7-857e-e1d56b17746d",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer - Critical Assets",
|
|
||||||
"visState": "{\"title\":\"VulnWhisperer - Critical Assets\",\"type\":\"heatmap\",\"params\":{\"addTooltip\":true,\"addLegend\":true,\"enableHover\":true,\"legendPosition\":\"right\",\"times\":[],\"colorsNumber\":4,\"colorSchema\":\"Green to Red\",\"setColorRange\":true,\"colorsRange\":[{\"from\":0,\"to\":3},{\"from\":3,\"to\":7},{\"from\":7,\"to\":9},{\"from\":9,\"to\":11}],\"invertColors\":false,\"percentageMode\":false,\"valueAxes\":[{\"show\":false,\"id\":\"ValueAxis-1\",\"type\":\"value\",\"scale\":{\"type\":\"linear\",\"defaultYExtents\":false},\"labels\":{\"show\":false,\"rotate\":0,\"color\":\"white\"}}],\"type\":\"heatmap\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"max\",\"schema\":\"metric\",\"params\":{\"field\":\"risk_score\",\"customLabel\":\"Residual Risk Score\"}},{\"id\":\"2\",\"enabled\":false,\"type\":\"terms\",\"schema\":\"split\",\"params\":{\"field\":\"risk_score\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\",\"row\":true}},{\"id\":\"3\",\"enabled\":true,\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{},\"customLabel\":\"Date\"}},{\"id\":\"4\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"group\",\"params\":{\"field\":\"asset.keyword\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\",\"customLabel\":\"Critical Asset\"}}],\"listeners\":{}}",
|
|
||||||
"uiStateJSON": "{\"vis\":{\"defaultColors\":{\"0 - 3\":\"rgb(0,104,55)\",\"3 - 7\":\"rgb(135,203,103)\",\"7 - 9\":\"rgb(255,255,190)\",\"9 - 11\":\"rgb(249,142,82)\"},\"colors\":{\"8 - 10\":\"#BF1B00\",\"9 - 11\":\"#BF1B00\",\"7 - 9\":\"#EF843C\",\"3 - 7\":\"#EAB839\",\"0 - 3\":\"#7EB26D\"},\"legendOpen\":false}}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"index\":\"logstash-vulnwhisperer-*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[{\"meta\":{\"index\":\"logstash-vulnwhisperer-*\",\"negate\":false,\"disabled\":false,\"alias\":\"Critical Asset\",\"type\":\"phrase\",\"key\":\"tags\",\"value\":\"critical_asset\"},\"query\":{\"match\":{\"tags\":{\"query\":\"critical_asset\",\"type\":\"phrase\"}}},\"$state\":{\"store\":\"appState\"}}]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "852816e0-3eb1-11e7-90cb-918f9cb01e3d",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer-CVSS",
|
|
||||||
"visState": "{\"title\":\"VulnWhisperer-CVSS\",\"type\":\"table\",\"params\":{\"perPage\":10,\"showMeticsAtAllLevels\":false,\"showPartialRows\":false,\"showTotal\":false,\"sort\":{\"columnIndex\":null,\"direction\":null},\"totalFunc\":\"sum\",\"type\":\"table\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"bucket\",\"params\":{\"field\":\"cvss.keyword\",\"size\":20,\"order\":\"desc\",\"orderBy\":\"1\",\"customLabel\":\"CVSS Score\"}},{\"id\":\"4\",\"enabled\":true,\"type\":\"cardinality\",\"schema\":\"metric\",\"params\":{\"field\":\"asset.keyword\",\"customLabel\":\"# of Assets\"}}],\"listeners\":{}}",
|
|
||||||
"uiStateJSON": "{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":0,\"direction\":\"desc\"}}}}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"index\":\"logstash-vulnwhisperer-*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "d048c220-80b3-11e7-8790-73b60225f736",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer - Risk: High",
|
|
||||||
"visState": "{\"title\":\"VulnWhisperer - Risk: High\",\"type\":\"goal\",\"params\":{\"addTooltip\":true,\"addLegend\":true,\"type\":\"gauge\",\"gauge\":{\"verticalSplit\":false,\"autoExtend\":false,\"percentageMode\":false,\"gaugeType\":\"Metric\",\"gaugeStyle\":\"Full\",\"backStyle\":\"Full\",\"orientation\":\"vertical\",\"useRanges\":false,\"colorSchema\":\"Green to Red\",\"gaugeColorMode\":\"Background\",\"colorsRange\":[{\"from\":0,\"to\":1000}],\"invertColors\":false,\"labels\":{\"show\":false,\"color\":\"black\"},\"scale\":{\"show\":true,\"labels\":false,\"color\":\"#333\",\"width\":2},\"type\":\"simple\",\"style\":{\"bgFill\":\"white\",\"bgColor\":true,\"labelColor\":false,\"subText\":\"\",\"fontSize\":\"34\"},\"extendRange\":true}},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{\"customLabel\":\"High Risk\"}},{\"id\":\"2\",\"enabled\":true,\"type\":\"filters\",\"schema\":\"group\",\"params\":{\"filters\":[{\"input\":{\"query\":{\"query_string\":{\"query\":\"risk_score_name:high\"}}},\"label\":\"\"}]}}],\"listeners\":{}}",
|
|
||||||
"uiStateJSON": "{\"vis\":{\"defaultColors\":{\"0 - 1000\":\"rgb(0,104,55)\"},\"legendOpen\":true,\"colors\":{\"0 - 10000\":\"#EF843C\",\"0 - 1000\":\"#E0752D\"}}}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"index\":\"logstash-vulnwhisperer-*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "db55bce0-80b3-11e7-8790-73b60225f736",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer - Risk: Critical",
|
|
||||||
"visState": "{\"title\":\"VulnWhisperer - Risk: Critical\",\"type\":\"goal\",\"params\":{\"addLegend\":true,\"addTooltip\":true,\"gauge\":{\"autoExtend\":false,\"backStyle\":\"Full\",\"colorSchema\":\"Green to Red\",\"colorsRange\":[{\"from\":0,\"to\":10000}],\"gaugeColorMode\":\"Background\",\"gaugeStyle\":\"Full\",\"gaugeType\":\"Metric\",\"invertColors\":false,\"labels\":{\"color\":\"black\",\"show\":false},\"orientation\":\"vertical\",\"percentageMode\":false,\"scale\":{\"color\":\"#333\",\"labels\":false,\"show\":true,\"width\":2},\"style\":{\"bgColor\":true,\"bgFill\":\"white\",\"fontSize\":\"34\",\"labelColor\":false,\"subText\":\"Risk\"},\"type\":\"simple\",\"useRanges\":false,\"verticalSplit\":false},\"type\":\"gauge\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{\"customLabel\":\"Critical Risk\"}},{\"id\":\"2\",\"enabled\":true,\"type\":\"filters\",\"schema\":\"group\",\"params\":{\"filters\":[{\"input\":{\"query\":{\"query_string\":{\"query\":\"risk_score_name:critical\"}}},\"label\":\"Critical\"}]}}],\"listeners\":{}}",
|
|
||||||
"uiStateJSON": "{\"vis\":{\"colors\":{\"0 - 10000\":\"#BF1B00\"},\"defaultColors\":{\"0 - 10000\":\"rgb(0,104,55)\"},\"legendOpen\":false}}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"index\":\"logstash-vulnwhisperer-*\",\"query\":{\"query_string\":{\"analyze_wildcard\":true,\"query\":\"*\"}},\"filter\":[]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "56f0f5f0-3ebe-11e7-a192-93f36fbd9d05",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer-RiskOverTime",
|
|
||||||
"visState": "{\"title\":\"VulnWhisperer-RiskOverTime\",\"type\":\"line\",\"params\":{\"addLegend\":true,\"addTimeMarker\":false,\"addTooltip\":true,\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"labels\":{\"show\":true,\"truncate\":100},\"position\":\"bottom\",\"scale\":{\"type\":\"linear\"},\"show\":true,\"style\":{},\"title\":{\"text\":\"@timestamp per 12 hours\"},\"type\":\"category\"}],\"defaultYExtents\":false,\"drawLinesBetweenPoints\":true,\"grid\":{\"categoryLines\":false,\"style\":{\"color\":\"#eee\"},\"valueAxis\":\"ValueAxis-1\"},\"interpolate\":\"linear\",\"legendPosition\":\"right\",\"orderBucketsBySum\":false,\"radiusRatio\":9,\"scale\":\"linear\",\"seriesParams\":[{\"data\":{\"id\":\"1\",\"label\":\"Count\"},\"drawLinesBetweenPoints\":true,\"interpolate\":\"linear\",\"mode\":\"normal\",\"show\":\"true\",\"showCircles\":true,\"type\":\"line\",\"valueAxis\":\"ValueAxis-1\"}],\"setYExtents\":false,\"showCircles\":true,\"times\":[],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"labels\":{\"filter\":false,\"rotate\":0,\"show\":true,\"truncate\":100},\"name\":\"LeftAxis-1\",\"position\":\"left\",\"scale\":{\"mode\":\"normal\",\"type\":\"linear\"},\"show\":true,\"style\":{},\"title\":{\"text\":\"Count\"},\"type\":\"value\"}],\"type\":\"line\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}},{\"id\":\"3\",\"enabled\":true,\"type\":\"filters\",\"schema\":\"group\",\"params\":{\"filters\":[{\"input\":{\"query\":{\"query_string\":{\"query\":\"risk_score_name:info\"}}},\"label\":\"Info\"},{\"input\":{\"query\":{\"query_string\":{\"query\":\"risk_score_name:low\"}}},\"label\":\"Low\"},{\"input\":{\"query\":{\"query_string\":{\"query\":\"risk_score_name:medium\"}}},\"label\":\"Medium\"},{\"input\":{\"query\":{\"query_string\":{\"query\":\"risk_score_name:high\"}}},\"label\":\"High\"},{\"input\":{\"query\":{\"query_string\":{\"query\":\"risk_score_name:critical\"}}},\"label\":\"Critical\"}]}}],\"listeners\":{}}",
|
|
||||||
"uiStateJSON": "{\"vis\":{\"colors\":{\"Critical\":\"#962D82\",\"High\":\"#BF1B00\",\"Low\":\"#629E51\",\"Medium\":\"#EAB839\",\"Info\":\"#65C5DB\"}}}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"index\":\"logstash-vulnwhisperer-*\",\"query\":{\"query_string\":{\"analyze_wildcard\":true,\"query\":\"*\"}},\"filter\":[]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "c1361da0-80b3-11e7-8790-73b60225f736",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer - Risk: Medium",
|
|
||||||
"visState": "{\"title\":\"VulnWhisperer - Risk: Medium\",\"type\":\"goal\",\"params\":{\"addTooltip\":true,\"addLegend\":true,\"type\":\"gauge\",\"gauge\":{\"verticalSplit\":false,\"autoExtend\":false,\"percentageMode\":false,\"gaugeType\":\"Metric\",\"gaugeStyle\":\"Full\",\"backStyle\":\"Full\",\"orientation\":\"vertical\",\"useRanges\":false,\"colorSchema\":\"Green to Red\",\"gaugeColorMode\":\"Background\",\"colorsRange\":[{\"from\":0,\"to\":10000}],\"invertColors\":false,\"labels\":{\"show\":false,\"color\":\"black\"},\"scale\":{\"show\":true,\"labels\":false,\"color\":\"#333\",\"width\":2},\"type\":\"simple\",\"style\":{\"bgFill\":\"white\",\"bgColor\":true,\"labelColor\":false,\"subText\":\"\",\"fontSize\":\"34\"},\"extendRange\":false},\"isDisplayWarning\":false},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{\"customLabel\":\"Medium Risk\"}},{\"id\":\"2\",\"enabled\":true,\"type\":\"filters\",\"schema\":\"group\",\"params\":{\"filters\":[{\"input\":{\"query\":{\"query_string\":{\"query\":\"risk_score_name:medium\"}}},\"label\":\"Medium Risk\"}]}}],\"listeners\":{}}",
|
|
||||||
"uiStateJSON": "{\"vis\":{\"defaultColors\":{\"0 - 10000\":\"rgb(0,104,55)\"},\"legendOpen\":true,\"colors\":{\"0 - 10000\":\"#EAB839\"}}}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"index\":\"logstash-vulnwhisperer-*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "e46ff7f0-897d-11e7-934b-67cec0a7da65",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer - Risk: Low",
|
|
||||||
"visState": "{\"title\":\"VulnWhisperer - Risk: Low\",\"type\":\"goal\",\"params\":{\"addTooltip\":true,\"addLegend\":true,\"type\":\"gauge\",\"gauge\":{\"verticalSplit\":false,\"autoExtend\":false,\"percentageMode\":false,\"gaugeType\":\"Metric\",\"gaugeStyle\":\"Full\",\"backStyle\":\"Full\",\"orientation\":\"vertical\",\"useRanges\":false,\"colorSchema\":\"Green to Red\",\"gaugeColorMode\":\"Background\",\"colorsRange\":[{\"from\":0,\"to\":10000}],\"invertColors\":false,\"labels\":{\"show\":false,\"color\":\"black\"},\"scale\":{\"show\":true,\"labels\":false,\"color\":\"#333\",\"width\":2},\"type\":\"simple\",\"style\":{\"bgFill\":\"white\",\"bgColor\":true,\"labelColor\":false,\"subText\":\"\",\"fontSize\":\"34\"},\"extendRange\":false}},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{\"customLabel\":\"Low Risk\"}},{\"id\":\"2\",\"enabled\":true,\"type\":\"filters\",\"schema\":\"group\",\"params\":{\"filters\":[{\"input\":{\"query\":{\"query_string\":{\"query\":\"risk_score_name:low\"}}},\"label\":\"Low Risk\"}]}}],\"listeners\":{}}",
|
|
||||||
"uiStateJSON": "{\"vis\":{\"defaultColors\":{\"0 - 10000\":\"rgb(0,104,55)\"},\"legendOpen\":true,\"colors\":{\"0 - 10000\":\"#629E51\"}}}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"index\":\"logstash-vulnwhisperer-*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "995e2280-3df3-11e7-a44e-c79ca8efb780",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer-Asset",
|
|
||||||
"visState": "{\"title\":\"VulnWhisperer-Asset\",\"type\":\"table\",\"params\":{\"perPage\":15,\"showMeticsAtAllLevels\":false,\"showPartialRows\":false,\"showTotal\":false,\"sort\":{\"columnIndex\":null,\"direction\":null},\"totalFunc\":\"sum\",\"type\":\"table\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"bucket\",\"params\":{\"field\":\"asset.keyword\",\"size\":50,\"order\":\"desc\",\"orderBy\":\"1\",\"customLabel\":\"Asset\"}}],\"listeners\":{}}",
|
|
||||||
"uiStateJSON": "{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"index\":\"logstash-vulnwhisperer-*\",\"query\":{\"query_string\":{\"analyze_wildcard\":true,\"query\":\"*\"}},\"filter\":[]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
@ -1,43 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"_id": "72051530-448e-11e7-a818-f5f80dfc3590",
|
|
||||||
"_type": "dashboard",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer - Reporting",
|
|
||||||
"hits": 0,
|
|
||||||
"description": "",
|
|
||||||
"panelsJSON": "[{\"col\":1,\"id\":\"2f979030-44b9-11e7-a818-f5f80dfc3590\",\"panelIndex\":5,\"row\":12,\"size_x\":6,\"size_y\":4,\"type\":\"visualization\"},{\"col\":1,\"id\":\"8d9592d0-44ec-11e7-a05f-d9719b331a27\",\"panelIndex\":12,\"row\":8,\"size_x\":6,\"size_y\":4,\"type\":\"visualization\"},{\"col\":7,\"id\":\"67d432e0-44ec-11e7-a05f-d9719b331a27\",\"panelIndex\":14,\"row\":4,\"size_x\":6,\"size_y\":4,\"type\":\"visualization\"},{\"col\":10,\"id\":\"297df800-3f7e-11e7-bd24-6903e3283192\",\"panelIndex\":15,\"row\":8,\"size_x\":3,\"size_y\":4,\"type\":\"visualization\"},{\"col\":7,\"id\":\"471a3580-3f6b-11e7-88e7-df1abe6547fb\",\"panelIndex\":20,\"row\":8,\"size_x\":3,\"size_y\":4,\"type\":\"visualization\"},{\"col\":11,\"id\":\"995e2280-3df3-11e7-a44e-c79ca8efb780\",\"panelIndex\":22,\"row\":1,\"size_x\":2,\"size_y\":3,\"type\":\"visualization\"},{\"col\":9,\"id\":\"b2f2adb0-897f-11e7-a2d2-c57bca21b3aa\",\"panelIndex\":23,\"row\":1,\"size_x\":2,\"size_y\":3,\"type\":\"visualization\"},{\"col\":7,\"id\":\"db55bce0-80b3-11e7-8790-73b60225f736\",\"panelIndex\":25,\"row\":1,\"size_x\":2,\"size_y\":3,\"type\":\"visualization\"},{\"col\":5,\"id\":\"d048c220-80b3-11e7-8790-73b60225f736\",\"panelIndex\":26,\"row\":1,\"size_x\":2,\"size_y\":3,\"type\":\"visualization\"},{\"col\":1,\"id\":\"e46ff7f0-897d-11e7-934b-67cec0a7da65\",\"panelIndex\":27,\"row\":1,\"size_x\":2,\"size_y\":3,\"type\":\"visualization\"},{\"col\":3,\"id\":\"c1361da0-80b3-11e7-8790-73b60225f736\",\"panelIndex\":28,\"row\":1,\"size_x\":2,\"size_y\":3,\"type\":\"visualization\"},{\"col\":1,\"id\":\"479deab0-8a39-11e7-a58a-9bfcb3761a3d\",\"panelIndex\":29,\"row\":4,\"size_x\":6,\"size_y\":4,\"type\":\"visualization\"}]",
|
|
||||||
"optionsJSON": "{\"darkTheme\":false}",
|
|
||||||
"uiStateJSON": "{\"P-15\":{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}},\"P-20\":{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}},\"P-21\":{\"vis\":{\"defaultColors\":{\"0 - 100\":\"rgb(0,104,55)\"}}},\"P-22\":{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}},\"P-23\":{\"vis\":{\"defaultColors\":{\"0 - 10000\":\"rgb(0,104,55)\"}}},\"P-24\":{\"vis\":{\"defaultColors\":{\"0 - 10000\":\"rgb(0,104,55)\"}}},\"P-25\":{\"vis\":{\"defaultColors\":{\"0 - 10000\":\"rgb(0,104,55)\"}}},\"P-26\":{\"vis\":{\"defaultColors\":{\"0 - 1000\":\"rgb(0,104,55)\"},\"legendOpen\":false}},\"P-27\":{\"vis\":{\"defaultColors\":{\"0 - 10000\":\"rgb(0,104,55)\"},\"legendOpen\":false}},\"P-28\":{\"vis\":{\"defaultColors\":{\"0 - 10000\":\"rgb(0,104,55)\"},\"legendOpen\":false}},\"P-5\":{\"vis\":{\"legendOpen\":false}}}",
|
|
||||||
"version": 1,
|
|
||||||
"timeRestore": true,
|
|
||||||
"timeTo": "now",
|
|
||||||
"timeFrom": "now-1y",
|
|
||||||
"refreshInterval": {
|
|
||||||
"display": "Off",
|
|
||||||
"pause": false,
|
|
||||||
"value": 0
|
|
||||||
},
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"filter\":[{\"query\":{\"match_all\":{}}}],\"highlightAll\":true,\"version\":true}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "AWCUqesWib22Ai8JwW3u",
|
|
||||||
"_type": "dashboard",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer - Risk Mitigation",
|
|
||||||
"hits": 0,
|
|
||||||
"description": "",
|
|
||||||
"panelsJSON": "[{\"col\":11,\"id\":\"995e2280-3df3-11e7-a44e-c79ca8efb780\",\"panelIndex\":20,\"row\":8,\"size_x\":2,\"size_y\":6,\"type\":\"visualization\"},{\"col\":1,\"id\":\"852816e0-3eb1-11e7-90cb-918f9cb01e3d\",\"panelIndex\":21,\"row\":10,\"size_x\":3,\"size_y\":5,\"type\":\"visualization\"},{\"col\":4,\"id\":\"297df800-3f7e-11e7-bd24-6903e3283192\",\"panelIndex\":27,\"row\":8,\"size_x\":3,\"size_y\":5,\"type\":\"visualization\"},{\"col\":9,\"id\":\"35b6d320-3f7f-11e7-bd24-6903e3283192\",\"panelIndex\":28,\"row\":8,\"size_x\":2,\"size_y\":6,\"type\":\"visualization\"},{\"col\":11,\"id\":\"471a3580-3f6b-11e7-88e7-df1abe6547fb\",\"panelIndex\":30,\"row\":1,\"size_x\":2,\"size_y\":3,\"type\":\"visualization\"},{\"col\":7,\"id\":\"de1a5f40-3f85-11e7-97f9-3777d794626d\",\"panelIndex\":31,\"row\":8,\"size_x\":2,\"size_y\":5,\"type\":\"visualization\"},{\"col\":10,\"id\":\"5093c620-44e9-11e7-8014-ede06a7e69f8\",\"panelIndex\":37,\"row\":4,\"size_x\":3,\"size_y\":4,\"type\":\"visualization\"},{\"col\":1,\"columns\":[\"host\",\"risk\",\"risk_score\",\"cve\",\"plugin_name\",\"solution\",\"plugin_output\"],\"id\":\"54648700-3f74-11e7-852e-69207a3d0726\",\"panelIndex\":38,\"row\":15,\"size_x\":12,\"size_y\":6,\"sort\":[\"@timestamp\",\"desc\"],\"type\":\"search\"},{\"col\":1,\"id\":\"fb6eb020-49ab-11e7-8f8c-57ad64ec48a6\",\"panelIndex\":39,\"row\":8,\"size_x\":3,\"size_y\":2,\"type\":\"visualization\"},{\"col\":5,\"id\":\"465c5820-8977-11e7-857e-e1d56b17746d\",\"panelIndex\":40,\"row\":4,\"size_x\":5,\"size_y\":4,\"type\":\"visualization\"},{\"col\":1,\"id\":\"56f0f5f0-3ebe-11e7-a192-93f36fbd9d05\",\"panelIndex\":46,\"row\":4,\"size_x\":4,\"size_y\":4,\"type\":\"visualization\"},{\"col\":1,\"id\":\"e46ff7f0-897d-11e7-934b-67cec0a7da65\",\"panelIndex\":47,\"row\":1,\"size_x\":2,\"size_y\":3,\"type\":\"visualization\"},{\"col\":3,\"id\":\"c1361da0-80b3-11e7-8790-73b60225f736\",\"panelIndex\":48,\"row\":1,\"size_x\":2,\"size_y\":3,\"type\":\"visualization\"},{\"col\":5,\"id\":\"d048c220-80b3-11e7-8790-73b60225f736\",\"panelIndex\":49,\"row\":1,\"size_x\":2,\"size_y\":3,\"type\":\"visualization\"},{\"col\":7,\"id\":\"db55bce0-80b3-11e7-8790-73b60225f736\",\"panelIndex\":50,\"row\":1,\"size_x\":2,\"size_y\":3,\"type\":\"visualization\"},{\"col\":9,\"id\":\"b2f2adb0-897f-11e7-a2d2-c57bca21b3aa\",\"panelIndex\":51,\"row\":1,\"size_x\":2,\"size_y\":3,\"type\":\"visualization\"}]",
|
|
||||||
"optionsJSON": "{\"darkTheme\":false}",
|
|
||||||
"uiStateJSON": "{\"P-11\":{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}},\"P-2\":{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}},\"P-20\":{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}},\"P-21\":{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":0,\"direction\":\"desc\"}}}},\"P-27\":{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}},\"P-28\":{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":0,\"direction\":\"desc\"}}}},\"P-3\":{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":0,\"direction\":\"asc\"}}}},\"P-30\":{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}},\"P-31\":{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}},\"P-40\":{\"vis\":{\"defaultColors\":{\"0 - 3\":\"rgb(0,104,55)\",\"3 - 7\":\"rgb(135,203,103)\",\"7 - 9\":\"rgb(255,255,190)\",\"9 - 11\":\"rgb(249,142,82)\"}}},\"P-41\":{\"vis\":{\"defaultColors\":{\"0 - 10000\":\"rgb(0,104,55)\"}}},\"P-42\":{\"vis\":{\"defaultColors\":{\"0 - 10000\":\"rgb(0,104,55)\"}}},\"P-43\":{\"vis\":{\"defaultColors\":{\"0 - 1000\":\"rgb(0,104,55)\"}}},\"P-44\":{\"vis\":{\"defaultColors\":{\"0 - 10000\":\"rgb(0,104,55)\"}}},\"P-45\":{\"vis\":{\"defaultColors\":{\"0 - 10000\":\"rgb(0,104,55)\"}}},\"P-46\":{\"vis\":{\"legendOpen\":true}},\"P-47\":{\"vis\":{\"defaultColors\":{\"0 - 10000\":\"rgb(0,104,55)\"},\"legendOpen\":false}},\"P-48\":{\"vis\":{\"defaultColors\":{\"0 - 10000\":\"rgb(0,104,55)\"},\"legendOpen\":false}},\"P-49\":{\"vis\":{\"defaultColors\":{\"0 - 1000\":\"rgb(0,104,55)\"},\"legendOpen\":false}},\"P-5\":{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}},\"P-50\":{\"vis\":{\"defaultColors\":{\"0 - 10000\":\"rgb(0,104,55)\"}}},\"P-51\":{\"vis\":{\"defaultColors\":{\"0 - 10000\":\"rgb(0,104,55)\"}}},\"P-6\":{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}},\"P-8\":{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}}}",
|
|
||||||
"version": 1,
|
|
||||||
"timeRestore": false,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"filter\":[{\"query\":{\"match_all\":{}}}],\"highlightAll\":true,\"version\":true}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
@ -1,170 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"_id": "AWCUo-jRib22Ai8JwW1N",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer - Risk: High Qualys Scoring",
|
|
||||||
"visState": "{\"title\":\"VulnWhisperer - Risk: High Qualys Scoring\",\"type\":\"goal\",\"params\":{\"addTooltip\":true,\"addLegend\":true,\"type\":\"gauge\",\"gauge\":{\"verticalSplit\":false,\"autoExtend\":false,\"percentageMode\":false,\"gaugeType\":\"Metric\",\"gaugeStyle\":\"Full\",\"backStyle\":\"Full\",\"orientation\":\"vertical\",\"useRanges\":false,\"colorSchema\":\"Green to Red\",\"gaugeColorMode\":\"Background\",\"colorsRange\":[{\"from\":0,\"to\":1000}],\"invertColors\":false,\"labels\":{\"show\":false,\"color\":\"black\"},\"scale\":{\"show\":true,\"labels\":false,\"color\":\"#333\",\"width\":2},\"type\":\"simple\",\"style\":{\"bgFill\":\"white\",\"bgColor\":true,\"labelColor\":false,\"subText\":\"\",\"fontSize\":\"34\"},\"extendRange\":true}},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{\"customLabel\":\"High Risk\"}},{\"id\":\"2\",\"enabled\":true,\"type\":\"filters\",\"schema\":\"group\",\"params\":{\"filters\":[{\"input\":{\"query\":{\"query_string\":{\"query\":\"risk:high\"}}},\"label\":\"\"}]}}],\"listeners\":{}}",
|
|
||||||
"uiStateJSON": "{\"vis\":{\"defaultColors\":{\"0 - 1000\":\"rgb(0,104,55)\"},\"legendOpen\":true,\"colors\":{\"0 - 10000\":\"#EF843C\",\"0 - 1000\":\"#E0752D\"}}}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"index\":\"logstash-vulnwhisperer-*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "AWCUozGBib22Ai8JwW1B",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer - Risk: Medium Qualys Scoring",
|
|
||||||
"visState": "{\"title\":\"VulnWhisperer - Risk: Medium Qualys Scoring\",\"type\":\"goal\",\"params\":{\"addTooltip\":true,\"addLegend\":true,\"type\":\"gauge\",\"gauge\":{\"verticalSplit\":false,\"autoExtend\":false,\"percentageMode\":false,\"gaugeType\":\"Metric\",\"gaugeStyle\":\"Full\",\"backStyle\":\"Full\",\"orientation\":\"vertical\",\"useRanges\":false,\"colorSchema\":\"Green to Red\",\"gaugeColorMode\":\"Background\",\"colorsRange\":[{\"from\":0,\"to\":10000}],\"invertColors\":false,\"labels\":{\"show\":false,\"color\":\"black\"},\"scale\":{\"show\":true,\"labels\":false,\"color\":\"#333\",\"width\":2},\"type\":\"simple\",\"style\":{\"bgFill\":\"white\",\"bgColor\":true,\"labelColor\":false,\"subText\":\"\",\"fontSize\":\"34\"},\"extendRange\":false}},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{\"customLabel\":\"Medium Risk\"}},{\"id\":\"2\",\"enabled\":true,\"type\":\"filters\",\"schema\":\"group\",\"params\":{\"filters\":[{\"input\":{\"query\":{\"query_string\":{\"query\":\"risk:medium\"}}},\"label\":\"Medium Risk\"}]}}],\"listeners\":{}}",
|
|
||||||
"uiStateJSON": "{\"vis\":{\"defaultColors\":{\"0 - 10000\":\"rgb(0,104,55)\"},\"legendOpen\":true,\"colors\":{\"0 - 10000\":\"#EAB839\"}}}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"index\":\"logstash-vulnwhisperer-*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "AWCUpE3Kib22Ai8JwW1c",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer - Risk: Critical Qualys Scoring",
|
|
||||||
"visState": "{\"title\":\"VulnWhisperer - Risk: Critical Qualys Scoring\",\"type\":\"goal\",\"params\":{\"addLegend\":true,\"addTooltip\":true,\"gauge\":{\"autoExtend\":false,\"backStyle\":\"Full\",\"colorSchema\":\"Green to Red\",\"colorsRange\":[{\"from\":0,\"to\":10000}],\"gaugeColorMode\":\"Background\",\"gaugeStyle\":\"Full\",\"gaugeType\":\"Metric\",\"invertColors\":false,\"labels\":{\"color\":\"black\",\"show\":false},\"orientation\":\"vertical\",\"percentageMode\":false,\"scale\":{\"color\":\"#333\",\"labels\":false,\"show\":true,\"width\":2},\"style\":{\"bgColor\":true,\"bgFill\":\"white\",\"fontSize\":\"34\",\"labelColor\":false,\"subText\":\"Risk\"},\"type\":\"simple\",\"useRanges\":false,\"verticalSplit\":false},\"type\":\"gauge\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{\"customLabel\":\"Critical Risk\"}},{\"id\":\"2\",\"enabled\":true,\"type\":\"filters\",\"schema\":\"group\",\"params\":{\"filters\":[{\"input\":{\"query\":{\"query_string\":{\"query\":\"risk:critical\"}}},\"label\":\"Critical\"}]}}],\"listeners\":{}}",
|
|
||||||
"uiStateJSON": "{\"vis\":{\"colors\":{\"0 - 10000\":\"#BF1B00\"},\"defaultColors\":{\"0 - 10000\":\"rgb(0,104,55)\"},\"legendOpen\":false}}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"index\":\"logstash-vulnwhisperer-*\",\"query\":{\"query_string\":{\"analyze_wildcard\":true,\"query\":\"*\"}},\"filter\":[]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "AWCUyeHGib22Ai8JwX62",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer-RiskOverTime Qualys Scoring",
|
|
||||||
"visState": "{\"title\":\"VulnWhisperer-RiskOverTime Qualys Scoring\",\"type\":\"line\",\"params\":{\"addLegend\":true,\"addTimeMarker\":false,\"addTooltip\":true,\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"labels\":{\"show\":true,\"truncate\":100},\"position\":\"bottom\",\"scale\":{\"type\":\"linear\"},\"show\":true,\"style\":{},\"title\":{\"text\":\"@timestamp per 12 hours\"},\"type\":\"category\"}],\"defaultYExtents\":false,\"drawLinesBetweenPoints\":true,\"grid\":{\"categoryLines\":false,\"style\":{\"color\":\"#eee\"},\"valueAxis\":\"ValueAxis-1\"},\"interpolate\":\"linear\",\"legendPosition\":\"right\",\"orderBucketsBySum\":false,\"radiusRatio\":9,\"scale\":\"linear\",\"seriesParams\":[{\"data\":{\"id\":\"1\",\"label\":\"Count\"},\"drawLinesBetweenPoints\":true,\"interpolate\":\"linear\",\"mode\":\"normal\",\"show\":\"true\",\"showCircles\":true,\"type\":\"line\",\"valueAxis\":\"ValueAxis-1\"}],\"setYExtents\":false,\"showCircles\":true,\"times\":[],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"labels\":{\"filter\":false,\"rotate\":0,\"show\":true,\"truncate\":100},\"name\":\"LeftAxis-1\",\"position\":\"left\",\"scale\":{\"mode\":\"normal\",\"type\":\"linear\"},\"show\":true,\"style\":{},\"title\":{\"text\":\"Count\"},\"type\":\"value\"}],\"type\":\"line\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}},{\"id\":\"3\",\"enabled\":true,\"type\":\"filters\",\"schema\":\"group\",\"params\":{\"filters\":[{\"input\":{\"query\":{\"query_string\":{\"query\":\"risk:info\"}}},\"label\":\"Info\"},{\"input\":{\"query\":{\"query_string\":{\"query\":\"risk:low\"}}},\"label\":\"Low\"},{\"input\":{\"query\":{\"query_string\":{\"query\":\"risk:medium\"}}},\"label\":\"Medium\"},{\"input\":{\"query\":{\"query_string\":{\"query\":\"risk:high\"}}},\"label\":\"High\"},{\"input\":{\"query\":{\"query_string\":{\"query\":\"risk:critical\"}}},\"label\":\"Critical\"}]}}],\"listeners\":{}}",
|
|
||||||
"uiStateJSON": "{\"vis\":{\"colors\":{\"Critical\":\"#962D82\",\"High\":\"#BF1B00\",\"Low\":\"#629E51\",\"Medium\":\"#EAB839\",\"Info\":\"#65C5DB\"}}}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"index\":\"logstash-vulnwhisperer-*\",\"query\":{\"query_string\":{\"analyze_wildcard\":true,\"query\":\"*\"}},\"filter\":[]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "AWCUos-Fib22Ai8JwW0y",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer - Risk: Low Qualys Scoring",
|
|
||||||
"visState": "{\"title\":\"VulnWhisperer - Risk: Low Qualys Scoring\",\"type\":\"goal\",\"params\":{\"addTooltip\":true,\"addLegend\":true,\"type\":\"gauge\",\"gauge\":{\"verticalSplit\":false,\"autoExtend\":false,\"percentageMode\":false,\"gaugeType\":\"Metric\",\"gaugeStyle\":\"Full\",\"backStyle\":\"Full\",\"orientation\":\"vertical\",\"useRanges\":false,\"colorSchema\":\"Green to Red\",\"gaugeColorMode\":\"Background\",\"colorsRange\":[{\"from\":0,\"to\":10000}],\"invertColors\":false,\"labels\":{\"show\":false,\"color\":\"black\"},\"scale\":{\"show\":true,\"labels\":false,\"color\":\"#333\",\"width\":2},\"type\":\"simple\",\"style\":{\"bgFill\":\"white\",\"bgColor\":true,\"labelColor\":false,\"subText\":\"\",\"fontSize\":\"34\"},\"extendRange\":false}},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{\"customLabel\":\"Low Risk\"}},{\"id\":\"2\",\"enabled\":true,\"type\":\"filters\",\"schema\":\"group\",\"params\":{\"filters\":[{\"input\":{\"query\":{\"query_string\":{\"query\":\"risk:low\"}}},\"label\":\"Low Risk\"}]}}],\"listeners\":{}}",
|
|
||||||
"uiStateJSON": "{\"vis\":{\"defaultColors\":{\"0 - 10000\":\"rgb(0,104,55)\"},\"legendOpen\":true,\"colors\":{\"0 - 10000\":\"#629E51\"}}}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"index\":\"logstash-vulnwhisperer-*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "AWCg9Wsfib22Ai8Jww3v",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer - Qualys: Category Description",
|
|
||||||
"visState": "{\"title\":\"VulnWhisperer - Qualys: Category Description\",\"type\":\"table\",\"params\":{\"perPage\":10,\"showPartialRows\":false,\"showMeticsAtAllLevels\":false,\"sort\":{\"columnIndex\":null,\"direction\":null},\"showTotal\":false,\"totalFunc\":\"sum\",\"type\":\"table\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"bucket\",\"params\":{\"field\":\"category_description.keyword\",\"size\":20,\"order\":\"desc\",\"orderBy\":\"1\",\"customLabel\":\"Category Description\"}}],\"listeners\":{}}",
|
|
||||||
"uiStateJSON": "{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"index\":\"logstash-vulnwhisperer-*\",\"query\":{\"match_all\":{}},\"filter\":[]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "AWCg88f1ib22Ai8Jww3C",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer - QualysOS",
|
|
||||||
"visState": "{\"title\":\"VulnWhisperer - QualysOS\",\"type\":\"table\",\"params\":{\"perPage\":10,\"showPartialRows\":false,\"showMeticsAtAllLevels\":false,\"sort\":{\"columnIndex\":null,\"direction\":null},\"showTotal\":false,\"totalFunc\":\"sum\",\"type\":\"table\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"bucket\",\"params\":{\"field\":\"operating_system.keyword\",\"size\":20,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}",
|
|
||||||
"uiStateJSON": "{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"index\":\"logstash-vulnwhisperer-*\",\"query\":{\"match_all\":{}},\"filter\":[]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "AWCg9JUAib22Ai8Jww3Y",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer - QualysOwner",
|
|
||||||
"visState": "{\"title\":\"VulnWhisperer - QualysOwner\",\"type\":\"table\",\"params\":{\"perPage\":10,\"showPartialRows\":false,\"showMeticsAtAllLevels\":false,\"sort\":{\"columnIndex\":null,\"direction\":null},\"showTotal\":false,\"totalFunc\":\"sum\",\"type\":\"table\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"bucket\",\"params\":{\"field\":\"owner.keyword\",\"size\":20,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}",
|
|
||||||
"uiStateJSON": "{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"index\":\"logstash-vulnwhisperer-*\",\"query\":{\"match_all\":{}},\"filter\":[]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "AWCg9tE6ib22Ai8Jww4R",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer - Qualys: Impact",
|
|
||||||
"visState": "{\"title\":\"VulnWhisperer - Qualys: Impact\",\"type\":\"table\",\"params\":{\"perPage\":10,\"showPartialRows\":false,\"showMeticsAtAllLevels\":false,\"sort\":{\"columnIndex\":null,\"direction\":null},\"showTotal\":false,\"totalFunc\":\"sum\",\"type\":\"table\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"bucket\",\"params\":{\"field\":\"impact.keyword\",\"size\":20,\"order\":\"desc\",\"orderBy\":\"1\",\"customLabel\":\"Impact\"}}],\"listeners\":{}}",
|
|
||||||
"uiStateJSON": "{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"index\":\"logstash-vulnwhisperer-*\",\"query\":{\"match_all\":{}},\"filter\":[]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "AWCg9igvib22Ai8Jww36",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer - Qualys: Level",
|
|
||||||
"visState": "{\"title\":\"VulnWhisperer - Qualys: Level\",\"type\":\"table\",\"params\":{\"perPage\":10,\"showPartialRows\":false,\"showMeticsAtAllLevels\":false,\"sort\":{\"columnIndex\":null,\"direction\":null},\"showTotal\":false,\"totalFunc\":\"sum\",\"type\":\"table\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"bucket\",\"params\":{\"field\":\"level.keyword\",\"size\":20,\"order\":\"desc\",\"orderBy\":\"1\",\"customLabel\":\"Level\"}}],\"listeners\":{}}",
|
|
||||||
"uiStateJSON": "{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"index\":\"logstash-vulnwhisperer-*\",\"query\":{\"match_all\":{}},\"filter\":[]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "AWCUsp_3ib22Ai8JwW7R",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer - TL-Critical Risk Qualys Scoring",
|
|
||||||
"visState": "{\"title\":\"VulnWhisperer - TL-Critical Risk Qualys Scoring\",\"type\":\"timelion\",\"params\":{\"expression\":\".es(index='logstash-vulnwhisperer-*',q='(risk:critical)').label(\\\"Original\\\"),.es(index='logstash-vulnwhisperer-*',q='(risk:critical)',offset=-1w).label(\\\"One week offset\\\"),.es(index='logstash-vulnwhisperer-*',q='(risk:critical)').subtract(.es(index='logstash-vulnwhisperer-*',q='(risk:critical)',offset=-1w)).label(\\\"Difference\\\").lines(steps=3,fill=2,width=1)\",\"interval\":\"auto\",\"type\":\"timelion\"},\"aggs\":[],\"listeners\":{}}",
|
|
||||||
"uiStateJSON": "{}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "AWCUtHETib22Ai8JwW79",
|
|
||||||
"_type": "visualization",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer - TL-High Risk Qualys Scoring",
|
|
||||||
"visState": "{\"title\":\"VulnWhisperer - TL-High Risk Qualys Scoring\",\"type\":\"timelion\",\"params\":{\"expression\":\".es(index='logstash-vulnwhisperer-*',q='(risk:high)').label(\\\"Original\\\"),.es(index='logstash-vulnwhisperer-*',q='(risk:high)',offset=-1w).label(\\\"One week offset\\\"),.es(index='logstash-vulnwhisperer-*',q='(risk:high)').subtract(.es(index='logstash-vulnwhisperer-*',q='(risk:high)',offset=-1w)).label(\\\"Difference\\\").lines(steps=3,fill=2,width=1)\",\"interval\":\"auto\",\"type\":\"timelion\"},\"aggs\":[],\"listeners\":{}}",
|
|
||||||
"uiStateJSON": "{}",
|
|
||||||
"description": "",
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
@ -1,50 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"_id": "AWCUrIBqib22Ai8JwW43",
|
|
||||||
"_type": "dashboard",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer - Reporting Qualys Scoring",
|
|
||||||
"hits": 0,
|
|
||||||
"description": "",
|
|
||||||
"panelsJSON": "[{\"col\":1,\"id\":\"2f979030-44b9-11e7-a818-f5f80dfc3590\",\"panelIndex\":5,\"row\":11,\"size_x\":6,\"size_y\":4,\"type\":\"visualization\"},{\"col\":10,\"id\":\"297df800-3f7e-11e7-bd24-6903e3283192\",\"panelIndex\":15,\"row\":7,\"size_x\":3,\"size_y\":4,\"type\":\"visualization\"},{\"col\":7,\"id\":\"471a3580-3f6b-11e7-88e7-df1abe6547fb\",\"panelIndex\":20,\"row\":7,\"size_x\":3,\"size_y\":4,\"type\":\"visualization\"},{\"col\":11,\"id\":\"995e2280-3df3-11e7-a44e-c79ca8efb780\",\"panelIndex\":22,\"row\":1,\"size_x\":2,\"size_y\":3,\"type\":\"visualization\"},{\"col\":9,\"id\":\"b2f2adb0-897f-11e7-a2d2-c57bca21b3aa\",\"panelIndex\":23,\"row\":1,\"size_x\":2,\"size_y\":3,\"type\":\"visualization\"},{\"col\":1,\"id\":\"479deab0-8a39-11e7-a58a-9bfcb3761a3d\",\"panelIndex\":29,\"row\":4,\"size_x\":6,\"size_y\":4,\"type\":\"visualization\"},{\"size_x\":6,\"size_y\":3,\"panelIndex\":30,\"type\":\"visualization\",\"id\":\"AWCUtHETib22Ai8JwW79\",\"col\":1,\"row\":8},{\"size_x\":6,\"size_y\":3,\"panelIndex\":31,\"type\":\"visualization\",\"id\":\"AWCUsp_3ib22Ai8JwW7R\",\"col\":7,\"row\":4},{\"size_x\":2,\"size_y\":3,\"panelIndex\":33,\"type\":\"visualization\",\"id\":\"AWCUozGBib22Ai8JwW1B\",\"col\":3,\"row\":1},{\"size_x\":2,\"size_y\":3,\"panelIndex\":34,\"type\":\"visualization\",\"id\":\"AWCUo-jRib22Ai8JwW1N\",\"col\":5,\"row\":1},{\"size_x\":2,\"size_y\":3,\"panelIndex\":35,\"type\":\"visualization\",\"id\":\"AWCUpE3Kib22Ai8JwW1c\",\"col\":7,\"row\":1},{\"size_x\":2,\"size_y\":3,\"panelIndex\":36,\"type\":\"visualization\",\"id\":\"AWCUos-Fib22Ai8JwW0y\",\"col\":1,\"row\":1}]",
|
|
||||||
"optionsJSON": "{\"darkTheme\":false}",
|
|
||||||
"uiStateJSON": "{\"P-15\":{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}},\"P-20\":{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}},\"P-21\":{\"vis\":{\"defaultColors\":{\"0 - 100\":\"rgb(0,104,55)\"}}},\"P-22\":{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}},\"P-23\":{\"vis\":{\"defaultColors\":{\"0 - 10000\":\"rgb(0,104,55)\"}}},\"P-24\":{\"vis\":{\"defaultColors\":{\"0 - 10000\":\"rgb(0,104,55)\"}}},\"P-5\":{\"vis\":{\"legendOpen\":false}},\"P-33\":{\"vis\":{\"defaultColors\":{\"0 - 10000\":\"rgb(0,104,55)\"},\"legendOpen\":false}},\"P-34\":{\"vis\":{\"defaultColors\":{\"0 - 1000\":\"rgb(0,104,55)\"},\"legendOpen\":false}},\"P-35\":{\"vis\":{\"defaultColors\":{\"0 - 10000\":\"rgb(0,104,55)\"}}},\"P-27\":{\"vis\":{\"defaultColors\":{\"0 - 10000\":\"rgb(0,104,55)\"}}},\"P-28\":{\"vis\":{\"defaultColors\":{\"0 - 10000\":\"rgb(0,104,55)\"}}},\"P-26\":{\"vis\":{\"defaultColors\":{\"0 - 1000\":\"rgb(0,104,55)\"}}},\"P-25\":{\"vis\":{\"defaultColors\":{\"0 - 10000\":\"rgb(0,104,55)\"}}},\"P-32\":{\"vis\":{\"defaultColors\":{\"0 - 10000\":\"rgb(0,104,55)\"}}},\"P-36\":{\"vis\":{\"defaultColors\":{\"0 - 10000\":\"rgb(0,104,55)\"},\"legendOpen\":false}}}",
|
|
||||||
"version": 1,
|
|
||||||
"timeRestore": true,
|
|
||||||
"timeTo": "now",
|
|
||||||
"timeFrom": "now-30d",
|
|
||||||
"refreshInterval": {
|
|
||||||
"display": "Off",
|
|
||||||
"pause": false,
|
|
||||||
"value": 0
|
|
||||||
},
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"filter\":[{\"query\":{\"query_string\":{\"analyze_wildcard\":true,\"query\":\"-vulnerability_category:\\\"INFORMATION_GATHERED\\\"\"}}}],\"highlightAll\":true,\"version\":true}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "5dba30c0-3df3-11e7-a44e-c79ca8efb780",
|
|
||||||
"_type": "dashboard",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer - Risk Mitigation Qualys Web Scoring",
|
|
||||||
"hits": 0,
|
|
||||||
"description": "",
|
|
||||||
"panelsJSON": "[{\"col\":11,\"id\":\"995e2280-3df3-11e7-a44e-c79ca8efb780\",\"panelIndex\":20,\"row\":8,\"size_x\":2,\"size_y\":7,\"type\":\"visualization\"},{\"col\":1,\"id\":\"852816e0-3eb1-11e7-90cb-918f9cb01e3d\",\"panelIndex\":21,\"row\":10,\"size_x\":3,\"size_y\":5,\"type\":\"visualization\"},{\"col\":4,\"id\":\"297df800-3f7e-11e7-bd24-6903e3283192\",\"panelIndex\":27,\"row\":8,\"size_x\":3,\"size_y\":4,\"type\":\"visualization\"},{\"col\":9,\"id\":\"35b6d320-3f7f-11e7-bd24-6903e3283192\",\"panelIndex\":28,\"row\":8,\"size_x\":2,\"size_y\":7,\"type\":\"visualization\"},{\"col\":11,\"id\":\"471a3580-3f6b-11e7-88e7-df1abe6547fb\",\"panelIndex\":30,\"row\":1,\"size_x\":2,\"size_y\":3,\"type\":\"visualization\"},{\"col\":7,\"id\":\"de1a5f40-3f85-11e7-97f9-3777d794626d\",\"panelIndex\":31,\"row\":8,\"size_x\":2,\"size_y\":4,\"type\":\"visualization\"},{\"col\":10,\"id\":\"5093c620-44e9-11e7-8014-ede06a7e69f8\",\"panelIndex\":37,\"row\":4,\"size_x\":3,\"size_y\":4,\"type\":\"visualization\"},{\"col\":1,\"columns\":[\"host\",\"risk\",\"risk_score\",\"cve\",\"plugin_name\",\"solution\",\"plugin_output\"],\"id\":\"54648700-3f74-11e7-852e-69207a3d0726\",\"panelIndex\":38,\"row\":15,\"size_x\":12,\"size_y\":6,\"sort\":[\"@timestamp\",\"desc\"],\"type\":\"search\"},{\"col\":1,\"id\":\"fb6eb020-49ab-11e7-8f8c-57ad64ec48a6\",\"panelIndex\":39,\"row\":8,\"size_x\":3,\"size_y\":2,\"type\":\"visualization\"},{\"col\":5,\"id\":\"465c5820-8977-11e7-857e-e1d56b17746d\",\"panelIndex\":40,\"row\":4,\"size_x\":5,\"size_y\":4,\"type\":\"visualization\"},{\"col\":9,\"id\":\"b2f2adb0-897f-11e7-a2d2-c57bca21b3aa\",\"panelIndex\":45,\"row\":1,\"size_x\":2,\"size_y\":3,\"type\":\"visualization\"},{\"col\":1,\"id\":\"AWCUos-Fib22Ai8JwW0y\",\"panelIndex\":47,\"row\":1,\"size_x\":2,\"size_y\":3,\"type\":\"visualization\"},{\"col\":3,\"id\":\"AWCUozGBib22Ai8JwW1B\",\"panelIndex\":48,\"row\":1,\"size_x\":2,\"size_y\":3,\"type\":\"visualization\"},{\"col\":5,\"id\":\"AWCUo-jRib22Ai8JwW1N\",\"panelIndex\":49,\"row\":1,\"size_x\":2,\"size_y\":3,\"type\":\"visualization\"},{\"col\":7,\"id\":\"AWCUpE3Kib22Ai8JwW1c\",\"panelIndex\":50,\"row\":1,\"size_x\":2,\"size_y\":3,\"type\":\"visualization\"},{\"col\":1,\"id\":\"AWCUyeHGib22Ai8JwX62\",\"panelIndex\":51,\"row\":4,\"size_x\":4,\"size_y\":4,\"type\":\"visualization\"},{\"col\":4,\"id\":\"AWCg88f1ib22Ai8Jww3C\",\"panelIndex\":52,\"row\":12,\"size_x\":3,\"size_y\":3,\"type\":\"visualization\"},{\"col\":7,\"id\":\"AWCg9JUAib22Ai8Jww3Y\",\"panelIndex\":53,\"row\":12,\"size_x\":2,\"size_y\":3,\"type\":\"visualization\"}]",
|
|
||||||
"optionsJSON": "{\"darkTheme\":false}",
|
|
||||||
"uiStateJSON": "{\"P-11\":{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}},\"P-2\":{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}},\"P-20\":{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}},\"P-21\":{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":0,\"direction\":\"desc\"}}}},\"P-27\":{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}},\"P-28\":{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":0,\"direction\":\"desc\"}}}},\"P-3\":{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":0,\"direction\":\"asc\"}}}},\"P-30\":{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}},\"P-31\":{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}},\"P-40\":{\"vis\":{\"defaultColors\":{\"0 - 3\":\"rgb(0,104,55)\",\"3 - 7\":\"rgb(135,203,103)\",\"7 - 9\":\"rgb(255,255,190)\",\"9 - 11\":\"rgb(249,142,82)\"}}},\"P-41\":{\"vis\":{\"defaultColors\":{\"0 - 10000\":\"rgb(0,104,55)\"}}},\"P-42\":{\"vis\":{\"defaultColors\":{\"0 - 10000\":\"rgb(0,104,55)\"}}},\"P-43\":{\"vis\":{\"defaultColors\":{\"0 - 1000\":\"rgb(0,104,55)\"}}},\"P-44\":{\"vis\":{\"defaultColors\":{\"0 - 10000\":\"rgb(0,104,55)\"}}},\"P-45\":{\"vis\":{\"defaultColors\":{\"0 - 10000\":\"rgb(0,104,55)\"}}},\"P-47\":{\"vis\":{\"defaultColors\":{\"0 - 10000\":\"rgb(0,104,55)\"},\"legendOpen\":false}},\"P-48\":{\"vis\":{\"defaultColors\":{\"0 - 10000\":\"rgb(0,104,55)\"},\"legendOpen\":false}},\"P-49\":{\"vis\":{\"defaultColors\":{\"0 - 1000\":\"rgb(0,104,55)\"},\"legendOpen\":false}},\"P-5\":{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}},\"P-50\":{\"vis\":{\"defaultColors\":{\"0 - 10000\":\"rgb(0,104,55)\"}}},\"P-6\":{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}},\"P-8\":{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}},\"P-52\":{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}},\"P-53\":{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}}}",
|
|
||||||
"version": 1,
|
|
||||||
"timeRestore": true,
|
|
||||||
"timeTo": "now",
|
|
||||||
"timeFrom": "now-30d",
|
|
||||||
"refreshInterval": {
|
|
||||||
"display": "Off",
|
|
||||||
"pause": false,
|
|
||||||
"value": 0
|
|
||||||
},
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"filter\":[{\"query\":{\"query_string\":{\"analyze_wildcard\":true,\"query\":\"-vulnerability_category:\\\"INFORMATION_GATHERED\\\"\"}}}],\"highlightAll\":true,\"version\":true}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
@ -1,28 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"_id": "54648700-3f74-11e7-852e-69207a3d0726",
|
|
||||||
"_type": "search",
|
|
||||||
"_source": {
|
|
||||||
"title": "VulnWhisperer - Saved Search",
|
|
||||||
"description": "",
|
|
||||||
"hits": 0,
|
|
||||||
"columns": [
|
|
||||||
"host",
|
|
||||||
"risk",
|
|
||||||
"risk_score",
|
|
||||||
"cve",
|
|
||||||
"plugin_name",
|
|
||||||
"solution",
|
|
||||||
"plugin_output"
|
|
||||||
],
|
|
||||||
"sort": [
|
|
||||||
"@timestamp",
|
|
||||||
"desc"
|
|
||||||
],
|
|
||||||
"version": 1,
|
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"searchSourceJSON": "{\"index\":\"logstash-vulnwhisperer-*\",\"query\":{\"query_string\":{\"analyze_wildcard\":true,\"query\":\"*\"}},\"filter\":[],\"highlight\":{\"pre_tags\":[\"@kibana-highlighted-field@\"],\"post_tags\":[\"@/kibana-highlighted-field@\"],\"fields\":{\"*\":{}},\"require_field_match\":false,\"fragment_size\":2147483647}}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
@ -1,14 +0,0 @@
|
|||||||
input {
|
|
||||||
beats {
|
|
||||||
port => 5044
|
|
||||||
tags => "beats"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
filter {
|
|
||||||
if [beat][hostname] == "filebeathost" {
|
|
||||||
mutate {
|
|
||||||
add_tag => ["nessus"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,220 +0,0 @@
|
|||||||
# Author: Austin Taylor and Justin Henderson
|
|
||||||
# Email: email@austintaylor.io
|
|
||||||
# Last Update: 12/20/2017
|
|
||||||
# Version 0.3
|
|
||||||
# Description: Take in nessus reports from vulnWhisperer and pumps into logstash
|
|
||||||
|
|
||||||
|
|
||||||
input {
|
|
||||||
file {
|
|
||||||
path => "/opt/VulnWhisperer/nessus/**/*"
|
|
||||||
start_position => "beginning"
|
|
||||||
tags => "nessus"
|
|
||||||
type => "nessus"
|
|
||||||
}
|
|
||||||
file {
|
|
||||||
path => "/opt/VulnWhisperer/tenable/*.csv"
|
|
||||||
start_position => "beginning"
|
|
||||||
tags => "tenable"
|
|
||||||
type => "tenable"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
filter {
|
|
||||||
if "nessus" in [tags] or "tenable" in [tags] {
|
|
||||||
# Drop the header column
|
|
||||||
if [message] =~ "^Plugin ID" { drop {} }
|
|
||||||
|
|
||||||
csv {
|
|
||||||
# columns => ["plugin_id", "cve", "cvss", "risk", "asset", "protocol", "port", "plugin_name", "synopsis", "description", "solution", "see_also", "plugin_output"]
|
|
||||||
columns => ["plugin_id", "cve", "cvss", "risk", "asset", "protocol", "port", "plugin_name", "synopsis", "description", "solution", "see_also", "plugin_output", "asset_uuid", "vulnerability_state", "ip", "fqdn", "netbios", "operating_system", "mac_address", "plugin_family", "cvss_base", "cvss_temporal", "cvss_temporal_vector", "cvss_vector", "cvss3_base", "cvss3_temporal", "cvss3_temporal_vector", "cvss3_vector", "system_type", "host_start", "host_end"]
|
|
||||||
separator => ","
|
|
||||||
source => "message"
|
|
||||||
}
|
|
||||||
|
|
||||||
ruby {
|
|
||||||
code => "if event.get('description')
|
|
||||||
event.set('description', event.get('description').gsub(92.chr + 'n', 10.chr).gsub(92.chr + 'r', 13.chr))
|
|
||||||
end
|
|
||||||
if event.get('synopsis')
|
|
||||||
event.set('synopsis', event.get('synopsis').gsub(92.chr + 'n', 10.chr).gsub(92.chr + 'r', 13.chr))
|
|
||||||
end
|
|
||||||
if event.get('solution')
|
|
||||||
event.set('solution', event.get('solution').gsub(92.chr + 'n', 10.chr).gsub(92.chr + 'r', 13.chr))
|
|
||||||
end
|
|
||||||
if event.get('see_also')
|
|
||||||
event.set('see_also', event.get('see_also').gsub(92.chr + 'n', 10.chr).gsub(92.chr + 'r', 13.chr))
|
|
||||||
end
|
|
||||||
if event.get('plugin_output')
|
|
||||||
event.set('plugin_output', event.get('plugin_output').gsub(92.chr + 'n', 10.chr).gsub(92.chr + 'r', 13.chr))
|
|
||||||
end"
|
|
||||||
}
|
|
||||||
|
|
||||||
#If using filebeats as your source, you will need to replace the "path" field to "source"
|
|
||||||
grok {
|
|
||||||
match => { "path" => "(?<scan_name>[a-zA-Z0-9_.\-]+)_%{INT:scan_id}_%{INT:history_id}_%{INT:last_updated}.csv$" }
|
|
||||||
tag_on_failure => []
|
|
||||||
}
|
|
||||||
|
|
||||||
date {
|
|
||||||
match => [ "last_updated", "UNIX" ]
|
|
||||||
target => "@timestamp"
|
|
||||||
remove_field => ["last_updated"]
|
|
||||||
}
|
|
||||||
|
|
||||||
if [risk] == "None" {
|
|
||||||
mutate { add_field => { "risk_number" => 0 }}
|
|
||||||
}
|
|
||||||
if [risk] == "Low" {
|
|
||||||
mutate { add_field => { "risk_number" => 1 }}
|
|
||||||
}
|
|
||||||
if [risk] == "Medium" {
|
|
||||||
mutate { add_field => { "risk_number" => 2 }}
|
|
||||||
}
|
|
||||||
if [risk] == "High" {
|
|
||||||
mutate { add_field => { "risk_number" => 3 }}
|
|
||||||
}
|
|
||||||
if [risk] == "Critical" {
|
|
||||||
mutate { add_field => { "risk_number" => 4 }}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ![cve] or [cve] == "nan" {
|
|
||||||
mutate { remove_field => [ "cve" ] }
|
|
||||||
}
|
|
||||||
if ![cvss] or [cvss] == "nan" {
|
|
||||||
mutate { remove_field => [ "cvss" ] }
|
|
||||||
}
|
|
||||||
if ![cvss_base] or [cvss_base] == "nan" {
|
|
||||||
mutate { remove_field => [ "cvss_base" ] }
|
|
||||||
}
|
|
||||||
if ![cvss_temporal] or [cvss_temporal] == "nan" {
|
|
||||||
mutate { remove_field => [ "cvss_temporal" ] }
|
|
||||||
}
|
|
||||||
if ![cvss_temporal_vector] or [cvss_temporal_vector] == "nan" {
|
|
||||||
mutate { remove_field => [ "cvss_temporal_vector" ] }
|
|
||||||
}
|
|
||||||
if ![cvss_vector] or [cvss_vector] == "nan" {
|
|
||||||
mutate { remove_field => [ "cvss_vector" ] }
|
|
||||||
}
|
|
||||||
if ![cvss3_base] or [cvss3_base] == "nan" {
|
|
||||||
mutate { remove_field => [ "cvss3_base" ] }
|
|
||||||
}
|
|
||||||
if ![cvss3_temporal] or [cvss3_temporal] == "nan" {
|
|
||||||
mutate { remove_field => [ "cvss3_temporal" ] }
|
|
||||||
}
|
|
||||||
if ![cvss3_temporal_vector] or [cvss3_temporal_vector] == "nan" {
|
|
||||||
mutate { remove_field => [ "cvss3_temporal_vector" ] }
|
|
||||||
}
|
|
||||||
if ![description] or [description] == "nan" {
|
|
||||||
mutate { remove_field => [ "description" ] }
|
|
||||||
}
|
|
||||||
if ![mac_address] or [mac_address] == "nan" {
|
|
||||||
mutate { remove_field => [ "mac_address" ] }
|
|
||||||
}
|
|
||||||
if ![netbios] or [netbios] == "nan" {
|
|
||||||
mutate { remove_field => [ "netbios" ] }
|
|
||||||
}
|
|
||||||
if ![operating_system] or [operating_system] == "nan" {
|
|
||||||
mutate { remove_field => [ "operating_system" ] }
|
|
||||||
}
|
|
||||||
if ![plugin_output] or [plugin_output] == "nan" {
|
|
||||||
mutate { remove_field => [ "plugin_output" ] }
|
|
||||||
}
|
|
||||||
if ![see_also] or [see_also] == "nan" {
|
|
||||||
mutate { remove_field => [ "see_also" ] }
|
|
||||||
}
|
|
||||||
if ![synopsis] or [synopsis] == "nan" {
|
|
||||||
mutate { remove_field => [ "synopsis" ] }
|
|
||||||
}
|
|
||||||
if ![system_type] or [system_type] == "nan" {
|
|
||||||
mutate { remove_field => [ "system_type" ] }
|
|
||||||
}
|
|
||||||
|
|
||||||
mutate {
|
|
||||||
remove_field => [ "message" ]
|
|
||||||
add_field => { "risk_score" => "%{cvss}" }
|
|
||||||
}
|
|
||||||
mutate {
|
|
||||||
convert => { "risk_score" => "float" }
|
|
||||||
}
|
|
||||||
if [risk_score] == 0 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "info" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [risk_score] > 0 and [risk_score] < 3 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "low" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [risk_score] >= 3 and [risk_score] < 6 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "medium" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [risk_score] >=6 and [risk_score] < 9 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "high" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [risk_score] >= 9 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "critical" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Compensating controls - adjust risk_score
|
|
||||||
# Adobe and Java are not allowed to run in browser unless whitelisted
|
|
||||||
# Therefore, lower score by dividing by 3 (score is subjective to risk)
|
|
||||||
|
|
||||||
#Modify and uncomment when ready to use
|
|
||||||
#if [risk_score] != 0 {
|
|
||||||
# if [plugin_name] =~ "Adobe" and [risk_score] > 6 or [plugin_name] =~ "Java" and [risk_score] > 6 {
|
|
||||||
# ruby {
|
|
||||||
# code => "event.set('risk_score', event.get('risk_score') / 3)"
|
|
||||||
# }
|
|
||||||
# mutate {
|
|
||||||
# add_field => { "compensating_control" => "Adobe and Flash removed from browsers unless whitelisted site." }
|
|
||||||
# }
|
|
||||||
# }
|
|
||||||
#}
|
|
||||||
|
|
||||||
# Add tags for reporting based on assets or criticality
|
|
||||||
|
|
||||||
if [asset] == "dc01" or [asset] == "dc02" or [asset] == "pki01" or [asset] == "192.168.0.54" or [asset] =~ "^192\.168\.0\." or [asset] =~ "^42.42.42." {
|
|
||||||
mutate {
|
|
||||||
add_tag => [ "critical_asset" ]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#if [asset] =~ "^192\.168\.[45][0-9][0-9]\.1$" or [asset] =~ "^192.168\.[50]\.[0-9]{1,2}\.1$"{
|
|
||||||
# mutate {
|
|
||||||
# add_tag => [ "has_hipaa_data" ]
|
|
||||||
# }
|
|
||||||
#}
|
|
||||||
#if [asset] =~ "^192\.168\.[45][0-9][0-9]\." {
|
|
||||||
# mutate {
|
|
||||||
# add_tag => [ "hipaa_asset" ]
|
|
||||||
# }
|
|
||||||
#}
|
|
||||||
if [asset] =~ "^hr" {
|
|
||||||
mutate {
|
|
||||||
add_tag => [ "pci_asset" ]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#if [asset] =~ "^10\.0\.50\." {
|
|
||||||
# mutate {
|
|
||||||
# add_tag => [ "web_servers" ]
|
|
||||||
# }
|
|
||||||
#}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
output {
|
|
||||||
if "nessus" in [tags] or "tenable" in [tags] or [type] in [ "nessus", "tenable" ] {
|
|
||||||
# stdout { codec => rubydebug }
|
|
||||||
elasticsearch {
|
|
||||||
hosts => [ "localhost:9200" ]
|
|
||||||
index => "logstash-vulnwhisperer-%{+YYYY.MM}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,153 +0,0 @@
|
|||||||
# Author: Austin Taylor and Justin Henderson
|
|
||||||
# Email: austin@hasecuritysolutions.com
|
|
||||||
# Last Update: 12/30/2017
|
|
||||||
# Version 0.3
|
|
||||||
# Description: Take in qualys web scan reports from vulnWhisperer and pumps into logstash
|
|
||||||
|
|
||||||
input {
|
|
||||||
file {
|
|
||||||
path => [ "/opt/VulnWhisperer/data/qualys/*.json" , "/opt/VulnWhisperer/data/qualys_web/*.json", "/opt/VulnWhisperer/data/qualys_vuln/*.json" ]
|
|
||||||
type => json
|
|
||||||
codec => json
|
|
||||||
start_position => "beginning"
|
|
||||||
tags => [ "qualys" ]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
filter {
|
|
||||||
if "qualys" in [tags] {
|
|
||||||
grok {
|
|
||||||
match => { "path" => [ "(?<tags>qualys_vuln)_scan_%{DATA}_%{INT:last_updated}.json$", "(?<tags>qualys_web)_%{INT:app_id}_%{INT:last_updated}.json$" ] }
|
|
||||||
tag_on_failure => []
|
|
||||||
}
|
|
||||||
|
|
||||||
mutate {
|
|
||||||
replace => [ "message", "%{message}" ]
|
|
||||||
#gsub => [
|
|
||||||
# "message", "\|\|\|", " ",
|
|
||||||
# "message", "\t\t", " ",
|
|
||||||
# "message", " ", " ",
|
|
||||||
# "message", " ", " ",
|
|
||||||
# "message", " ", " ",
|
|
||||||
# "message", "nan", " ",
|
|
||||||
# "message",'\n',''
|
|
||||||
#]
|
|
||||||
}
|
|
||||||
|
|
||||||
if "qualys_web" in [tags] {
|
|
||||||
mutate {
|
|
||||||
add_field => { "asset" => "%{web_application_name}" }
|
|
||||||
add_field => { "risk_score" => "%{cvss}" }
|
|
||||||
}
|
|
||||||
} else if "qualys_vuln" in [tags] {
|
|
||||||
mutate {
|
|
||||||
add_field => { "asset" => "%{ip}" }
|
|
||||||
add_field => { "risk_score" => "%{cvss}" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if [risk] == "1" {
|
|
||||||
mutate { add_field => { "risk_number" => 0 }}
|
|
||||||
mutate { replace => { "risk" => "info" }}
|
|
||||||
}
|
|
||||||
if [risk] == "2" {
|
|
||||||
mutate { add_field => { "risk_number" => 1 }}
|
|
||||||
mutate { replace => { "risk" => "low" }}
|
|
||||||
}
|
|
||||||
if [risk] == "3" {
|
|
||||||
mutate { add_field => { "risk_number" => 2 }}
|
|
||||||
mutate { replace => { "risk" => "medium" }}
|
|
||||||
}
|
|
||||||
if [risk] == "4" {
|
|
||||||
mutate { add_field => { "risk_number" => 3 }}
|
|
||||||
mutate { replace => { "risk" => "high" }}
|
|
||||||
}
|
|
||||||
if [risk] == "5" {
|
|
||||||
mutate { add_field => { "risk_number" => 4 }}
|
|
||||||
mutate { replace => { "risk" => "critical" }}
|
|
||||||
}
|
|
||||||
|
|
||||||
mutate {
|
|
||||||
remove_field => "message"
|
|
||||||
}
|
|
||||||
|
|
||||||
if [first_time_detected] {
|
|
||||||
date {
|
|
||||||
match => [ "first_time_detected", "dd MMM yyyy HH:mma 'GMT'ZZ", "dd MMM yyyy HH:mma 'GMT'" ]
|
|
||||||
target => "first_time_detected"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [first_time_tested] {
|
|
||||||
date {
|
|
||||||
match => [ "first_time_tested", "dd MMM yyyy HH:mma 'GMT'ZZ", "dd MMM yyyy HH:mma 'GMT'" ]
|
|
||||||
target => "first_time_tested"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [last_time_detected] {
|
|
||||||
date {
|
|
||||||
match => [ "last_time_detected", "dd MMM yyyy HH:mma 'GMT'ZZ", "dd MMM yyyy HH:mma 'GMT'" ]
|
|
||||||
target => "last_time_detected"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [last_time_tested] {
|
|
||||||
date {
|
|
||||||
match => [ "last_time_tested", "dd MMM yyyy HH:mma 'GMT'ZZ", "dd MMM yyyy HH:mma 'GMT'" ]
|
|
||||||
target => "last_time_tested"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
date {
|
|
||||||
match => [ "last_updated", "UNIX" ]
|
|
||||||
target => "@timestamp"
|
|
||||||
remove_field => "last_updated"
|
|
||||||
}
|
|
||||||
mutate {
|
|
||||||
convert => { "plugin_id" => "integer"}
|
|
||||||
convert => { "id" => "integer"}
|
|
||||||
convert => { "risk_number" => "integer"}
|
|
||||||
convert => { "risk_score" => "float"}
|
|
||||||
convert => { "total_times_detected" => "integer"}
|
|
||||||
convert => { "cvss_temporal" => "float"}
|
|
||||||
convert => { "cvss" => "float"}
|
|
||||||
}
|
|
||||||
if [risk_score] == 0 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "info" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [risk_score] > 0 and [risk_score] < 3 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "low" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [risk_score] >= 3 and [risk_score] < 6 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "medium" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [risk_score] >=6 and [risk_score] < 9 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "high" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [risk_score] >= 9 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "critical" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if [asset] =~ "\.yourdomain\.(com|net)$" {
|
|
||||||
mutate {
|
|
||||||
add_tag => [ "critical_asset" ]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
output {
|
|
||||||
if "qualys" in [tags] {
|
|
||||||
stdout { codec => rubydebug }
|
|
||||||
elasticsearch {
|
|
||||||
hosts => [ "localhost:9200" ]
|
|
||||||
index => "logstash-vulnwhisperer-%{+YYYY.MM}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,146 +0,0 @@
|
|||||||
# Author: Austin Taylor and Justin Henderson
|
|
||||||
# Email: austin@hasecuritysolutions.com
|
|
||||||
# Last Update: 03/04/2018
|
|
||||||
# Version 0.3
|
|
||||||
# Description: Take in qualys web scan reports from vulnWhisperer and pumps into logstash
|
|
||||||
|
|
||||||
input {
|
|
||||||
file {
|
|
||||||
path => "/opt/VulnWhisperer/openvas/*.json"
|
|
||||||
type => json
|
|
||||||
codec => json
|
|
||||||
start_position => "beginning"
|
|
||||||
tags => [ "openvas_scan", "openvas" ]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
filter {
|
|
||||||
if "openvas_scan" in [tags] {
|
|
||||||
mutate {
|
|
||||||
replace => [ "message", "%{message}" ]
|
|
||||||
gsub => [
|
|
||||||
"message", "\|\|\|", " ",
|
|
||||||
"message", "\t\t", " ",
|
|
||||||
"message", " ", " ",
|
|
||||||
"message", " ", " ",
|
|
||||||
"message", " ", " ",
|
|
||||||
"message", "nan", " ",
|
|
||||||
"message",'\n',''
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
grok {
|
|
||||||
match => { "path" => "openvas_scan_%{DATA:scan_id}_%{INT:last_updated}.json$" }
|
|
||||||
tag_on_failure => []
|
|
||||||
}
|
|
||||||
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score" => "%{cvss}" }
|
|
||||||
}
|
|
||||||
|
|
||||||
if [risk] == "1" {
|
|
||||||
mutate { add_field => { "risk_number" => 0 }}
|
|
||||||
mutate { replace => { "risk" => "info" }}
|
|
||||||
}
|
|
||||||
if [risk] == "2" {
|
|
||||||
mutate { add_field => { "risk_number" => 1 }}
|
|
||||||
mutate { replace => { "risk" => "low" }}
|
|
||||||
}
|
|
||||||
if [risk] == "3" {
|
|
||||||
mutate { add_field => { "risk_number" => 2 }}
|
|
||||||
mutate { replace => { "risk" => "medium" }}
|
|
||||||
}
|
|
||||||
if [risk] == "4" {
|
|
||||||
mutate { add_field => { "risk_number" => 3 }}
|
|
||||||
mutate { replace => { "risk" => "high" }}
|
|
||||||
}
|
|
||||||
if [risk] == "5" {
|
|
||||||
mutate { add_field => { "risk_number" => 4 }}
|
|
||||||
mutate { replace => { "risk" => "critical" }}
|
|
||||||
}
|
|
||||||
|
|
||||||
mutate {
|
|
||||||
remove_field => "message"
|
|
||||||
}
|
|
||||||
|
|
||||||
if [first_time_detected] {
|
|
||||||
date {
|
|
||||||
match => [ "first_time_detected", "dd MMM yyyy HH:mma 'GMT'ZZ", "dd MMM yyyy HH:mma 'GMT'" ]
|
|
||||||
target => "first_time_detected"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [first_time_tested] {
|
|
||||||
date {
|
|
||||||
match => [ "first_time_tested", "dd MMM yyyy HH:mma 'GMT'ZZ", "dd MMM yyyy HH:mma 'GMT'" ]
|
|
||||||
target => "first_time_tested"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [last_time_detected] {
|
|
||||||
date {
|
|
||||||
match => [ "last_time_detected", "dd MMM yyyy HH:mma 'GMT'ZZ", "dd MMM yyyy HH:mma 'GMT'" ]
|
|
||||||
target => "last_time_detected"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [last_time_tested] {
|
|
||||||
date {
|
|
||||||
match => [ "last_time_tested", "dd MMM yyyy HH:mma 'GMT'ZZ", "dd MMM yyyy HH:mma 'GMT'" ]
|
|
||||||
target => "last_time_tested"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
date {
|
|
||||||
match => [ "last_updated", "UNIX" ]
|
|
||||||
target => "@timestamp"
|
|
||||||
remove_field => "last_updated"
|
|
||||||
}
|
|
||||||
mutate {
|
|
||||||
convert => { "plugin_id" => "integer"}
|
|
||||||
convert => { "id" => "integer"}
|
|
||||||
convert => { "risk_number" => "integer"}
|
|
||||||
convert => { "risk_score" => "float"}
|
|
||||||
convert => { "total_times_detected" => "integer"}
|
|
||||||
convert => { "cvss_temporal" => "float"}
|
|
||||||
convert => { "cvss" => "float"}
|
|
||||||
}
|
|
||||||
if [risk_score] == 0 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "info" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [risk_score] > 0 and [risk_score] < 3 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "low" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [risk_score] >= 3 and [risk_score] < 6 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "medium" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [risk_score] >=6 and [risk_score] < 9 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "high" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [risk_score] >= 9 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "critical" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
# Add your critical assets by subnet or by hostname. Comment this field out if you don't want to tag any, but the asset panel will break.
|
|
||||||
if [asset] =~ "^10\.0\.100\." {
|
|
||||||
mutate {
|
|
||||||
add_tag => [ "critical_asset" ]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
output {
|
|
||||||
if "openvas" in [tags] {
|
|
||||||
stdout { codec => rubydebug }
|
|
||||||
elasticsearch {
|
|
||||||
hosts => [ "localhost:9200" ]
|
|
||||||
index => "logstash-vulnwhisperer-%{+YYYY.MM}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
# Description: Take in jira tickets from vulnWhisperer and pumps into logstash
|
|
||||||
|
|
||||||
input {
|
|
||||||
file {
|
|
||||||
path => "/opt/VulnWhisperer/jira/*.json"
|
|
||||||
type => json
|
|
||||||
codec => json
|
|
||||||
start_position => "beginning"
|
|
||||||
tags => [ "jira" ]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
output {
|
|
||||||
if "jira" in [tags] {
|
|
||||||
stdout { codec => rubydebug }
|
|
||||||
elasticsearch {
|
|
||||||
hosts => [ "localhost:9200" ]
|
|
||||||
index => "logstash-vulnwhisperer-%{+YYYY.MM}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
input {
|
|
||||||
rabbitmq {
|
|
||||||
key => "nessus"
|
|
||||||
queue => "nessus"
|
|
||||||
durable => true
|
|
||||||
exchange => "nessus"
|
|
||||||
user => "logstash"
|
|
||||||
password => "yourpassword"
|
|
||||||
host => "buffer01"
|
|
||||||
port => 5672
|
|
||||||
tags => [ "queue_nessus", "rabbitmq" ]
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
output {
|
|
||||||
if "nessus" in [tags]{
|
|
||||||
rabbitmq {
|
|
||||||
key => "nessus"
|
|
||||||
exchange => "nessus"
|
|
||||||
exchange_type => "direct"
|
|
||||||
user => "logstash"
|
|
||||||
password => "yourbufferpassword"
|
|
||||||
host => "buffer01"
|
|
||||||
port => 5672
|
|
||||||
durable => true
|
|
||||||
persistent => true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
15
resources/elk6/get-kibana-objects.py
Normal file
15
resources/elk6/get-kibana-objects.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import json
|
||||||
|
import requests
|
||||||
|
|
||||||
|
api_objects = []
|
||||||
|
|
||||||
|
for object_type in ['dashboard', 'visualization', 'search', 'index-pattern', 'timelion-sheet']:
|
||||||
|
r = requests.get('http://localhost:5601/api/saved_objects/_find?per_page=500&type={}'.format(object_type)).json()
|
||||||
|
api_objects += r['saved_objects']
|
||||||
|
print object_type, len(r['saved_objects'])
|
||||||
|
print len(api_objects)
|
||||||
|
|
||||||
|
for api_object in api_objects:
|
||||||
|
api_object.pop('updated_at', None)
|
||||||
|
|
||||||
|
json.dump(sorted(api_objects, key=lambda x:x['id']), open('kibana_APIonly.json', 'w'), indent=2)
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"index_patterns": "logstash-vulnwhisperer-*",
|
"index_patterns": "logstash-vulnwhisperer-*",
|
||||||
|
"version": 2019041701,
|
||||||
"mappings": {
|
"mappings": {
|
||||||
"doc": {
|
"doc": {
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -22,34 +23,34 @@
|
|||||||
"asset_uuid": {
|
"asset_uuid": {
|
||||||
"type": "keyword"
|
"type": "keyword"
|
||||||
},
|
},
|
||||||
"assign_ip": {
|
|
||||||
"type": "ip"
|
|
||||||
},
|
|
||||||
"category": {
|
"category": {
|
||||||
"type": "keyword"
|
"type": "keyword"
|
||||||
},
|
},
|
||||||
"cve": {
|
"cve": {
|
||||||
"type": "keyword"
|
"type": "keyword"
|
||||||
},
|
},
|
||||||
"cvss_base": {
|
|
||||||
"type": "float"
|
|
||||||
},
|
|
||||||
"cvss_temporal_vector": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"cvss_temporal": {
|
|
||||||
"type": "float"
|
|
||||||
},
|
|
||||||
"cvss_vector": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"cvss": {
|
"cvss": {
|
||||||
"type": "float"
|
"type": "float"
|
||||||
},
|
},
|
||||||
|
"cvss_severity": {
|
||||||
|
"type": "keyword"
|
||||||
|
},
|
||||||
|
"cvss2_base": {
|
||||||
|
"type": "float"
|
||||||
|
},
|
||||||
|
"cvss2_severity": {
|
||||||
|
"type": "keyword"
|
||||||
|
},
|
||||||
|
"cvss2_temporal": {
|
||||||
|
"type": "float"
|
||||||
|
},
|
||||||
|
"cvss2_vector": {
|
||||||
|
"type": "keyword"
|
||||||
|
},
|
||||||
"cvss3_base": {
|
"cvss3_base": {
|
||||||
"type": "float"
|
"type": "float"
|
||||||
},
|
},
|
||||||
"cvss3_temporal_vector": {
|
"cvss3_severity": {
|
||||||
"type": "keyword"
|
"type": "keyword"
|
||||||
},
|
},
|
||||||
"cvss3_temporal": {
|
"cvss3_temporal": {
|
||||||
@ -117,24 +118,14 @@
|
|||||||
"host_start": {
|
"host_start": {
|
||||||
"type": "date"
|
"type": "date"
|
||||||
},
|
},
|
||||||
"impact": {
|
|
||||||
"fields": {
|
|
||||||
"keyword": {
|
|
||||||
"ignore_above": 256,
|
|
||||||
"type": "keyword"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"norms": false,
|
|
||||||
"type": "text"
|
|
||||||
},
|
|
||||||
"ip_status": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"ip": {
|
"ip": {
|
||||||
"type": "ip"
|
"type": "ip"
|
||||||
},
|
},
|
||||||
"last_updated": {
|
"mac_address": {
|
||||||
"type": "date"
|
"type": "keyword"
|
||||||
|
},
|
||||||
|
"netbios": {
|
||||||
|
"type": "keyword"
|
||||||
},
|
},
|
||||||
"operating_system": {
|
"operating_system": {
|
||||||
"type": "keyword"
|
"type": "keyword"
|
||||||
@ -148,10 +139,10 @@
|
|||||||
"plugin_family": {
|
"plugin_family": {
|
||||||
"type": "keyword"
|
"type": "keyword"
|
||||||
},
|
},
|
||||||
"plugin_id": {
|
"signature_id": {
|
||||||
"type": "keyword"
|
"type": "keyword"
|
||||||
},
|
},
|
||||||
"plugin_name": {
|
"signature": {
|
||||||
"type": "keyword"
|
"type": "keyword"
|
||||||
},
|
},
|
||||||
"plugin_output": {
|
"plugin_output": {
|
||||||
@ -170,18 +161,9 @@
|
|||||||
"protocol": {
|
"protocol": {
|
||||||
"type": "keyword"
|
"type": "keyword"
|
||||||
},
|
},
|
||||||
"results": {
|
|
||||||
"type": "text"
|
|
||||||
},
|
|
||||||
"risk_number": {
|
"risk_number": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"risk_score_name": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"risk_score": {
|
|
||||||
"type": "float"
|
|
||||||
},
|
|
||||||
"risk": {
|
"risk": {
|
||||||
"type": "keyword"
|
"type": "keyword"
|
||||||
},
|
},
|
||||||
@ -189,43 +171,41 @@
|
|||||||
"type": "keyword"
|
"type": "keyword"
|
||||||
},
|
},
|
||||||
"scan_name": {
|
"scan_name": {
|
||||||
|
"fields": {
|
||||||
|
"keyword": {
|
||||||
|
"ignore_above": 256,
|
||||||
|
"type": "keyword"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"norms": false,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"scan_source": {
|
||||||
"type": "keyword"
|
"type": "keyword"
|
||||||
},
|
},
|
||||||
"scan_reference": {
|
"severity": {
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"see_also": {
|
|
||||||
"type": "keyword"
|
"type": "keyword"
|
||||||
},
|
},
|
||||||
"solution": {
|
"solution": {
|
||||||
"type": "keyword"
|
"type": "keyword"
|
||||||
},
|
},
|
||||||
"source": {
|
"ssl": {
|
||||||
"type": "keyword"
|
"type": "keyword"
|
||||||
},
|
},
|
||||||
"ssl": {
|
"state": {
|
||||||
"type": "keyword"
|
"type": "keyword"
|
||||||
},
|
},
|
||||||
"synopsis": {
|
"synopsis": {
|
||||||
"type": "keyword"
|
"type": "keyword"
|
||||||
},
|
},
|
||||||
"system_type": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"tags": {
|
"tags": {
|
||||||
"type": "keyword"
|
"type": "keyword"
|
||||||
},
|
},
|
||||||
"threat": {
|
|
||||||
"type": "text"
|
|
||||||
},
|
|
||||||
"type": {
|
"type": {
|
||||||
"type": "keyword"
|
"type": "keyword"
|
||||||
},
|
},
|
||||||
"vendor_reference": {
|
"vendor_reference": {
|
||||||
"type": "keyword"
|
"type": "keyword"
|
||||||
},
|
|
||||||
"vulnerability_state": {
|
|
||||||
"type": "keyword"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,231 +0,0 @@
|
|||||||
{
|
|
||||||
"index_patterns": "logstash-vulnwhisperer-*",
|
|
||||||
"mappings": {
|
|
||||||
"properties": {
|
|
||||||
"@timestamp": {
|
|
||||||
"type": "date"
|
|
||||||
},
|
|
||||||
"@version": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"asset": {
|
|
||||||
"type": "text",
|
|
||||||
"norms": false,
|
|
||||||
"fields": {
|
|
||||||
"keyword": {
|
|
||||||
"type": "keyword",
|
|
||||||
"ignore_above": 256
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"asset_uuid": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"assign_ip": {
|
|
||||||
"type": "ip"
|
|
||||||
},
|
|
||||||
"category": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"cve": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"cvss_base": {
|
|
||||||
"type": "float"
|
|
||||||
},
|
|
||||||
"cvss_temporal_vector": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"cvss_temporal": {
|
|
||||||
"type": "float"
|
|
||||||
},
|
|
||||||
"cvss_vector": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"cvss": {
|
|
||||||
"type": "float"
|
|
||||||
},
|
|
||||||
"cvss3_base": {
|
|
||||||
"type": "float"
|
|
||||||
},
|
|
||||||
"cvss3_temporal_vector": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"cvss3_temporal": {
|
|
||||||
"type": "float"
|
|
||||||
},
|
|
||||||
"cvss3_vector": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"cvss3": {
|
|
||||||
"type": "float"
|
|
||||||
},
|
|
||||||
"description": {
|
|
||||||
"fields": {
|
|
||||||
"keyword": {
|
|
||||||
"ignore_above": 256,
|
|
||||||
"type": "keyword"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"norms": false,
|
|
||||||
"type": "text"
|
|
||||||
},
|
|
||||||
"dns": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"exploitability": {
|
|
||||||
"fields": {
|
|
||||||
"keyword": {
|
|
||||||
"ignore_above": 256,
|
|
||||||
"type": "keyword"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"norms": false,
|
|
||||||
"type": "text"
|
|
||||||
},
|
|
||||||
"fqdn": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"geoip": {
|
|
||||||
"dynamic": true,
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"ip": {
|
|
||||||
"type": "ip"
|
|
||||||
},
|
|
||||||
"latitude": {
|
|
||||||
"type": "float"
|
|
||||||
},
|
|
||||||
"location": {
|
|
||||||
"type": "geo_point"
|
|
||||||
},
|
|
||||||
"longitude": {
|
|
||||||
"type": "float"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"history_id": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"host": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"host_end": {
|
|
||||||
"type": "date"
|
|
||||||
},
|
|
||||||
"host_start": {
|
|
||||||
"type": "date"
|
|
||||||
},
|
|
||||||
"impact": {
|
|
||||||
"fields": {
|
|
||||||
"keyword": {
|
|
||||||
"ignore_above": 256,
|
|
||||||
"type": "keyword"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"norms": false,
|
|
||||||
"type": "text"
|
|
||||||
},
|
|
||||||
"ip_status": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"ip": {
|
|
||||||
"type": "ip"
|
|
||||||
},
|
|
||||||
"last_updated": {
|
|
||||||
"type": "date"
|
|
||||||
},
|
|
||||||
"operating_system": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"path": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"pci_vuln": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"plugin_family": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"plugin_id": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"plugin_name": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"plugin_output": {
|
|
||||||
"fields": {
|
|
||||||
"keyword": {
|
|
||||||
"ignore_above": 256,
|
|
||||||
"type": "keyword"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"norms": false,
|
|
||||||
"type": "text"
|
|
||||||
},
|
|
||||||
"port": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"protocol": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"results": {
|
|
||||||
"type": "text"
|
|
||||||
},
|
|
||||||
"risk_number": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"risk_score_name": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"risk_score": {
|
|
||||||
"type": "float"
|
|
||||||
},
|
|
||||||
"risk": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"scan_id": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"scan_name": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"scan_reference": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"see_also": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"solution": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"source": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"ssl": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"synopsis": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"system_type": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"tags": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"threat": {
|
|
||||||
"type": "text"
|
|
||||||
},
|
|
||||||
"type": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"vendor_reference": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"vulnerability_state": {
|
|
||||||
"type": "keyword"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
110
resources/elk6/pipeline/1000_combined.conf
Normal file
110
resources/elk6/pipeline/1000_combined.conf
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
input {
|
||||||
|
file {
|
||||||
|
codec => json
|
||||||
|
mode => "read"
|
||||||
|
path => ["/opt/VulnWhisperer/data/nessus/**/*.json", "/opt/VulnWhisperer/data/openvas/*.json", "/opt/VulnWhisperer/data/qualys_vm/*.json", "/opt/VulnWhisperer/data/qualys_was/*.json", "/opt/VulnWhisperer/data/tenable/*.json"]
|
||||||
|
start_position => "beginning"
|
||||||
|
file_completed_action => "delete"
|
||||||
|
file_chunk_size => 262144
|
||||||
|
}
|
||||||
|
file {
|
||||||
|
codec => json
|
||||||
|
mode => "read"
|
||||||
|
path => "/opt/VulnWhisperer/data/jira/*.json"
|
||||||
|
tags => [ "jira" ]
|
||||||
|
start_position => "beginning"
|
||||||
|
file_completed_action => "delete"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filter {
|
||||||
|
if [scan_source] in ["nessus", "tenable", "qualys_vm", "qualys_was", "openvas"] {
|
||||||
|
|
||||||
|
# Parse the date/time from scan_time
|
||||||
|
date {
|
||||||
|
match => [ "scan_time", "UNIX" ]
|
||||||
|
target => "@timestamp"
|
||||||
|
remove_field => ["scan_time"]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add scan_source to tags
|
||||||
|
mutate {
|
||||||
|
add_field => { "[tags]" => "%{scan_source}" }
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create a unique document_id if _unique field exists
|
||||||
|
if [_unique] {
|
||||||
|
# Set document ID from _unique
|
||||||
|
mutate {
|
||||||
|
rename => { "_unique" => "[@metadata][id]" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Do we need this?
|
||||||
|
mutate {
|
||||||
|
convert => { "cvss" => "float"}
|
||||||
|
convert => { "cvss2" => "float"}
|
||||||
|
convert => { "cvss2_base" => "float"}
|
||||||
|
convert => { "cvss2_temporal" => "float"}
|
||||||
|
convert => { "cvss3" => "float"}
|
||||||
|
convert => { "cvss3_base" => "float"}
|
||||||
|
convert => { "cvss3_temporal" => "float"}
|
||||||
|
convert => { "risk_number" => "integer"}
|
||||||
|
convert => { "total_times_detected" => "integer"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if [scan_source] == "qualys_was" {
|
||||||
|
if [first_time_detected] {
|
||||||
|
date {
|
||||||
|
match => [ "first_time_detected", "dd MMM yyyy HH:mma 'GMT'ZZ", "dd MMM yyyy HH:mma 'GMT'" ]
|
||||||
|
target => "first_time_detected"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if [first_time_tested] {
|
||||||
|
date {
|
||||||
|
match => [ "first_time_tested", "dd MMM yyyy HH:mma 'GMT'ZZ", "dd MMM yyyy HH:mma 'GMT'" ]
|
||||||
|
target => "first_time_tested"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if [last_time_detected] {
|
||||||
|
date {
|
||||||
|
match => [ "last_time_detected", "dd MMM yyyy HH:mma 'GMT'ZZ", "dd MMM yyyy HH:mma 'GMT'" ]
|
||||||
|
target => "last_time_detected"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if [last_time_tested] {
|
||||||
|
date {
|
||||||
|
match => [ "last_time_tested", "dd MMM yyyy HH:mma 'GMT'ZZ", "dd MMM yyyy HH:mma 'GMT'" ]
|
||||||
|
target => "last_time_tested"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output {
|
||||||
|
if [scan_source] in ["nessus", "tenable", "qualys_vm", "qualys_was", "openvas"] {
|
||||||
|
if [@metadata][id] {
|
||||||
|
elasticsearch {
|
||||||
|
hosts => [ "elasticsearch:9200" ]
|
||||||
|
index => "logstash-vulnwhisperer-%{+YYYY.MM}"
|
||||||
|
document_id => "%{[@metadata][id]}"
|
||||||
|
manage_template => false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
elasticsearch {
|
||||||
|
hosts => [ "elasticsearch:9200" ]
|
||||||
|
index => "logstash-vulnwhisperer-%{+YYYY.MM}"
|
||||||
|
manage_template => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# Should these go to the same index?
|
||||||
|
if "jira" in [tags] {
|
||||||
|
stdout { codec => rubydebug }
|
||||||
|
elasticsearch {
|
||||||
|
hosts => [ "elasticsearch:9200" ]
|
||||||
|
index => "logstash-vulnwhisperer-%{+YYYY.MM}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,182 +0,0 @@
|
|||||||
# Author: Austin Taylor and Justin Henderson
|
|
||||||
# Email: email@austintaylor.io
|
|
||||||
# Last Update: 12/20/2017
|
|
||||||
# Version 0.3
|
|
||||||
# Description: Take in nessus reports from vulnWhisperer and pumps into logstash
|
|
||||||
|
|
||||||
|
|
||||||
input {
|
|
||||||
file {
|
|
||||||
path => "/opt/VulnWhisperer/data/nessus/**/*"
|
|
||||||
mode => "read"
|
|
||||||
start_position => "beginning"
|
|
||||||
file_completed_action => "delete"
|
|
||||||
tags => "nessus"
|
|
||||||
}
|
|
||||||
file {
|
|
||||||
path => "/opt/VulnWhisperer/data/tenable/*.csv"
|
|
||||||
mode => "read"
|
|
||||||
start_position => "beginning"
|
|
||||||
file_completed_action => "delete"
|
|
||||||
tags => "tenable"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
filter {
|
|
||||||
if "nessus" in [tags] or "tenable" in [tags] {
|
|
||||||
# Drop the header column
|
|
||||||
if [message] =~ "^Plugin ID" { drop {} }
|
|
||||||
|
|
||||||
csv {
|
|
||||||
# columns => ["plugin_id", "cve", "cvss", "risk", "asset", "protocol", "port", "plugin_name", "synopsis", "description", "solution", "see_also", "plugin_output"]
|
|
||||||
columns => ["plugin_id", "cve", "cvss", "risk", "asset", "protocol", "port", "plugin_name", "synopsis", "description", "solution", "see_also", "plugin_output", "asset_uuid", "vulnerability_state", "ip", "fqdn", "netbios", "operating_system", "mac_address", "plugin_family", "cvss_base", "cvss_temporal", "cvss_temporal_vector", "cvss_vector", "cvss3_base", "cvss3_temporal", "cvss3_temporal_vector", "cvss3_vector", "system_type", "host_start", "host_end"]
|
|
||||||
separator => ","
|
|
||||||
source => "message"
|
|
||||||
}
|
|
||||||
|
|
||||||
ruby {
|
|
||||||
code => "if event.get('description')
|
|
||||||
event.set('description', event.get('description').gsub(92.chr + 'n', 10.chr).gsub(92.chr + 'r', 13.chr))
|
|
||||||
end
|
|
||||||
if event.get('synopsis')
|
|
||||||
event.set('synopsis', event.get('synopsis').gsub(92.chr + 'n', 10.chr).gsub(92.chr + 'r', 13.chr))
|
|
||||||
end
|
|
||||||
if event.get('solution')
|
|
||||||
event.set('solution', event.get('solution').gsub(92.chr + 'n', 10.chr).gsub(92.chr + 'r', 13.chr))
|
|
||||||
end
|
|
||||||
if event.get('see_also')
|
|
||||||
event.set('see_also', event.get('see_also').gsub(92.chr + 'n', 10.chr).gsub(92.chr + 'r', 13.chr))
|
|
||||||
end
|
|
||||||
if event.get('plugin_output')
|
|
||||||
event.set('plugin_output', event.get('plugin_output').gsub(92.chr + 'n', 10.chr).gsub(92.chr + 'r', 13.chr))
|
|
||||||
end"
|
|
||||||
}
|
|
||||||
|
|
||||||
#If using filebeats as your source, you will need to replace the "path" field to "source"
|
|
||||||
# Remove when scan name is included in event (current method is error prone)
|
|
||||||
grok {
|
|
||||||
match => { "path" => "(?<scan_name>[a-zA-Z0-9_.\-]+)_%{INT:scan_id}_%{INT:history_id}_%{INT:last_updated}.csv$" }
|
|
||||||
tag_on_failure => []
|
|
||||||
}
|
|
||||||
|
|
||||||
# TODO remove when @timestamp is included in event
|
|
||||||
date {
|
|
||||||
match => [ "last_updated", "UNIX" ]
|
|
||||||
target => "@timestamp"
|
|
||||||
remove_field => ["last_updated"]
|
|
||||||
}
|
|
||||||
|
|
||||||
if [risk] == "None" {
|
|
||||||
mutate { add_field => { "risk_number" => 0 }}
|
|
||||||
}
|
|
||||||
if [risk] == "Low" {
|
|
||||||
mutate { add_field => { "risk_number" => 1 }}
|
|
||||||
}
|
|
||||||
if [risk] == "Medium" {
|
|
||||||
mutate { add_field => { "risk_number" => 2 }}
|
|
||||||
}
|
|
||||||
if [risk] == "High" {
|
|
||||||
mutate { add_field => { "risk_number" => 3 }}
|
|
||||||
}
|
|
||||||
if [risk] == "Critical" {
|
|
||||||
mutate { add_field => { "risk_number" => 4 }}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ![cve] or [cve] == "nan" {
|
|
||||||
mutate { remove_field => [ "cve" ] }
|
|
||||||
}
|
|
||||||
if ![cvss] or [cvss] == "nan" {
|
|
||||||
mutate { remove_field => [ "cvss" ] }
|
|
||||||
}
|
|
||||||
if ![cvss_base] or [cvss_base] == "nan" {
|
|
||||||
mutate { remove_field => [ "cvss_base" ] }
|
|
||||||
}
|
|
||||||
if ![cvss_temporal] or [cvss_temporal] == "nan" {
|
|
||||||
mutate { remove_field => [ "cvss_temporal" ] }
|
|
||||||
}
|
|
||||||
if ![cvss_temporal_vector] or [cvss_temporal_vector] == "nan" {
|
|
||||||
mutate { remove_field => [ "cvss_temporal_vector" ] }
|
|
||||||
}
|
|
||||||
if ![cvss_vector] or [cvss_vector] == "nan" {
|
|
||||||
mutate { remove_field => [ "cvss_vector" ] }
|
|
||||||
}
|
|
||||||
if ![cvss3_base] or [cvss3_base] == "nan" {
|
|
||||||
mutate { remove_field => [ "cvss3_base" ] }
|
|
||||||
}
|
|
||||||
if ![cvss3_temporal] or [cvss3_temporal] == "nan" {
|
|
||||||
mutate { remove_field => [ "cvss3_temporal" ] }
|
|
||||||
}
|
|
||||||
if ![cvss3_temporal_vector] or [cvss3_temporal_vector] == "nan" {
|
|
||||||
mutate { remove_field => [ "cvss3_temporal_vector" ] }
|
|
||||||
}
|
|
||||||
if ![description] or [description] == "nan" {
|
|
||||||
mutate { remove_field => [ "description" ] }
|
|
||||||
}
|
|
||||||
if ![mac_address] or [mac_address] == "nan" {
|
|
||||||
mutate { remove_field => [ "mac_address" ] }
|
|
||||||
}
|
|
||||||
if ![netbios] or [netbios] == "nan" {
|
|
||||||
mutate { remove_field => [ "netbios" ] }
|
|
||||||
}
|
|
||||||
if ![operating_system] or [operating_system] == "nan" {
|
|
||||||
mutate { remove_field => [ "operating_system" ] }
|
|
||||||
}
|
|
||||||
if ![plugin_output] or [plugin_output] == "nan" {
|
|
||||||
mutate { remove_field => [ "plugin_output" ] }
|
|
||||||
}
|
|
||||||
if ![see_also] or [see_also] == "nan" {
|
|
||||||
mutate { remove_field => [ "see_also" ] }
|
|
||||||
}
|
|
||||||
if ![synopsis] or [synopsis] == "nan" {
|
|
||||||
mutate { remove_field => [ "synopsis" ] }
|
|
||||||
}
|
|
||||||
if ![system_type] or [system_type] == "nan" {
|
|
||||||
mutate { remove_field => [ "system_type" ] }
|
|
||||||
}
|
|
||||||
|
|
||||||
mutate {
|
|
||||||
remove_field => [ "message" ]
|
|
||||||
add_field => { "risk_score" => "%{cvss}" }
|
|
||||||
}
|
|
||||||
mutate {
|
|
||||||
convert => { "risk_score" => "float" }
|
|
||||||
}
|
|
||||||
if [risk_score] == 0 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "info" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [risk_score] > 0 and [risk_score] < 3 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "low" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [risk_score] >= 3 and [risk_score] < 6 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "medium" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [risk_score] >=6 and [risk_score] < 9 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "high" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [risk_score] >= 9 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "critical" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
output {
|
|
||||||
if "nessus" in [tags] or "tenable" in [tags]{
|
|
||||||
stdout {
|
|
||||||
codec => dots
|
|
||||||
}
|
|
||||||
elasticsearch {
|
|
||||||
hosts => [ "elasticsearch:9200" ]
|
|
||||||
index => "logstash-vulnwhisperer-%{+YYYY.MM}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,160 +0,0 @@
|
|||||||
# Author: Austin Taylor and Justin Henderson
|
|
||||||
# Email: austin@hasecuritysolutions.com
|
|
||||||
# Last Update: 12/30/2017
|
|
||||||
# Version 0.3
|
|
||||||
# Description: Take in qualys web scan reports from vulnWhisperer and pumps into logstash
|
|
||||||
|
|
||||||
input {
|
|
||||||
file {
|
|
||||||
path => [ "/opt/VulnWhisperer/data/qualys/*.json" , "/opt/VulnWhisperer/data/qualys_web/*.json", "/opt/VulnWhisperer/data/qualys_vuln/*.json"]
|
|
||||||
type => json
|
|
||||||
codec => json
|
|
||||||
start_position => "beginning"
|
|
||||||
tags => [ "qualys" ]
|
|
||||||
mode => "read"
|
|
||||||
start_position => "beginning"
|
|
||||||
file_completed_action => "delete"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
filter {
|
|
||||||
if "qualys" in [tags] {
|
|
||||||
grok {
|
|
||||||
match => { "path" => [ "(?<tags>qualys_vuln)_scan_%{DATA}_%{INT:last_updated}.json$", "(?<tags>qualys_web)_%{INT:app_id}_%{INT:last_updated}.json$" ] }
|
|
||||||
tag_on_failure => []
|
|
||||||
}
|
|
||||||
|
|
||||||
mutate {
|
|
||||||
replace => [ "message", "%{message}" ]
|
|
||||||
#gsub => [
|
|
||||||
# "message", "\|\|\|", " ",
|
|
||||||
# "message", "\t\t", " ",
|
|
||||||
# "message", " ", " ",
|
|
||||||
# "message", " ", " ",
|
|
||||||
# "message", " ", " ",
|
|
||||||
# "message", "nan", " ",
|
|
||||||
# "message",'\n',''
|
|
||||||
#]
|
|
||||||
}
|
|
||||||
|
|
||||||
if "qualys_web" in [tags] {
|
|
||||||
mutate {
|
|
||||||
add_field => { "asset" => "%{web_application_name}" }
|
|
||||||
add_field => { "risk_score" => "%{cvss}" }
|
|
||||||
}
|
|
||||||
} else if "qualys_vuln" in [tags] {
|
|
||||||
mutate {
|
|
||||||
add_field => { "asset" => "%{ip}" }
|
|
||||||
add_field => { "risk_score" => "%{cvss}" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if [risk] == "1" {
|
|
||||||
mutate { add_field => { "risk_number" => 0 }}
|
|
||||||
mutate { replace => { "risk" => "info" }}
|
|
||||||
}
|
|
||||||
if [risk] == "2" {
|
|
||||||
mutate { add_field => { "risk_number" => 1 }}
|
|
||||||
mutate { replace => { "risk" => "low" }}
|
|
||||||
}
|
|
||||||
if [risk] == "3" {
|
|
||||||
mutate { add_field => { "risk_number" => 2 }}
|
|
||||||
mutate { replace => { "risk" => "medium" }}
|
|
||||||
}
|
|
||||||
if [risk] == "4" {
|
|
||||||
mutate { add_field => { "risk_number" => 3 }}
|
|
||||||
mutate { replace => { "risk" => "high" }}
|
|
||||||
}
|
|
||||||
if [risk] == "5" {
|
|
||||||
mutate { add_field => { "risk_number" => 4 }}
|
|
||||||
mutate { replace => { "risk" => "critical" }}
|
|
||||||
}
|
|
||||||
|
|
||||||
mutate {
|
|
||||||
remove_field => "message"
|
|
||||||
}
|
|
||||||
|
|
||||||
if [first_time_detected] {
|
|
||||||
date {
|
|
||||||
match => [ "first_time_detected", "dd MMM yyyy HH:mma 'GMT'ZZ", "dd MMM yyyy HH:mma 'GMT'" ]
|
|
||||||
target => "first_time_detected"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [first_time_tested] {
|
|
||||||
date {
|
|
||||||
match => [ "first_time_tested", "dd MMM yyyy HH:mma 'GMT'ZZ", "dd MMM yyyy HH:mma 'GMT'" ]
|
|
||||||
target => "first_time_tested"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [last_time_detected] {
|
|
||||||
date {
|
|
||||||
match => [ "last_time_detected", "dd MMM yyyy HH:mma 'GMT'ZZ", "dd MMM yyyy HH:mma 'GMT'" ]
|
|
||||||
target => "last_time_detected"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [last_time_tested] {
|
|
||||||
date {
|
|
||||||
match => [ "last_time_tested", "dd MMM yyyy HH:mma 'GMT'ZZ", "dd MMM yyyy HH:mma 'GMT'" ]
|
|
||||||
target => "last_time_tested"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# TODO remove when @timestamp is included in event
|
|
||||||
date {
|
|
||||||
match => [ "last_updated", "UNIX" ]
|
|
||||||
target => "@timestamp"
|
|
||||||
remove_field => "last_updated"
|
|
||||||
}
|
|
||||||
mutate {
|
|
||||||
convert => { "plugin_id" => "integer"}
|
|
||||||
convert => { "id" => "integer"}
|
|
||||||
convert => { "risk_number" => "integer"}
|
|
||||||
convert => { "risk_score" => "float"}
|
|
||||||
convert => { "total_times_detected" => "integer"}
|
|
||||||
convert => { "cvss_temporal" => "float"}
|
|
||||||
convert => { "cvss" => "float"}
|
|
||||||
}
|
|
||||||
if [risk_score] == 0 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "info" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [risk_score] > 0 and [risk_score] < 3 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "low" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [risk_score] >= 3 and [risk_score] < 6 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "medium" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [risk_score] >=6 and [risk_score] < 9 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "high" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [risk_score] >= 9 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "critical" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if [asset] =~ "\.yourdomain\.(com|net)$" {
|
|
||||||
mutate {
|
|
||||||
add_tag => [ "critical_asset" ]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
output {
|
|
||||||
if "qualys" in [tags] {
|
|
||||||
stdout {
|
|
||||||
codec => dots
|
|
||||||
}
|
|
||||||
elasticsearch {
|
|
||||||
hosts => [ "elasticsearch:9200" ]
|
|
||||||
index => "logstash-vulnwhisperer-%{+YYYY.MM}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,154 +0,0 @@
|
|||||||
# Author: Austin Taylor and Justin Henderson
|
|
||||||
# Email: austin@hasecuritysolutions.com
|
|
||||||
# Last Update: 03/04/2018
|
|
||||||
# Version 0.3
|
|
||||||
# Description: Take in qualys web scan reports from vulnWhisperer and pumps into logstash
|
|
||||||
|
|
||||||
input {
|
|
||||||
file {
|
|
||||||
path => "/opt/VulnWhisperer/data/openvas/*.json"
|
|
||||||
type => json
|
|
||||||
codec => json
|
|
||||||
start_position => "beginning"
|
|
||||||
tags => [ "openvas_scan", "openvas" ]
|
|
||||||
mode => "read"
|
|
||||||
start_position => "beginning"
|
|
||||||
file_completed_action => "delete"
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
filter {
|
|
||||||
if "openvas_scan" in [tags] {
|
|
||||||
mutate {
|
|
||||||
replace => [ "message", "%{message}" ]
|
|
||||||
gsub => [
|
|
||||||
"message", "\|\|\|", " ",
|
|
||||||
"message", "\t\t", " ",
|
|
||||||
"message", " ", " ",
|
|
||||||
"message", " ", " ",
|
|
||||||
"message", " ", " ",
|
|
||||||
"message", "nan", " ",
|
|
||||||
"message",'\n',''
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
grok {
|
|
||||||
match => { "path" => "openvas_scan_%{DATA:scan_id}_%{INT:last_updated}.json$" }
|
|
||||||
tag_on_failure => []
|
|
||||||
}
|
|
||||||
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score" => "%{cvss}" }
|
|
||||||
}
|
|
||||||
|
|
||||||
if [risk] == "1" {
|
|
||||||
mutate { add_field => { "risk_number" => 0 }}
|
|
||||||
mutate { replace => { "risk" => "info" }}
|
|
||||||
}
|
|
||||||
if [risk] == "2" {
|
|
||||||
mutate { add_field => { "risk_number" => 1 }}
|
|
||||||
mutate { replace => { "risk" => "low" }}
|
|
||||||
}
|
|
||||||
if [risk] == "3" {
|
|
||||||
mutate { add_field => { "risk_number" => 2 }}
|
|
||||||
mutate { replace => { "risk" => "medium" }}
|
|
||||||
}
|
|
||||||
if [risk] == "4" {
|
|
||||||
mutate { add_field => { "risk_number" => 3 }}
|
|
||||||
mutate { replace => { "risk" => "high" }}
|
|
||||||
}
|
|
||||||
if [risk] == "5" {
|
|
||||||
mutate { add_field => { "risk_number" => 4 }}
|
|
||||||
mutate { replace => { "risk" => "critical" }}
|
|
||||||
}
|
|
||||||
|
|
||||||
mutate {
|
|
||||||
remove_field => "message"
|
|
||||||
}
|
|
||||||
|
|
||||||
if [first_time_detected] {
|
|
||||||
date {
|
|
||||||
match => [ "first_time_detected", "dd MMM yyyy HH:mma 'GMT'ZZ", "dd MMM yyyy HH:mma 'GMT'" ]
|
|
||||||
target => "first_time_detected"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [first_time_tested] {
|
|
||||||
date {
|
|
||||||
match => [ "first_time_tested", "dd MMM yyyy HH:mma 'GMT'ZZ", "dd MMM yyyy HH:mma 'GMT'" ]
|
|
||||||
target => "first_time_tested"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [last_time_detected] {
|
|
||||||
date {
|
|
||||||
match => [ "last_time_detected", "dd MMM yyyy HH:mma 'GMT'ZZ", "dd MMM yyyy HH:mma 'GMT'" ]
|
|
||||||
target => "last_time_detected"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [last_time_tested] {
|
|
||||||
date {
|
|
||||||
match => [ "last_time_tested", "dd MMM yyyy HH:mma 'GMT'ZZ", "dd MMM yyyy HH:mma 'GMT'" ]
|
|
||||||
target => "last_time_tested"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# TODO remove when @timestamp is included in event
|
|
||||||
date {
|
|
||||||
match => [ "last_updated", "UNIX" ]
|
|
||||||
target => "@timestamp"
|
|
||||||
remove_field => "last_updated"
|
|
||||||
}
|
|
||||||
mutate {
|
|
||||||
convert => { "plugin_id" => "integer"}
|
|
||||||
convert => { "id" => "integer"}
|
|
||||||
convert => { "risk_number" => "integer"}
|
|
||||||
convert => { "risk_score" => "float"}
|
|
||||||
convert => { "total_times_detected" => "integer"}
|
|
||||||
convert => { "cvss_temporal" => "float"}
|
|
||||||
convert => { "cvss" => "float"}
|
|
||||||
}
|
|
||||||
if [risk_score] == 0 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "info" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [risk_score] > 0 and [risk_score] < 3 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "low" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [risk_score] >= 3 and [risk_score] < 6 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "medium" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [risk_score] >=6 and [risk_score] < 9 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "high" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if [risk_score] >= 9 {
|
|
||||||
mutate {
|
|
||||||
add_field => { "risk_score_name" => "critical" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
# Add your critical assets by subnet or by hostname. Comment this field out if you don't want to tag any, but the asset panel will break.
|
|
||||||
if [asset] =~ "^10\.0\.100\." {
|
|
||||||
mutate {
|
|
||||||
add_tag => [ "critical_asset" ]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
output {
|
|
||||||
if "openvas" in [tags] {
|
|
||||||
stdout {
|
|
||||||
codec => dots
|
|
||||||
}
|
|
||||||
elasticsearch {
|
|
||||||
hosts => [ "elasticsearch:9200" ]
|
|
||||||
index => "logstash-vulnwhisperer-%{+YYYY.MM}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
# Description: Take in jira tickets from vulnWhisperer and pumps into logstash
|
|
||||||
|
|
||||||
input {
|
|
||||||
file {
|
|
||||||
path => "/opt/VulnWhisperer/data/jira/*.json"
|
|
||||||
type => json
|
|
||||||
codec => json
|
|
||||||
start_position => "beginning"
|
|
||||||
mode => "read"
|
|
||||||
start_position => "beginning"
|
|
||||||
file_completed_action => "delete"
|
|
||||||
|
|
||||||
tags => [ "jira" ]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
output {
|
|
||||||
if "jira" in [tags] {
|
|
||||||
stdout { codec => rubydebug }
|
|
||||||
elasticsearch {
|
|
||||||
hosts => [ "elasticsearch:9200" ]
|
|
||||||
index => "logstash-vulnwhisperer-%{+YYYY.MM}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
1
setup.py
1
setup.py
@ -1,6 +1,5 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
from setuptools import setup, find_packages
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
|
Submodule tests/data updated: 55dc6832f8...1d0e07075e
@ -29,49 +29,49 @@ done
|
|||||||
green "✅ Elasticsearch status is green..."
|
green "✅ Elasticsearch status is green..."
|
||||||
|
|
||||||
count=0
|
count=0
|
||||||
until [[ $(curl -s "$logstash_url/_node/stats" | jq '.events.out') -ge 1236 ]]; do
|
until [[ $(curl -s "$logstash_url/_node/stats" | jq '.events.out') -ge 1617 ]]; do
|
||||||
yellow "Waiting for Logstash load to finish... $(curl -s "$logstash_url/_node/stats" | jq '.events.out') of 1236 (attempt $count of 60)"
|
yellow "Waiting for Logstash load to finish... $(curl -s "$logstash_url/_node/stats" | jq '.events.out') of 1617 (attempt $count of 60)"
|
||||||
((count++)) && ((count==60)) && break
|
((count++)) && ((count==60)) && break
|
||||||
sleep 5
|
sleep 5
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ count -le 60 && $(curl -s "$logstash_url/_node/stats" | jq '.events.out') -ge 1236 ]]; then
|
if [[ count -le 60 && $(curl -s "$logstash_url/_node/stats" | jq '.events.out') -ge 1617 ]]; then
|
||||||
green "✅ Logstash load finished..."
|
green "✅ Logstash load finished $(curl -s "$logstash_url/_node/stats" | jq '.events.out') logs processed..."
|
||||||
else
|
else
|
||||||
red "❌ Logstash load didn't complete... $(curl -s "$logstash_url/_node/stats" | jq '.events.out')"
|
red "❌ Logstash load didn't complete $(curl -s "$logstash_url/_node/stats" | jq '.events.out') logs processed... $(curl -s "$logstash_url/_node/stats" | jq '.events.out')"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
count=0
|
count=0
|
||||||
until [[ $(curl -s "$elasticsearch_url/logstash-vulnwhisperer-2019.03/_count" | jq '.count') -ge 1232 ]] ; do
|
until [[ $(curl -s "$elasticsearch_url/logstash-vulnwhisperer-*/_count" | jq '.count') -ge 1617 ]] ; do
|
||||||
yellow "Waiting for Elasticsearch index to sync... $(curl -s "$elasticsearch_url/logstash-vulnwhisperer-2019.03/_count" | jq '.count') of 1232 logs loaded (attempt $count of 150)"
|
yellow "Waiting for Elasticsearch index to sync... $(curl -s "$elasticsearch_url/logstash-vulnwhisperer-*/_count" | jq '.count') of 1617 logs loaded (attempt $count of 150)"
|
||||||
((count++)) && ((count==150)) && break
|
((count++)) && ((count==150)) && break
|
||||||
sleep 2
|
sleep 2
|
||||||
done
|
done
|
||||||
if [[ count -le 50 && $(curl -s "$elasticsearch_url/logstash-vulnwhisperer-2019.03/_count" | jq '.count') -ge 1232 ]]; then
|
if [[ count -le 50 && $(curl -s "$elasticsearch_url/logstash-vulnwhisperer-*/_count" | jq '.count') -ge 1617 ]]; then
|
||||||
green "✅ logstash-vulnwhisperer-2019.03 document count >= 1232"
|
green "✅ logstash-vulnwhisperer-* document count $(curl -s "$elasticsearch_url/logstash-vulnwhisperer-*/_count" | jq '.count') >= 1617"
|
||||||
else
|
else
|
||||||
red "❌ TIMED OUT waiting for logstash-vulnwhisperer-2019.03 document count: $(curl -s "$elasticsearch_url/logstash-vulnwhisperer-2019.03/_count" | jq) != 1232"
|
red "❌ TIMED OUT waiting for logstash-vulnwhisperer-* document count: $(curl -s "$elasticsearch_url/logstash-vulnwhisperer-*/_count" | jq) != 1617"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# if [[ $(curl -s "$elasticsearch_url/logstash-vulnwhisperer-2019.03/_count" | jq '.count') == 1232 ]]; then
|
# if [[ $(curl -s "$elasticsearch_url/logstash-vulnwhisperer-*/_count" | jq '.count') == 1232 ]]; then
|
||||||
# green "✅ Passed: logstash-vulnwhisperer-2019.03 document count == 1232"
|
# green "✅ Passed: logstash-vulnwhisperer-* document count == 1232"
|
||||||
# else
|
# else
|
||||||
# red "❌ Failed: logstash-vulnwhisperer-2019.03 document count == 1232 was: $(curl -s "$elasticsearch_url/logstash-vulnwhisperer-2019.03/_count") instead"
|
# red "❌ Failed: logstash-vulnwhisperer-* document count == 1232 was: $(curl -s "$elasticsearch_url/logstash-vulnwhisperer-*/_count") instead"
|
||||||
# ((return_code = return_code + 1))
|
# ((return_code = return_code + 1))
|
||||||
# fi
|
# fi
|
||||||
|
|
||||||
# Test Nessus plugin_name:Backported Security Patch Detection (FTP)
|
# Test Nessus signature:Backported Security Patch Detection (FTP)
|
||||||
nessus_doc=$(curl -s "$elasticsearch_url/logstash-vulnwhisperer-2019.03/_search?q=plugin_name:%22Backported%20Security%20Patch%20Detection%20(FTP)%22%20AND%20asset:176.28.50.164%20AND%20tags:nessus" | jq '.hits.hits[]._source')
|
nessus_doc=$(curl -s "$elasticsearch_url/logstash-vulnwhisperer-*/_search?q=signature:%22Backported%20Security%20Patch%20Detection%20(FTP)%22%20AND%20asset:176.28.50.164%20AND%20tags:nessus" | jq '.hits.hits[]._source')
|
||||||
if echo $nessus_doc | jq '.risk' | grep -q "None"; then
|
if echo $nessus_doc | jq '.risk' | grep -q "none"; then
|
||||||
green "✅ Passed: Nessus risk == None"
|
green "✅ Passed: Nessus risk == none"
|
||||||
else
|
else
|
||||||
red "❌ Failed: Nessus risk == None was: $(echo $nessus_doc | jq '.risk') instead"
|
red "❌ Failed: Nessus risk == none was: $(echo $nessus_doc | jq '.risk') instead"
|
||||||
((return_code = return_code + 1))
|
((return_code = return_code + 1))
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Test Tenable plugin_name:Backported Security Patch Detection (FTP)
|
# Test Tenable signature:Backported Security Patch Detection (FTP)
|
||||||
tenable_doc=$(curl -s "$elasticsearch_url/logstash-vulnwhisperer-2019.03/_search?q=plugin_name:%22Backported%20Security%20Patch%20Detection%20(FTP)%22%20AND%20asset:176.28.50.164%20AND%20tags:tenable" | jq '.hits.hits[]._source')
|
tenable_doc=$(curl -s "$elasticsearch_url/logstash-vulnwhisperer-*/_search?q=signature:%22Backported%20Security%20Patch%20Detection%20(FTP)%22%20AND%20asset:176.28.50.164%20AND%20tags:tenable" | jq '.hits.hits[]._source')
|
||||||
# Test asset
|
# Test asset
|
||||||
if echo $tenable_doc | jq .asset | grep -q '176.28.50.164'; then
|
if echo $tenable_doc | jq .asset | grep -q '176.28.50.164'; then
|
||||||
green "✅ Passed: Tenable asset == 176.28.50.164"
|
green "✅ Passed: Tenable asset == 176.28.50.164"
|
||||||
@ -88,21 +88,21 @@ else
|
|||||||
((return_code = return_code + 1))
|
((return_code = return_code + 1))
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Test Qualys plugin_name:OpenSSL Multiple Remote Security Vulnerabilities
|
# Test Qualys signature:OpenSSL Multiple Remote Security Vulnerabilities
|
||||||
qualys_vuln_doc=$(curl -s "$elasticsearch_url/logstash-vulnwhisperer-2019.03/_search?q=tags:qualys_vuln%20AND%20ip:%22176.28.50.164%22%20AND%20plugin_name:%22OpenSSL%20Multiple%20Remote%20Security%20Vulnerabilities%22%20AND%20port:465" | jq '.hits.hits[]._source')
|
qualys_vm_doc=$(curl -s "$elasticsearch_url/logstash-vulnwhisperer-*/_search?q=tags:qualys_vm%20AND%20ip:%22176.28.50.164%22%20AND%20signature:%22OpenSSL%20Multiple%20Remote%20Security%20Vulnerabilities%22%20AND%20port:465" | jq '.hits.hits[]._source')
|
||||||
# Test @timestamp
|
# Test @timestamp
|
||||||
if echo $qualys_vuln_doc | jq '.["@timestamp"]' | grep -q '2019-03-30T10:17:41.000Z'; then
|
if echo $qualys_vm_doc | jq '.["@timestamp"]' | grep -q '2019-03-30T10:17:41.000Z'; then
|
||||||
green "✅ Passed: Qualys VM @timestamp == 2019-03-30T10:17:41.000Z"
|
green "✅ Passed: Qualys VM @timestamp == 2019-03-30T10:17:41.000Z"
|
||||||
else
|
else
|
||||||
red "❌ Failed: Qualys VM @timestamp == 2019-03-30T10:17:41.000Z was: $(echo $qualys_vuln_doc | jq '.["@timestamp"]') instead"
|
red "❌ Failed: Qualys VM @timestamp == 2019-03-30T10:17:41.000Z was: $(echo $qualys_vm_doc | jq '.["@timestamp"]') instead"
|
||||||
((return_code = return_code + 1))
|
((return_code = return_code + 1))
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Test @XXXX
|
# Test @XXXX
|
||||||
if echo $qualys_vuln_doc | jq '.cvss' | grep -q '6.8'; then
|
if echo $qualys_vm_doc | jq '.cvss' | grep -q '5.6'; then
|
||||||
green "✅ Passed: Qualys VM cvss == 6.8"
|
green "✅ Passed: Qualys VM cvss == 5.6"
|
||||||
else
|
else
|
||||||
red "❌ Failed: Qualys VM cvss == 6.8 was: $(echo $qualys_vuln_doc | jq '.cvss') instead"
|
red "❌ Failed: Qualys VM cvss == 5.6 was: $(echo $qualys_vm_doc | jq '.cvss') instead"
|
||||||
((return_code = return_code + 1))
|
((return_code = return_code + 1))
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -59,8 +59,8 @@ yellow "\n*********************************************"
|
|||||||
yellow "* Test two failed scans *"
|
yellow "* Test two failed scans *"
|
||||||
yellow "*********************************************"
|
yellow "*********************************************"
|
||||||
rm -rf /opt/VulnWhisperer/*
|
rm -rf /opt/VulnWhisperer/*
|
||||||
yellow "Removing ${TEST_PATH}/qualys_vuln/scan_1553941061.87241"
|
yellow "Removing ${TEST_PATH}/qualys_vm/scan_1553941061.87241"
|
||||||
mv "${TEST_PATH}/qualys_vuln/scan_1553941061.87241"{,.bak}
|
mv "${TEST_PATH}/qualys_vm/scan_1553941061.87241"{,.bak}
|
||||||
if vuln_whisperer -F -c configs/test.ini --mock --mock_dir "${TEST_PATH}"; [[ $? -eq 2 ]]; then
|
if vuln_whisperer -F -c configs/test.ini --mock --mock_dir "${TEST_PATH}"; [[ $? -eq 2 ]]; then
|
||||||
green "\n✅ Passed: Test two failed scans"
|
green "\n✅ Passed: Test two failed scans"
|
||||||
else
|
else
|
||||||
@ -79,11 +79,11 @@ else
|
|||||||
((return_code = return_code + 1))
|
((return_code = return_code + 1))
|
||||||
fi
|
fi
|
||||||
|
|
||||||
yellow "*********************************************"
|
yellow "\n*********************************************"
|
||||||
yellow "* Test only Qualys VM with one failed scan *"
|
yellow "* Test only Qualys VM with one failed scan *"
|
||||||
yellow "*********************************************"
|
yellow "*********************************************"
|
||||||
rm -rf /opt/VulnWhisperer/*
|
rm -rf /opt/VulnWhisperer/*
|
||||||
if vuln_whisperer -F -c configs/test.ini -s qualys_vuln --mock --mock_dir "${TEST_PATH}"; [[ $? -eq 1 ]]; then
|
if vuln_whisperer -F -c configs/test.ini -s qualys_vm --mock --mock_dir "${TEST_PATH}"; [[ $? -eq 1 ]]; then
|
||||||
green "\n✅ Passed: Test only Qualys VM with one failed scan"
|
green "\n✅ Passed: Test only Qualys VM with one failed scan"
|
||||||
else
|
else
|
||||||
red "\n❌ Failed: Test only Qualys VM with one failed scan"
|
red "\n❌ Failed: Test only Qualys VM with one failed scan"
|
||||||
@ -91,7 +91,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Restore the removed files
|
# Restore the removed files
|
||||||
mv "${TEST_PATH}/qualys_vuln/scan_1553941061.87241.bak" "${TEST_PATH}/qualys_vuln/scan_1553941061.87241"
|
mv "${TEST_PATH}/qualys_vm/scan_1553941061.87241.bak" "${TEST_PATH}/qualys_vm/scan_1553941061.87241"
|
||||||
mv "${TEST_PATH}/nessus/GET_scans_exports_164_download.bak" "${TEST_PATH}/nessus/GET_scans_exports_164_download"
|
mv "${TEST_PATH}/nessus/GET_scans_exports_164_download.bak" "${TEST_PATH}/nessus/GET_scans_exports_164_download"
|
||||||
|
|
||||||
exit $return_code
|
exit $return_code
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
from __future__ import absolute_import
|
|
||||||
import sys
|
import sys
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@ -6,7 +5,7 @@ import logging
|
|||||||
if sys.version_info > (3, 0):
|
if sys.version_info > (3, 0):
|
||||||
import configparser as cp
|
import configparser as cp
|
||||||
else:
|
else:
|
||||||
import six.moves.configparser as cp
|
import ConfigParser as cp
|
||||||
|
|
||||||
|
|
||||||
class vwConfig(object):
|
class vwConfig(object):
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
from __future__ import absolute_import
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
import requests
|
import requests
|
||||||
@ -18,21 +17,38 @@ class NessusAPI(object):
|
|||||||
SCANS = '/scans'
|
SCANS = '/scans'
|
||||||
SCAN_ID = SCANS + '/{scan_id}'
|
SCAN_ID = SCANS + '/{scan_id}'
|
||||||
HOST_VULN = SCAN_ID + '/hosts/{host_id}'
|
HOST_VULN = SCAN_ID + '/hosts/{host_id}'
|
||||||
PLUGINS = HOST_VULN + '/plugins/{plugin_id}'
|
PLUGINS = HOST_VULN + '/plugins/{signature_id}'
|
||||||
EXPORT = SCAN_ID + '/export'
|
EXPORT = SCAN_ID + '/export'
|
||||||
EXPORT_TOKEN_DOWNLOAD = '/scans/exports/{token_id}/download'
|
EXPORT_TOKEN_DOWNLOAD = '/scans/exports/{token_id}/download'
|
||||||
EXPORT_FILE_DOWNLOAD = EXPORT + '/{file_id}/download'
|
EXPORT_FILE_DOWNLOAD = EXPORT + '/{file_id}/download'
|
||||||
EXPORT_STATUS = EXPORT + '/{file_id}/status'
|
EXPORT_STATUS = EXPORT + '/{file_id}/status'
|
||||||
EXPORT_HISTORY = EXPORT + '?history_id={history_id}'
|
EXPORT_HISTORY = EXPORT + '?history_id={history_id}'
|
||||||
|
# All column mappings should be lowercase
|
||||||
|
COLUMN_MAPPING = {
|
||||||
|
'cvss base score': 'cvss2_base',
|
||||||
|
'cvss temporal score': 'cvss2_temporal',
|
||||||
|
'cvss temporal vector': 'cvss2_temporal_vector',
|
||||||
|
'cvss vector': 'cvss2_vector',
|
||||||
|
'cvss3 base score': 'cvss3_base',
|
||||||
|
'cvss3 temporal score': 'cvss3_temporal',
|
||||||
|
'cvss3 temporal vector': 'cvss3_temporal_vector',
|
||||||
|
'fqdn': 'dns',
|
||||||
|
'host': 'asset',
|
||||||
|
'ip address': 'ip',
|
||||||
|
'name': 'signature',
|
||||||
|
'os': 'operating_system',
|
||||||
|
'plugin id': 'signature_id',
|
||||||
|
'see also': 'exploitability',
|
||||||
|
'system type': 'category',
|
||||||
|
'vulnerability state': 'state'
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, hostname=None, port=None, username=None, password=None, verbose=True, profile=None, access_key=None, secret_key=None):
|
def __init__(self, hostname=None, port=None, username=None, password=None, verbose=True, profile=None, access_key=None, secret_key=None):
|
||||||
self.logger = logging.getLogger('NessusAPI')
|
self.logger = logging.getLogger('NessusAPI')
|
||||||
if verbose:
|
self.logger.setLevel(logging.DEBUG if verbose else logging.INFO)
|
||||||
self.logger.setLevel(logging.DEBUG)
|
|
||||||
if not all((username, password)) and not all((access_key, secret_key)):
|
if not all((username, password)) and not all((access_key, secret_key)):
|
||||||
raise Exception('ERROR: Missing username, password or API keys.')
|
raise Exception('ERROR: Missing username, password or API keys.')
|
||||||
|
|
||||||
self.profile = profile
|
|
||||||
self.user = username
|
self.user = username
|
||||||
self.password = password
|
self.password = password
|
||||||
self.api_keys = False
|
self.api_keys = False
|
||||||
@ -40,6 +56,7 @@ class NessusAPI(object):
|
|||||||
self.secret_key = secret_key
|
self.secret_key = secret_key
|
||||||
self.base = 'https://{hostname}:{port}'.format(hostname=hostname, port=port)
|
self.base = 'https://{hostname}:{port}'.format(hostname=hostname, port=port)
|
||||||
self.verbose = verbose
|
self.verbose = verbose
|
||||||
|
self.profile = profile
|
||||||
|
|
||||||
self.session = requests.Session()
|
self.session = requests.Session()
|
||||||
self.session.verify = False
|
self.session.verify = False
|
||||||
@ -64,9 +81,6 @@ class NessusAPI(object):
|
|||||||
else:
|
else:
|
||||||
self.login()
|
self.login()
|
||||||
|
|
||||||
self.scans = self.get_scans()
|
|
||||||
self.scan_ids = self.get_scan_ids()
|
|
||||||
|
|
||||||
def login(self):
|
def login(self):
|
||||||
auth = '{"username":"%s", "password":"%s"}' % (self.user, self.password)
|
auth = '{"username":"%s", "password":"%s"}' % (self.user, self.password)
|
||||||
resp = self.request(self.SESSION, data=auth, json_output=False)
|
resp = self.request(self.SESSION, data=auth, json_output=False)
|
||||||
@ -75,7 +89,7 @@ class NessusAPI(object):
|
|||||||
else:
|
else:
|
||||||
raise Exception('[FAIL] Could not login to Nessus')
|
raise Exception('[FAIL] Could not login to Nessus')
|
||||||
|
|
||||||
def request(self, url, data=None, headers=None, method='POST', download=False, json_output=False):
|
def request(self, url, data=None, headers=None, method='POST', download=False, json_output=False, params=None):
|
||||||
timeout = 0
|
timeout = 0
|
||||||
success = False
|
success = False
|
||||||
|
|
||||||
@ -84,7 +98,7 @@ class NessusAPI(object):
|
|||||||
self.logger.debug('Requesting to url {}'.format(url))
|
self.logger.debug('Requesting to url {}'.format(url))
|
||||||
|
|
||||||
while (timeout <= 10) and (not success):
|
while (timeout <= 10) and (not success):
|
||||||
response = getattr(self.session, method)(url, data=data)
|
response = getattr(self.session, method)(url, data=data, params=params)
|
||||||
if response.status_code == 401:
|
if response.status_code == 401:
|
||||||
if url == self.base + self.SESSION:
|
if url == self.base + self.SESSION:
|
||||||
break
|
break
|
||||||
@ -113,12 +127,16 @@ class NessusAPI(object):
|
|||||||
return response_data
|
return response_data
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def get_scans(self):
|
def get_scans(self, days=None):
|
||||||
scans = self.request(self.SCANS, method='GET', json_output=True)
|
parameters = {}
|
||||||
|
if days != None:
|
||||||
|
parameters = {
|
||||||
|
"last_modification_date": (datetime.now() - timedelta(days=days)).strftime("%s")
|
||||||
|
}
|
||||||
|
scans = self.request(self.SCANS, method="GET", params=parameters, json_output=True)
|
||||||
return scans
|
return scans
|
||||||
|
|
||||||
def get_scan_ids(self):
|
def get_scan_ids(self, scans):
|
||||||
scans = self.scans
|
|
||||||
scan_ids = [scan_id['id'] for scan_id in scans['scans']] if scans['scans'] else []
|
scan_ids = [scan_id['id'] for scan_id in scans['scans']] if scans['scans'] else []
|
||||||
self.logger.debug('Found {} scan_ids'.format(len(scan_ids)))
|
self.logger.debug('Found {} scan_ids'.format(len(scan_ids)))
|
||||||
return scan_ids
|
return scan_ids
|
||||||
@ -127,10 +145,8 @@ class NessusAPI(object):
|
|||||||
data = self.request(self.SCAN_ID.format(scan_id=scan_id), method='GET', json_output=True)
|
data = self.request(self.SCAN_ID.format(scan_id=scan_id), method='GET', json_output=True)
|
||||||
return data['history']
|
return data['history']
|
||||||
|
|
||||||
def download_scan(self, scan_id=None, history=None, export_format=""):
|
def download_scan(self, scan_id=None, history=None, export_format=''):
|
||||||
running = True
|
running = True
|
||||||
counter = 0
|
|
||||||
|
|
||||||
data = {'format': export_format}
|
data = {'format': export_format}
|
||||||
if not history:
|
if not history:
|
||||||
query = self.EXPORT.format(scan_id=scan_id)
|
query = self.EXPORT.format(scan_id=scan_id)
|
||||||
@ -144,19 +160,12 @@ class NessusAPI(object):
|
|||||||
token_id = req['token'] if 'token' in req else req['temp_token']
|
token_id = req['token'] if 'token' in req else req['temp_token']
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error('{}'.format(str(e)))
|
self.logger.error('{}'.format(str(e)))
|
||||||
self.logger.info('Download for file id {}'.format(str(file_id)))
|
self.logger.info('Downloading file id {}'.format(str(file_id)))
|
||||||
while running:
|
while running:
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
counter += 2
|
|
||||||
report_status = self.request(self.EXPORT_STATUS.format(scan_id=scan_id, file_id=file_id), method='GET',
|
report_status = self.request(self.EXPORT_STATUS.format(scan_id=scan_id, file_id=file_id), method='GET',
|
||||||
json_output=True)
|
json_output=True)
|
||||||
running = report_status['status'] != 'ready'
|
running = report_status['status'] != 'ready'
|
||||||
sys.stdout.write(".")
|
|
||||||
sys.stdout.flush()
|
|
||||||
# FIXME: why? can this be removed in favour of a counter?
|
|
||||||
if counter % 60 == 0:
|
|
||||||
self.logger.info("Completed: {}".format(counter))
|
|
||||||
self.logger.info("Done: {}".format(counter))
|
|
||||||
if self.profile == 'tenable' or self.api_keys:
|
if self.profile == 'tenable' or self.api_keys:
|
||||||
content = self.request(self.EXPORT_FILE_DOWNLOAD.format(scan_id=scan_id, file_id=file_id), method='GET', download=True)
|
content = self.request(self.EXPORT_FILE_DOWNLOAD.format(scan_id=scan_id, file_id=file_id), method='GET', download=True)
|
||||||
else:
|
else:
|
||||||
@ -166,7 +175,7 @@ class NessusAPI(object):
|
|||||||
def get_utc_from_local(self, date_time, local_tz=None, epoch=True):
|
def get_utc_from_local(self, date_time, local_tz=None, epoch=True):
|
||||||
date_time = datetime.fromtimestamp(date_time)
|
date_time = datetime.fromtimestamp(date_time)
|
||||||
if local_tz is None:
|
if local_tz is None:
|
||||||
local_tz = pytz.timezone('UTC')
|
local_tz = pytz.timezone('US/Central')
|
||||||
else:
|
else:
|
||||||
local_tz = pytz.timezone(local_tz)
|
local_tz = pytz.timezone(local_tz)
|
||||||
local_time = local_tz.normalize(local_tz.localize(date_time))
|
local_time = local_tz.normalize(local_tz.localize(date_time))
|
||||||
@ -183,3 +192,43 @@ class NessusAPI(object):
|
|||||||
'Pacific Standard Time': 'US/Pacific',
|
'Pacific Standard Time': 'US/Pacific',
|
||||||
'None': 'US/Central'}
|
'None': 'US/Central'}
|
||||||
return time_map.get(tz, None)
|
return time_map.get(tz, None)
|
||||||
|
|
||||||
|
def normalise(self, df):
|
||||||
|
self.logger.debug('Normalising data')
|
||||||
|
df = self.map_fields(df)
|
||||||
|
df = self.transform_values(df)
|
||||||
|
return df
|
||||||
|
|
||||||
|
def map_fields(self, df):
|
||||||
|
self.logger.debug('Mapping fields')
|
||||||
|
# Any specific mappings here
|
||||||
|
if self.profile == 'tenable':
|
||||||
|
# Prefer CVSS Base Score over CVSS for tenable
|
||||||
|
self.logger.debug('Dropping redundant tenable fields')
|
||||||
|
df.drop('CVSS', axis=1, inplace=True, errors='ignore')
|
||||||
|
|
||||||
|
# Lowercase and map fields from COLUMN_MAPPING
|
||||||
|
df.columns = [x.lower() for x in df.columns]
|
||||||
|
df.rename(columns=self.COLUMN_MAPPING, inplace=True)
|
||||||
|
df.columns = [x.replace(' ', '_') for x in df.columns]
|
||||||
|
|
||||||
|
return df
|
||||||
|
|
||||||
|
def transform_values(self, df):
|
||||||
|
self.logger.debug('Transforming values')
|
||||||
|
|
||||||
|
df.fillna('', inplace=True)
|
||||||
|
|
||||||
|
if self.profile == 'nessus':
|
||||||
|
# Set IP from asset field
|
||||||
|
df["ip"] = df.loc[df["asset"].str.match("^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$"), "asset"]
|
||||||
|
|
||||||
|
# upper/lowercase fields
|
||||||
|
self.logger.debug('Changing case of fields')
|
||||||
|
df['cve'] = df['cve'].str.upper()
|
||||||
|
df['protocol'] = df['protocol'].str.lower()
|
||||||
|
df['risk'] = df['risk'].str.lower()
|
||||||
|
|
||||||
|
df.fillna('', inplace=True)
|
||||||
|
|
||||||
|
return df
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import absolute_import
|
|
||||||
__author__ = 'Austin Taylor'
|
__author__ = 'Austin Taylor'
|
||||||
|
|
||||||
import datetime as dt
|
import datetime as dt
|
||||||
@ -14,6 +13,20 @@ from bs4 import BeautifulSoup
|
|||||||
|
|
||||||
class OpenVAS_API(object):
|
class OpenVAS_API(object):
|
||||||
OMP = '/omp'
|
OMP = '/omp'
|
||||||
|
COLUMN_MAPPING = {
|
||||||
|
'affected software/os': 'affected_software',
|
||||||
|
'cves': 'cve',
|
||||||
|
'impact': 'description',
|
||||||
|
'nvt name': 'signature',
|
||||||
|
'nvt oid': 'signature_id',
|
||||||
|
'other references': 'exploitability',
|
||||||
|
'port protocol': 'protocol',
|
||||||
|
'severity': 'risk',
|
||||||
|
'solution type': 'category',
|
||||||
|
'task name': 'scan_name',
|
||||||
|
'specific result': 'plugin_output',
|
||||||
|
'summary': 'synopsis',
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
hostname=None,
|
hostname=None,
|
||||||
@ -111,6 +124,7 @@ class OpenVAS_API(object):
|
|||||||
]
|
]
|
||||||
token = requests.post(self.base + self.OMP, data=data, verify=False)
|
token = requests.post(self.base + self.OMP, data=data, verify=False)
|
||||||
return token
|
return token
|
||||||
|
|
||||||
def get_report_formats(self):
|
def get_report_formats(self):
|
||||||
params = (
|
params = (
|
||||||
('cmd', 'get_report_formats'),
|
('cmd', 'get_report_formats'),
|
||||||
@ -191,3 +205,25 @@ class OpenVAS_API(object):
|
|||||||
self.processed_reports += 1
|
self.processed_reports += 1
|
||||||
merged_df = pd.merge(report_df, self.openvas_reports, on='report_ids').reset_index().drop('index', axis=1)
|
merged_df = pd.merge(report_df, self.openvas_reports, on='report_ids').reset_index().drop('index', axis=1)
|
||||||
return merged_df
|
return merged_df
|
||||||
|
|
||||||
|
def normalise(self, df):
|
||||||
|
self.logger.debug('Normalising data')
|
||||||
|
df = self.map_fields(df)
|
||||||
|
df = self.transform_values(df)
|
||||||
|
return df
|
||||||
|
|
||||||
|
def map_fields(self, df):
|
||||||
|
self.logger.debug('Mapping fields')
|
||||||
|
# Lowercase and map fields from COLUMN_MAPPING
|
||||||
|
df.columns = [x.lower() for x in df.columns]
|
||||||
|
df.rename(columns=self.COLUMN_MAPPING, inplace=True)
|
||||||
|
df.columns = [x.replace(' ', '_') for x in df.columns]
|
||||||
|
return df
|
||||||
|
|
||||||
|
def transform_values(self, df):
|
||||||
|
self.logger.debug('Transforming values')
|
||||||
|
df['port'].fillna(0).astype(int)
|
||||||
|
df['risk'] = df['risk'].str.lower()
|
||||||
|
df['asset'] = df['ip']
|
||||||
|
df.fillna('', inplace=True)
|
||||||
|
return df
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import absolute_import
|
|
||||||
__author__ = 'Nathan Young'
|
__author__ = 'Nathan Young'
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
import dateutil.parser as dp
|
import dateutil.parser as dp
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
@ -19,9 +19,9 @@ class qualysWhisperAPI(object):
|
|||||||
self.logger = logging.getLogger('qualysWhisperAPI')
|
self.logger = logging.getLogger('qualysWhisperAPI')
|
||||||
self.config = config
|
self.config = config
|
||||||
try:
|
try:
|
||||||
self.qgc = qualysapi.connect(config_file=config, section='qualys_vuln')
|
self.qgc = qualysapi.connect(config, 'qualys_vm')
|
||||||
# Fail early if we can't make a request or auth is incorrect
|
# Fail early if we can't make a request or auth is incorrect
|
||||||
# self.qgc.request('about.php')
|
self.qgc.request('about.php')
|
||||||
self.logger.info('Connected to Qualys at {}'.format(self.qgc.server))
|
self.logger.info('Connected to Qualys at {}'.format(self.qgc.server))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error('Could not connect to Qualys: {}'.format(str(e)))
|
self.logger.error('Could not connect to Qualys: {}'.format(str(e)))
|
||||||
@ -29,7 +29,9 @@ class qualysWhisperAPI(object):
|
|||||||
|
|
||||||
def scan_xml_parser(self, xml):
|
def scan_xml_parser(self, xml):
|
||||||
all_records = []
|
all_records = []
|
||||||
root = ET.XML(xml.encode("utf-8"))
|
root = ET.XML(xml.encode('utf-8'))
|
||||||
|
if len(root.find('.//SCAN_LIST')) == 0:
|
||||||
|
return pd.DataFrame(columns=['id', 'status'])
|
||||||
for child in root.find('.//SCAN_LIST'):
|
for child in root.find('.//SCAN_LIST'):
|
||||||
all_records.append({
|
all_records.append({
|
||||||
'name': child.find('TITLE').text,
|
'name': child.find('TITLE').text,
|
||||||
@ -41,12 +43,17 @@ class qualysWhisperAPI(object):
|
|||||||
})
|
})
|
||||||
return pd.DataFrame(all_records)
|
return pd.DataFrame(all_records)
|
||||||
|
|
||||||
def get_all_scans(self):
|
def get_all_scans(self, days=None):
|
||||||
|
if days == None:
|
||||||
|
self.launched_date = '0001-01-01'
|
||||||
|
else:
|
||||||
|
self.launched_date = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d')
|
||||||
parameters = {
|
parameters = {
|
||||||
'action': 'list',
|
'action': 'list',
|
||||||
'echo_request': 0,
|
'echo_request': 0,
|
||||||
'show_op': 0,
|
'show_op': 0,
|
||||||
'launched_after_datetime': '0001-01-01'
|
'state': 'Finished',
|
||||||
|
'launched_after_datetime': self.launched_date
|
||||||
}
|
}
|
||||||
scans_xml = self.qgc.request(self.SCANS, parameters)
|
scans_xml = self.qgc.request(self.SCANS, parameters)
|
||||||
return self.scan_xml_parser(scans_xml)
|
return self.scan_xml_parser(scans_xml)
|
||||||
@ -79,6 +86,17 @@ class qualysUtils:
|
|||||||
|
|
||||||
class qualysVulnScan:
|
class qualysVulnScan:
|
||||||
|
|
||||||
|
COLUMN_MAPPING = {
|
||||||
|
'cve_id': 'cve',
|
||||||
|
'impact': 'synopsis',
|
||||||
|
'ip_status': 'state',
|
||||||
|
'os': 'operating_system',
|
||||||
|
'qid': 'signature_id',
|
||||||
|
'results': 'plugin_output',
|
||||||
|
'threat': 'description',
|
||||||
|
'title': 'signature'
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
config=None,
|
config=None,
|
||||||
@ -123,3 +141,51 @@ class qualysVulnScan:
|
|||||||
return scan_report
|
return scan_report
|
||||||
|
|
||||||
return scan_report
|
return scan_report
|
||||||
|
|
||||||
|
def normalise(self, df):
|
||||||
|
self.logger.debug('Normalising data')
|
||||||
|
df = self.map_fields(df)
|
||||||
|
df = self.transform_values(df)
|
||||||
|
return df
|
||||||
|
|
||||||
|
def map_fields(self, df):
|
||||||
|
self.logger.info('Mapping fields')
|
||||||
|
|
||||||
|
# Lowercase and map fields from COLUMN_MAPPING
|
||||||
|
df.columns = [x.lower() for x in df.columns]
|
||||||
|
df.rename(columns=self.COLUMN_MAPPING, inplace=True)
|
||||||
|
df.columns = [x.replace(' ', '_') for x in df.columns]
|
||||||
|
|
||||||
|
return df
|
||||||
|
|
||||||
|
def transform_values(self, df):
|
||||||
|
self.logger.info('Transforming values')
|
||||||
|
|
||||||
|
df.fillna('', inplace=True)
|
||||||
|
|
||||||
|
# upper/lowercase fields
|
||||||
|
self.logger.info('Changing case of fields')
|
||||||
|
df['cve'] = df['cve'].str.upper()
|
||||||
|
df['protocol'] = df['protocol'].str.lower()
|
||||||
|
|
||||||
|
# Contruct the CVSS vector
|
||||||
|
self.logger.info('Extracting CVSS components')
|
||||||
|
df['cvss2_vector'] = df['cvss_base'].str.extract('\((.*)\)', expand=False)
|
||||||
|
df['cvss2_base'] = df['cvss_base'].str.extract('^(\d+(?:\.\d+)?)', expand=False)
|
||||||
|
df['cvss2_temporal_vector'] = df['cvss_temporal'].str.extract('\((.*)\)', expand=False)
|
||||||
|
df['cvss2_temporal'] = df['cvss_temporal'].str.extract('^(\d+(?:\.\d+)?)', expand=False)
|
||||||
|
df.drop('cvss_base', axis=1, inplace=True, errors='ignore')
|
||||||
|
df.drop('cvss_temporal', axis=1, inplace=True, errors='ignore')
|
||||||
|
|
||||||
|
# Set asset to ip
|
||||||
|
df['asset'] = df['ip']
|
||||||
|
|
||||||
|
# Set dns to fqdn if missing
|
||||||
|
df.loc[df['dns'] == '', 'dns'] = df['fqdn']
|
||||||
|
|
||||||
|
# Convert Qualys severity to standardised risk number
|
||||||
|
df['risk_number'] = df['severity'].astype(int)-1
|
||||||
|
|
||||||
|
df.fillna('', inplace=True)
|
||||||
|
|
||||||
|
return df
|
543
vulnwhisp/frameworks/qualys_was.py
Normal file
543
vulnwhisp/frameworks/qualys_was.py
Normal file
@ -0,0 +1,543 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
__author__ = 'Austin Taylor'
|
||||||
|
|
||||||
|
import csv
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
import dateutil.parser as dp
|
||||||
|
import pandas as pd
|
||||||
|
import qualysapi
|
||||||
|
import qualysapi.config as qcconf
|
||||||
|
import requests
|
||||||
|
from lxml import objectify
|
||||||
|
from lxml.builder import E
|
||||||
|
|
||||||
|
|
||||||
|
class qualysWhisperAPI(object):
|
||||||
|
COUNT_WEBAPP = '/count/was/webapp'
|
||||||
|
COUNT_WASSCAN = '/count/was/wasscan'
|
||||||
|
DELETE_REPORT = '/delete/was/report/{report_id}'
|
||||||
|
GET_WEBAPP_DETAILS = '/get/was/webapp/{was_id}'
|
||||||
|
QPS_REST_3 = '/qps/rest/3.0'
|
||||||
|
REPORT_DETAILS = '/get/was/report/{report_id}'
|
||||||
|
REPORT_STATUS = '/status/was/report/{report_id}'
|
||||||
|
REPORT_CREATE = '/create/was/report'
|
||||||
|
REPORT_DOWNLOAD = '/download/was/report/{report_id}'
|
||||||
|
SCAN_DETAILS = '/get/was/wasscan/{scan_id}'
|
||||||
|
SCAN_DOWNLOAD = '/download/was/wasscan/{scan_id}'
|
||||||
|
SEARCH_REPORTS = '/search/was/report'
|
||||||
|
SEARCH_WEB_APPS = '/search/was/webapp'
|
||||||
|
SEARCH_WAS_SCAN = '/search/was/wasscan'
|
||||||
|
VERSION = '/qps/rest/portal/version'
|
||||||
|
|
||||||
|
def __init__(self, config=None):
|
||||||
|
self.logger = logging.getLogger('qualysWhisperAPI')
|
||||||
|
self.config = config
|
||||||
|
try:
|
||||||
|
self.qgc = qualysapi.connect(config, 'qualys_was')
|
||||||
|
self.logger.info('Connected to Qualys at {}'.format(self.qgc.server))
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error('Could not connect to Qualys: {}'.format(str(e)))
|
||||||
|
self.headers = {
|
||||||
|
#"content-type": "text/xml"}
|
||||||
|
"Accept" : "application/json",
|
||||||
|
"Content-Type": "application/json"}
|
||||||
|
self.config_parse = qcconf.QualysConnectConfig(config, 'qualys_was')
|
||||||
|
try:
|
||||||
|
self.template_id = self.config_parse.get_template_id()
|
||||||
|
except:
|
||||||
|
self.logger.error('Could not retrieve template ID')
|
||||||
|
|
||||||
|
####
|
||||||
|
#### GET SCANS TO PROCESS
|
||||||
|
####
|
||||||
|
|
||||||
|
def get_was_scan_count(self, status):
|
||||||
|
"""
|
||||||
|
Checks number of scans, used to control the api limits
|
||||||
|
"""
|
||||||
|
parameters = E.ServiceRequest(
|
||||||
|
E.filters(
|
||||||
|
E.Criteria({"field": "status", "operator": "EQUALS"}, status),
|
||||||
|
E.Criteria({"field": "launchedDate", "operator": "GREATER"}, self.launched_date)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
xml_output = self.qgc.request(self.COUNT_WASSCAN, parameters)
|
||||||
|
root = objectify.fromstring(xml_output.encode('utf-8'))
|
||||||
|
return root.count.text
|
||||||
|
|
||||||
|
def generate_scan_result_XML(self, limit=1000, offset=1, status='FINISHED'):
|
||||||
|
report_xml = E.ServiceRequest(
|
||||||
|
E.filters(
|
||||||
|
E.Criteria({'field': 'status', 'operator': 'EQUALS'}, status),
|
||||||
|
E.Criteria({"field": "launchedDate", "operator": "GREATER"}, self.launched_date)
|
||||||
|
),
|
||||||
|
E.preferences(
|
||||||
|
E.startFromOffset(str(offset)),
|
||||||
|
E.limitResults(str(limit))
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return report_xml
|
||||||
|
|
||||||
|
def get_scan_info(self, limit=1000, offset=1, status='FINISHED'):
|
||||||
|
""" Returns XML of ALL WAS Scans"""
|
||||||
|
data = self.generate_scan_result_XML(limit=limit, offset=offset, status=status)
|
||||||
|
return self.qgc.request(self.SEARCH_WAS_SCAN, data)
|
||||||
|
|
||||||
|
def xml_parser(self, xml, dupfield=None):
|
||||||
|
all_records = []
|
||||||
|
root = ET.XML(xml)
|
||||||
|
for i, child in enumerate(root):
|
||||||
|
for subchild in child:
|
||||||
|
record = {}
|
||||||
|
dup_tracker = 0
|
||||||
|
for p in subchild:
|
||||||
|
record[p.tag] = p.text
|
||||||
|
for o in p:
|
||||||
|
if o.tag in record:
|
||||||
|
dup_tracker += 1
|
||||||
|
record[o.tag + '_%s' % dup_tracker] = o.text
|
||||||
|
else:
|
||||||
|
record[o.tag] = o.text
|
||||||
|
all_records.append(record)
|
||||||
|
return pd.DataFrame(all_records)
|
||||||
|
|
||||||
|
|
||||||
|
def get_all_scans(self, limit=1000, offset=1, status='FINISHED', days=None):
|
||||||
|
if days == None:
|
||||||
|
self.launched_date = '0001-01-01'
|
||||||
|
else:
|
||||||
|
self.launched_date = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d')
|
||||||
|
qualys_api_limit = limit
|
||||||
|
dataframes = []
|
||||||
|
_records = []
|
||||||
|
try:
|
||||||
|
total = int(self.get_was_scan_count(status=status))
|
||||||
|
self.logger.info('Retrieving information for {} scans'.format(total))
|
||||||
|
for i in range(0, total):
|
||||||
|
if i % limit == 0:
|
||||||
|
if (total - i) < limit:
|
||||||
|
qualys_api_limit = total - i
|
||||||
|
self.logger.info('Making a request with a limit of {} at offset {}'.format((str(qualys_api_limit)), str(i + 1)))
|
||||||
|
scan_info = self.get_scan_info(limit=qualys_api_limit, offset=i + 1, status=status)
|
||||||
|
_records.append(scan_info)
|
||||||
|
self.logger.debug('Converting XML to DataFrame')
|
||||||
|
dataframes = [self.xml_parser(xml) for xml in _records]
|
||||||
|
if not dataframes:
|
||||||
|
return pd.DataFrame(columns=['id'])
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error("Couldn't process all scans: {}".format(e))
|
||||||
|
|
||||||
|
return pd.concat(dataframes, axis=0).reset_index().drop('index', axis=1)
|
||||||
|
|
||||||
|
####
|
||||||
|
#### CREATE VULNERABILITY REPORT AND DOWNLOAD IT
|
||||||
|
####
|
||||||
|
|
||||||
|
def get_report_status(self, report_id):
|
||||||
|
return self.qgc.request(self.REPORT_STATUS.format(report_id=report_id))
|
||||||
|
|
||||||
|
def download_report(self, report_id):
|
||||||
|
return self.qgc.request(self.REPORT_DOWNLOAD.format(report_id=report_id))
|
||||||
|
|
||||||
|
def generate_scan_report_XML(self, scan_id):
|
||||||
|
"""Generates a CSV report for an asset based on template defined in .ini file"""
|
||||||
|
report_xml = E.ServiceRequest(
|
||||||
|
E.data(
|
||||||
|
E.Report(
|
||||||
|
E.name('<![CDATA[API Scan Report generated by VulnWhisperer]]>'),
|
||||||
|
E.description('<![CDATA[CSV Scanning report for VulnWhisperer]]>'),
|
||||||
|
E.format('CSV'),
|
||||||
|
#type is not needed, as the template already has it
|
||||||
|
E.type('WAS_SCAN_REPORT'),
|
||||||
|
E.template(
|
||||||
|
E.id(self.template_id)
|
||||||
|
),
|
||||||
|
E.config(
|
||||||
|
E.scanReport(
|
||||||
|
E.target(
|
||||||
|
E.scans(
|
||||||
|
E.WasScan(
|
||||||
|
E.id(scan_id)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return report_xml
|
||||||
|
|
||||||
|
def create_report(self, report_id, kind='scan'):
|
||||||
|
mapper = {'scan': self.generate_scan_report_XML}
|
||||||
|
try:
|
||||||
|
data = mapper[kind](report_id)
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error('Error creating report: {}'.format(str(e)))
|
||||||
|
return self.qgc.request(self.REPORT_CREATE, data).encode('utf-8')
|
||||||
|
|
||||||
|
def delete_report(self, report_id):
|
||||||
|
return self.qgc.request(self.DELETE_REPORT.format(report_id=report_id))
|
||||||
|
|
||||||
|
|
||||||
|
class qualysReportFields:
|
||||||
|
CATEGORIES = ['VULNERABILITY',
|
||||||
|
'SENSITIVECONTENT',
|
||||||
|
'INFORMATION_GATHERED']
|
||||||
|
|
||||||
|
# URL Vulnerability Information
|
||||||
|
|
||||||
|
VULN_BLOCK = [
|
||||||
|
CATEGORIES[0],
|
||||||
|
'ID',
|
||||||
|
'QID',
|
||||||
|
'Url',
|
||||||
|
'Param',
|
||||||
|
'Function',
|
||||||
|
'Form Entry Point',
|
||||||
|
'Access Path',
|
||||||
|
'Authentication',
|
||||||
|
'Ajax Request',
|
||||||
|
'Ajax Request ID',
|
||||||
|
'Ignored',
|
||||||
|
'Ignore Reason',
|
||||||
|
'Ignore Date',
|
||||||
|
'Ignore User',
|
||||||
|
'Ignore Comments',
|
||||||
|
'First Time Detected',
|
||||||
|
'Last Time Detected',
|
||||||
|
'Last Time Tested',
|
||||||
|
'Times Detected',
|
||||||
|
'Payload #1',
|
||||||
|
'Request Method #1',
|
||||||
|
'Request URL #1',
|
||||||
|
'Request Headers #1',
|
||||||
|
'Response #1',
|
||||||
|
'Evidence #1',
|
||||||
|
]
|
||||||
|
|
||||||
|
INFO_HEADER = [
|
||||||
|
'Vulnerability Category',
|
||||||
|
'ID',
|
||||||
|
'QID',
|
||||||
|
'Response #1',
|
||||||
|
'Last Time Detected',
|
||||||
|
]
|
||||||
|
INFO_BLOCK = [
|
||||||
|
CATEGORIES[2],
|
||||||
|
'ID',
|
||||||
|
'QID',
|
||||||
|
'Results',
|
||||||
|
'Detection Date',
|
||||||
|
]
|
||||||
|
|
||||||
|
QID_HEADER = [
|
||||||
|
'QID',
|
||||||
|
'Id',
|
||||||
|
'Title',
|
||||||
|
'Category',
|
||||||
|
'Severity Level',
|
||||||
|
'Groups',
|
||||||
|
'OWASP',
|
||||||
|
'WASC',
|
||||||
|
'CWE',
|
||||||
|
'CVSS Base',
|
||||||
|
'CVSS Temporal',
|
||||||
|
'Description',
|
||||||
|
'Impact',
|
||||||
|
'Solution',
|
||||||
|
]
|
||||||
|
GROUP_HEADER = ['GROUP', 'Name', 'Category']
|
||||||
|
OWASP_HEADER = ['OWASP', 'Code', 'Name']
|
||||||
|
WASC_HEADER = ['WASC', 'Code', 'Name']
|
||||||
|
SCAN_META = ['Web Application Name', 'URL', 'Owner', 'Scope', 'Operating System']
|
||||||
|
CATEGORY_HEADER = ['Category', 'Severity', 'Level', 'Description']
|
||||||
|
|
||||||
|
|
||||||
|
class qualysUtils:
|
||||||
|
def __init__(self):
|
||||||
|
self.logger = logging.getLogger('qualysUtils')
|
||||||
|
|
||||||
|
def grab_section(
|
||||||
|
self,
|
||||||
|
report,
|
||||||
|
section,
|
||||||
|
end=[],
|
||||||
|
pop_last=False,
|
||||||
|
):
|
||||||
|
temp_list = []
|
||||||
|
max_col_count = 0
|
||||||
|
with open(report, 'rb') as csvfile:
|
||||||
|
q_report = csv.reader(csvfile, delimiter=',', quotechar='"')
|
||||||
|
for line in q_report:
|
||||||
|
if set(line) == set(section):
|
||||||
|
break
|
||||||
|
|
||||||
|
# Reads text until the end of the block:
|
||||||
|
for line in q_report: # This keeps reading the file
|
||||||
|
temp_list.append(line)
|
||||||
|
|
||||||
|
if line in end:
|
||||||
|
break
|
||||||
|
if pop_last and len(temp_list) > 1:
|
||||||
|
temp_list.pop(-1)
|
||||||
|
return temp_list
|
||||||
|
|
||||||
|
def iso_to_epoch(self, dt):
|
||||||
|
return dp.parse(dt).strftime('%s')
|
||||||
|
|
||||||
|
class qualysScanReport:
|
||||||
|
|
||||||
|
COLUMN_MAPPING = {
|
||||||
|
'CVSS Base': 'cvss2_base',
|
||||||
|
'CVSS Temporal': 'cvss2_temporal',
|
||||||
|
'DescriptionCatSev': 'category_description',
|
||||||
|
'DescriptionSeverity': 'synopsis',
|
||||||
|
'Evidence #1': 'evidence',
|
||||||
|
'Payload #1': 'payload',
|
||||||
|
'QID': 'signature_id',
|
||||||
|
'Request Headers #1': 'request_headers',
|
||||||
|
'Request Method #1': 'request_method',
|
||||||
|
'Request URL #1': 'request_url',
|
||||||
|
'Response #1': 'plugin_output',
|
||||||
|
'Title': 'signature',
|
||||||
|
'Url': 'uri',
|
||||||
|
'URL': 'url',
|
||||||
|
'Vulnerability Category': 'type',
|
||||||
|
}
|
||||||
|
|
||||||
|
# URL Vulnerability Information
|
||||||
|
WEB_SCAN_VULN_BLOCK = list(qualysReportFields.VULN_BLOCK)
|
||||||
|
WEB_SCAN_VULN_BLOCK.insert(WEB_SCAN_VULN_BLOCK.index('QID'), 'Detection ID')
|
||||||
|
|
||||||
|
WEB_SCAN_VULN_HEADER = list(WEB_SCAN_VULN_BLOCK)
|
||||||
|
WEB_SCAN_VULN_HEADER[WEB_SCAN_VULN_BLOCK.index(qualysReportFields.CATEGORIES[0])] = \
|
||||||
|
'Vulnerability Category'
|
||||||
|
|
||||||
|
# Add an alternative vulnerability header
|
||||||
|
WEB_SCAN_VULN_BLOCK_ALT = WEB_SCAN_VULN_BLOCK[:]
|
||||||
|
WEB_SCAN_VULN_BLOCK_ALT.insert(WEB_SCAN_VULN_BLOCK_ALT.index('First Time Detected'), 'Detection Date')
|
||||||
|
remove_fields = ['Last Time Tested', 'Times Detected', 'First Time Detected', 'Last Time Detected']
|
||||||
|
WEB_SCAN_VULN_BLOCK_ALT = [x for x in WEB_SCAN_VULN_BLOCK_ALT if x not in remove_fields]
|
||||||
|
|
||||||
|
WEB_SCAN_VULN_HEADER_ALT = WEB_SCAN_VULN_BLOCK_ALT[:]
|
||||||
|
WEB_SCAN_VULN_HEADER_ALT[WEB_SCAN_VULN_BLOCK_ALT.index(qualysReportFields.CATEGORIES[0])] = \
|
||||||
|
'Vulnerability Category'
|
||||||
|
|
||||||
|
WEB_SCAN_SENSITIVE_HEADER = list(WEB_SCAN_VULN_HEADER)
|
||||||
|
WEB_SCAN_SENSITIVE_HEADER.insert(WEB_SCAN_SENSITIVE_HEADER.index('Url'
|
||||||
|
), 'Content')
|
||||||
|
|
||||||
|
WEB_SCAN_SENSITIVE_BLOCK = list(WEB_SCAN_SENSITIVE_HEADER)
|
||||||
|
WEB_SCAN_SENSITIVE_BLOCK.insert(WEB_SCAN_SENSITIVE_BLOCK.index('QID'), 'Detection ID')
|
||||||
|
WEB_SCAN_SENSITIVE_BLOCK[WEB_SCAN_SENSITIVE_BLOCK.index('Vulnerability Category'
|
||||||
|
)] = qualysReportFields.CATEGORIES[1]
|
||||||
|
|
||||||
|
WEB_SCAN_INFO_HEADER = list(qualysReportFields.INFO_HEADER)
|
||||||
|
WEB_SCAN_INFO_HEADER.insert(WEB_SCAN_INFO_HEADER.index('QID'), 'Detection ID')
|
||||||
|
|
||||||
|
WEB_SCAN_INFO_BLOCK = list(qualysReportFields.INFO_BLOCK)
|
||||||
|
WEB_SCAN_INFO_BLOCK.insert(WEB_SCAN_INFO_BLOCK.index('QID'), 'Detection ID')
|
||||||
|
|
||||||
|
QID_HEADER = list(qualysReportFields.QID_HEADER)
|
||||||
|
GROUP_HEADER = list(qualysReportFields.GROUP_HEADER)
|
||||||
|
OWASP_HEADER = list(qualysReportFields.OWASP_HEADER)
|
||||||
|
WASC_HEADER = list(qualysReportFields.WASC_HEADER)
|
||||||
|
SCAN_META = list(qualysReportFields.SCAN_META)
|
||||||
|
CATEGORY_HEADER = list(qualysReportFields.CATEGORY_HEADER)
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
config=None,
|
||||||
|
file_in=None,
|
||||||
|
file_stream=False,
|
||||||
|
delimiter=',',
|
||||||
|
quotechar='"',
|
||||||
|
):
|
||||||
|
self.logger = logging.getLogger('qualysScanReport')
|
||||||
|
self.file_in = file_in
|
||||||
|
self.file_stream = file_stream
|
||||||
|
self.report = None
|
||||||
|
self.utils = qualysUtils()
|
||||||
|
|
||||||
|
if config:
|
||||||
|
try:
|
||||||
|
self.qw = qualysWhisperAPI(config=config)
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error('Could not load config! Please check settings. Error: {}'.format(str(e)))
|
||||||
|
|
||||||
|
if file_stream:
|
||||||
|
self.open_file = file_in.splitlines()
|
||||||
|
elif file_in:
|
||||||
|
|
||||||
|
self.open_file = open(file_in, 'rb')
|
||||||
|
|
||||||
|
self.downloaded_file = None
|
||||||
|
|
||||||
|
def grab_sections(self, report):
|
||||||
|
all_dataframes = []
|
||||||
|
dict_tracker = {}
|
||||||
|
with open(report, 'rb') as csvfile:
|
||||||
|
dict_tracker['WEB_SCAN_VULN_BLOCK'] = pd.DataFrame(self.utils.grab_section(report,
|
||||||
|
self.WEB_SCAN_VULN_BLOCK,
|
||||||
|
end=[
|
||||||
|
self.WEB_SCAN_SENSITIVE_BLOCK,
|
||||||
|
self.WEB_SCAN_INFO_BLOCK],
|
||||||
|
pop_last=True),
|
||||||
|
columns=self.WEB_SCAN_VULN_HEADER)
|
||||||
|
if len(dict_tracker['WEB_SCAN_VULN_BLOCK']) == 0:
|
||||||
|
# Try alternative headers
|
||||||
|
dict_tracker["WEB_SCAN_VULN_BLOCK"] = pd.DataFrame(
|
||||||
|
self.utils.grab_section(
|
||||||
|
report,
|
||||||
|
self.WEB_SCAN_VULN_BLOCK_ALT,
|
||||||
|
end=[self.WEB_SCAN_SENSITIVE_BLOCK, self.WEB_SCAN_INFO_BLOCK],
|
||||||
|
pop_last=True,
|
||||||
|
),
|
||||||
|
columns=self.WEB_SCAN_VULN_HEADER_ALT,
|
||||||
|
)
|
||||||
|
dict_tracker['WEB_SCAN_SENSITIVE_BLOCK'] = pd.DataFrame(self.utils.grab_section(report,
|
||||||
|
self.WEB_SCAN_SENSITIVE_BLOCK,
|
||||||
|
end=[
|
||||||
|
self.WEB_SCAN_INFO_BLOCK,
|
||||||
|
self.WEB_SCAN_SENSITIVE_BLOCK],
|
||||||
|
pop_last=True),
|
||||||
|
columns=self.WEB_SCAN_SENSITIVE_HEADER)
|
||||||
|
dict_tracker['WEB_SCAN_INFO_BLOCK'] = pd.DataFrame(self.utils.grab_section(report,
|
||||||
|
self.WEB_SCAN_INFO_BLOCK,
|
||||||
|
end=[self.QID_HEADER],
|
||||||
|
pop_last=True),
|
||||||
|
columns=self.WEB_SCAN_INFO_HEADER)
|
||||||
|
dict_tracker['QID_HEADER'] = pd.DataFrame(self.utils.grab_section(report,
|
||||||
|
self.QID_HEADER,
|
||||||
|
end=[self.GROUP_HEADER],
|
||||||
|
pop_last=True),
|
||||||
|
columns=self.QID_HEADER)
|
||||||
|
dict_tracker['GROUP_HEADER'] = pd.DataFrame(self.utils.grab_section(report,
|
||||||
|
self.GROUP_HEADER,
|
||||||
|
end=[self.OWASP_HEADER],
|
||||||
|
pop_last=True),
|
||||||
|
columns=self.GROUP_HEADER)
|
||||||
|
dict_tracker['OWASP_HEADER'] = pd.DataFrame(self.utils.grab_section(report,
|
||||||
|
self.OWASP_HEADER,
|
||||||
|
end=[self.WASC_HEADER],
|
||||||
|
pop_last=True),
|
||||||
|
columns=self.OWASP_HEADER)
|
||||||
|
dict_tracker['WASC_HEADER'] = pd.DataFrame(self.utils.grab_section(report,
|
||||||
|
self.WASC_HEADER, end=[['APPENDIX']],
|
||||||
|
pop_last=True),
|
||||||
|
columns=self.WASC_HEADER)
|
||||||
|
|
||||||
|
dict_tracker['SCAN_META'] = pd.DataFrame(self.utils.grab_section(report,
|
||||||
|
self.SCAN_META,
|
||||||
|
end=[self.CATEGORY_HEADER],
|
||||||
|
pop_last=True),
|
||||||
|
columns=self.SCAN_META)
|
||||||
|
|
||||||
|
dict_tracker['CATEGORY_HEADER'] = pd.DataFrame(self.utils.grab_section(report,
|
||||||
|
self.CATEGORY_HEADER),
|
||||||
|
columns=self.CATEGORY_HEADER)
|
||||||
|
all_dataframes.append(dict_tracker)
|
||||||
|
|
||||||
|
return all_dataframes
|
||||||
|
|
||||||
|
def data_normalizer(self, dataframes):
|
||||||
|
"""
|
||||||
|
Merge and clean data
|
||||||
|
:param dataframes:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
df_dict = dataframes[0]
|
||||||
|
merged_df = pd.concat([df_dict['WEB_SCAN_VULN_BLOCK'], df_dict['WEB_SCAN_SENSITIVE_BLOCK'],
|
||||||
|
df_dict['WEB_SCAN_INFO_BLOCK']], axis=0,
|
||||||
|
ignore_index=False)
|
||||||
|
merged_df = pd.merge(merged_df, df_dict['QID_HEADER'], left_on='QID',
|
||||||
|
right_on='Id')
|
||||||
|
|
||||||
|
if 'Content' not in merged_df:
|
||||||
|
merged_df['Content'] = ''
|
||||||
|
|
||||||
|
columns_to_cleanse = ['Payload #1', 'Request Method #1', 'Request URL #1',
|
||||||
|
'Request Headers #1', 'Response #1', 'Evidence #1',
|
||||||
|
'Description', 'Impact', 'Solution', 'Url', 'Content']
|
||||||
|
|
||||||
|
merged_df = merged_df.drop(['QID_y', 'QID_x'], axis=1)
|
||||||
|
merged_df = merged_df.rename(columns={'Id': 'QID'})
|
||||||
|
|
||||||
|
merged_df = merged_df.assign(**df_dict['SCAN_META'].to_dict(orient='records')[0])
|
||||||
|
|
||||||
|
merged_df = pd.merge(merged_df, df_dict['CATEGORY_HEADER'], how='left', left_on=['Category', 'Severity Level'],
|
||||||
|
right_on=['Category', 'Severity'], suffixes=('Severity', 'CatSev'))
|
||||||
|
|
||||||
|
merged_df = merged_df.replace('N/A', '').fillna('')
|
||||||
|
|
||||||
|
try:
|
||||||
|
merged_df = \
|
||||||
|
merged_df[~merged_df.Title.str.contains('Links Crawled|External Links Discovered')]
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error('Error normalizing: {}'.format(str(e)))
|
||||||
|
return merged_df
|
||||||
|
|
||||||
|
def download_file(self, path='', file_id=None):
|
||||||
|
report = self.qw.download_report(file_id).encode('utf-8')
|
||||||
|
filename = path + str(file_id) + '.csv'
|
||||||
|
file_out = open(filename, 'w')
|
||||||
|
for line in report.splitlines():
|
||||||
|
file_out.write(line + '\n')
|
||||||
|
file_out.close()
|
||||||
|
self.logger.info('File written to {}'.format(filename))
|
||||||
|
return filename
|
||||||
|
|
||||||
|
def process_data(self, path='', file_id=None, cleanup=True):
|
||||||
|
"""Downloads a file from qualys and normalizes it"""
|
||||||
|
|
||||||
|
download_file = self.download_file(path=path, file_id=file_id)
|
||||||
|
self.logger.info('Downloading file ID: {}'.format(file_id))
|
||||||
|
report_data = self.grab_sections(download_file)
|
||||||
|
merged_data = self.data_normalizer(report_data)
|
||||||
|
merged_data.sort_index(axis=1, inplace=True)
|
||||||
|
|
||||||
|
return merged_data
|
||||||
|
|
||||||
|
def normalise(self, df):
|
||||||
|
self.logger.debug('Normalising data')
|
||||||
|
df = self.map_fields(df)
|
||||||
|
df = self.transform_values(df)
|
||||||
|
return df
|
||||||
|
|
||||||
|
def map_fields(self, df):
|
||||||
|
self.logger.debug('Mapping fields')
|
||||||
|
|
||||||
|
df.rename(columns=self.COLUMN_MAPPING, inplace=True)
|
||||||
|
|
||||||
|
# Lowercase and map fields from COLUMN_MAPPING
|
||||||
|
df.columns = [x.lower() for x in df.columns]
|
||||||
|
df.columns = [x.replace(' ', '_') for x in df.columns]
|
||||||
|
|
||||||
|
return df
|
||||||
|
|
||||||
|
def transform_values(self, df):
|
||||||
|
self.logger.debug('Transforming values')
|
||||||
|
df.fillna('', inplace=True)
|
||||||
|
|
||||||
|
self.logger.info('Changing case of fields')
|
||||||
|
df['cwe'] = df['cwe'].str.upper()
|
||||||
|
|
||||||
|
# Convert Qualys severity to standardised risk number
|
||||||
|
df['risk_number'] = df['severity'].astype(int)-1
|
||||||
|
|
||||||
|
# Extract dns field from URL
|
||||||
|
df['dns'] = df['url'].str.extract('https?://([^/]+)', expand=False)
|
||||||
|
df['dns'] = df.loc[df['uri'] != '','uri'].str.extract('https?://([^/]+)', expand=False)
|
||||||
|
|
||||||
|
# Set asset to web_application_name
|
||||||
|
df['asset'] = df['web_application_name']
|
||||||
|
|
||||||
|
df.fillna('', inplace=True)
|
||||||
|
return df
|
@ -1,389 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import absolute_import
|
|
||||||
from six.moves import range
|
|
||||||
from functools import reduce
|
|
||||||
__author__ = 'Austin Taylor'
|
|
||||||
|
|
||||||
from lxml import objectify
|
|
||||||
from lxml.builder import E
|
|
||||||
import xml.etree.ElementTree as ET
|
|
||||||
import pandas as pd
|
|
||||||
import qualysapi
|
|
||||||
import qualysapi.config as qcconf
|
|
||||||
import requests
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import csv
|
|
||||||
import logging
|
|
||||||
import dateutil.parser as dp
|
|
||||||
csv.field_size_limit(sys.maxsize)
|
|
||||||
|
|
||||||
|
|
||||||
class qualysWhisperAPI(object):
|
|
||||||
COUNT_WASSCAN = '/count/was/wasscan'
|
|
||||||
DELETE_REPORT = '/delete/was/report/{report_id}'
|
|
||||||
REPORT_STATUS = '/status/was/report/{report_id}'
|
|
||||||
REPORT_CREATE = '/create/was/report'
|
|
||||||
REPORT_DOWNLOAD = '/download/was/report/{report_id}'
|
|
||||||
SEARCH_WAS_SCAN = '/search/was/wasscan'
|
|
||||||
|
|
||||||
def __init__(self, config=None):
|
|
||||||
self.logger = logging.getLogger('qualysWhisperAPI')
|
|
||||||
self.config = config
|
|
||||||
try:
|
|
||||||
self.qgc = qualysapi.connect(config, 'qualys_web')
|
|
||||||
self.logger.info('Connected to Qualys at {}'.format(self.qgc.server))
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.error('Could not connect to Qualys: {}'.format(str(e)))
|
|
||||||
self.config_parse = qcconf.QualysConnectConfig(config, 'qualys_web')
|
|
||||||
try:
|
|
||||||
self.template_id = self.config_parse.get_template_id()
|
|
||||||
except:
|
|
||||||
self.logger.error('Could not retrieve template ID')
|
|
||||||
|
|
||||||
####
|
|
||||||
#### GET SCANS TO PROCESS
|
|
||||||
####
|
|
||||||
|
|
||||||
def get_was_scan_count(self, status):
|
|
||||||
"""
|
|
||||||
Checks number of scans, used to control the api limits
|
|
||||||
"""
|
|
||||||
parameters = (
|
|
||||||
E.ServiceRequest(
|
|
||||||
E.filters(
|
|
||||||
E.Criteria({'field': 'status', 'operator': 'EQUALS'}, status))))
|
|
||||||
xml_output = self.qgc.request(self.COUNT_WASSCAN, parameters)
|
|
||||||
root = objectify.fromstring(xml_output.encode('utf-8'))
|
|
||||||
return root.count.text
|
|
||||||
|
|
||||||
def generate_scan_result_XML(self, limit=1000, offset=1, status='FINISHED'):
|
|
||||||
report_xml = E.ServiceRequest(
|
|
||||||
E.filters(E.Criteria({'field': 'status', 'operator': 'EQUALS'}, status)),
|
|
||||||
E.preferences(E.startFromOffset(str(offset)), E.limitResults(str(limit))),
|
|
||||||
)
|
|
||||||
return report_xml
|
|
||||||
|
|
||||||
def get_scan_info(self, limit=1000, offset=1, status='FINISHED'):
|
|
||||||
""" Returns XML of ALL WAS Scans"""
|
|
||||||
data = self.generate_scan_result_XML(limit=limit, offset=offset, status=status)
|
|
||||||
return self.qgc.request(self.SEARCH_WAS_SCAN, data)
|
|
||||||
|
|
||||||
def xml_parser(self, xml, dupfield=None):
|
|
||||||
all_records = []
|
|
||||||
root = ET.XML(xml)
|
|
||||||
for i, child in enumerate(root):
|
|
||||||
for subchild in child:
|
|
||||||
record = {}
|
|
||||||
dup_tracker = 0
|
|
||||||
for p in subchild:
|
|
||||||
record[p.tag] = p.text
|
|
||||||
for o in p:
|
|
||||||
if o.tag in record:
|
|
||||||
dup_tracker += 1
|
|
||||||
record[o.tag + '_%s' % dup_tracker] = o.text
|
|
||||||
else:
|
|
||||||
record[o.tag] = o.text
|
|
||||||
all_records.append(record)
|
|
||||||
return pd.DataFrame(all_records)
|
|
||||||
|
|
||||||
def get_all_scans(self, limit=1000, offset=1, status='FINISHED'):
|
|
||||||
qualys_api_limit = limit
|
|
||||||
dataframes = []
|
|
||||||
_records = []
|
|
||||||
try:
|
|
||||||
total = int(self.get_was_scan_count(status=status))
|
|
||||||
self.logger.error('Already have WAS scan count')
|
|
||||||
self.logger.info('Retrieving information for {} scans'.format(total))
|
|
||||||
for i in range(0, total):
|
|
||||||
if i % limit == 0:
|
|
||||||
if (total - i) < limit:
|
|
||||||
qualys_api_limit = total - i
|
|
||||||
self.logger.info('Making a request with a limit of {} at offset {}'
|
|
||||||
.format((str(qualys_api_limit)), str(i + 1)))
|
|
||||||
scan_info = self.get_scan_info(
|
|
||||||
limit=qualys_api_limit, offset=i + 1, status=status)
|
|
||||||
_records.append(scan_info)
|
|
||||||
self.logger.debug('Converting XML to DataFrame')
|
|
||||||
dataframes = [self.xml_parser(xml) for xml in _records]
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.error("Couldn't process all scans: {}".format(e))
|
|
||||||
|
|
||||||
return pd.concat(dataframes, axis=0).reset_index().drop('index', axis=1)
|
|
||||||
|
|
||||||
####
|
|
||||||
#### CREATE VULNERABILITY REPORT AND DOWNLOAD IT
|
|
||||||
####
|
|
||||||
|
|
||||||
def get_report_status(self, report_id):
|
|
||||||
return self.qgc.request(self.REPORT_STATUS.format(report_id=report_id))
|
|
||||||
|
|
||||||
def download_report(self, report_id):
|
|
||||||
return self.qgc.request(
|
|
||||||
self.REPORT_DOWNLOAD.format(report_id=report_id), http_method='get')
|
|
||||||
|
|
||||||
def generate_scan_report_XML(self, scan_id):
|
|
||||||
"""Generates a CSV report for an asset based on template defined in .ini file"""
|
|
||||||
report_xml = E.ServiceRequest(
|
|
||||||
E.data(
|
|
||||||
E.Report(
|
|
||||||
E.name('<![CDATA[API Scan Report generated by VulnWhisperer]]>'),
|
|
||||||
E.description('<![CDATA[CSV Scanning report for VulnWhisperer]]>'),
|
|
||||||
E.format('CSV'),
|
|
||||||
#type is not needed, as the template already has it
|
|
||||||
E.type('WAS_SCAN_REPORT'),
|
|
||||||
E.template(E.id(self.template_id)),
|
|
||||||
E.config(E.scanReport(E.target(E.scans(E.WasScan(E.id(scan_id))))))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return report_xml
|
|
||||||
|
|
||||||
def create_report(self, report_id, kind='scan'):
|
|
||||||
mapper = {'scan': self.generate_scan_report_XML}
|
|
||||||
try:
|
|
||||||
data = mapper[kind](report_id)
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.error('Error creating report: {}'.format(str(e)))
|
|
||||||
return self.qgc.request(self.REPORT_CREATE, data).encode('utf-8')
|
|
||||||
|
|
||||||
def delete_report(self, report_id):
|
|
||||||
return self.qgc.request(self.DELETE_REPORT.format(report_id=report_id))
|
|
||||||
|
|
||||||
class qualysUtils:
|
|
||||||
def __init__(self):
|
|
||||||
self.logger = logging.getLogger('qualysUtils')
|
|
||||||
|
|
||||||
def grab_section(self, report, section, end=[], pop_last=False):
|
|
||||||
temp_list = []
|
|
||||||
max_col_count = 0
|
|
||||||
with open(report, 'rt') as csvfile:
|
|
||||||
q_report = csv.reader(csvfile, delimiter=',', quotechar='"')
|
|
||||||
for line in q_report:
|
|
||||||
if set(line) == set(section):
|
|
||||||
break
|
|
||||||
|
|
||||||
# Reads text until the end of the block:
|
|
||||||
for line in q_report: # This keeps reading the file
|
|
||||||
temp_list.append(line)
|
|
||||||
|
|
||||||
if line in end:
|
|
||||||
break
|
|
||||||
if pop_last and len(temp_list) > 1:
|
|
||||||
temp_list.pop(-1)
|
|
||||||
return temp_list
|
|
||||||
|
|
||||||
def iso_to_epoch(self, dt):
|
|
||||||
return dp.parse(dt).strftime('%s')
|
|
||||||
|
|
||||||
def cleanser(self, _data):
|
|
||||||
repls = (('\n', '|||'), ('\r', '|||'), (',', ';'), ('\t', '|||'))
|
|
||||||
if _data:
|
|
||||||
_data = reduce(lambda a, kv: a.replace(*kv), repls, str(_data))
|
|
||||||
return _data
|
|
||||||
|
|
||||||
class qualysScanReport:
|
|
||||||
CATEGORIES = ['VULNERABILITY', 'SENSITIVECONTENT', 'INFORMATION_GATHERED']
|
|
||||||
|
|
||||||
WEB_SCAN_BLOCK = [
|
|
||||||
"ID", "Detection ID", "QID", "Url", "Param/Cookie", "Function",
|
|
||||||
"Form Entry Point", "Access Path", "Authentication", "Ajax Request",
|
|
||||||
"Ajax Request ID", "Ignored", "Ignore Reason", "Ignore Date", "Ignore User",
|
|
||||||
"Ignore Comments", "Detection Date", "Payload #1", "Request Method #1",
|
|
||||||
"Request URL #1", "Request Headers #1", "Response #1", "Evidence #1",
|
|
||||||
"Unique ID", "Flags", "Protocol", "Virtual Host", "IP", "Port", "Result",
|
|
||||||
"Info#1", "CVSS V3 Base", "CVSS V3 Temporal", "CVSS V3 Attack Vector",
|
|
||||||
"Request Body #1"
|
|
||||||
]
|
|
||||||
WEB_SCAN_VULN_BLOCK = [CATEGORIES[0]] + WEB_SCAN_BLOCK
|
|
||||||
WEB_SCAN_SENSITIVE_BLOCK = [CATEGORIES[1]] + WEB_SCAN_BLOCK
|
|
||||||
|
|
||||||
WEB_SCAN_HEADER = ["Vulnerability Category"] + WEB_SCAN_BLOCK
|
|
||||||
WEB_SCAN_HEADER[WEB_SCAN_HEADER.index("Detection Date")] = "Last Time Detected"
|
|
||||||
|
|
||||||
|
|
||||||
WEB_SCAN_INFO_BLOCK = [
|
|
||||||
"INFORMATION_GATHERED", "ID", "Detection ID", "QID", "Results", "Detection Date",
|
|
||||||
"Unique ID", "Flags", "Protocol", "Virtual Host", "IP", "Port", "Result",
|
|
||||||
"Info#1"
|
|
||||||
]
|
|
||||||
|
|
||||||
WEB_SCAN_INFO_HEADER = [
|
|
||||||
"Vulnerability Category", "ID", "Detection ID", "QID", "Results", "Last Time Detected",
|
|
||||||
"Unique ID", "Flags", "Protocol", "Virtual Host", "IP", "Port", "Result",
|
|
||||||
"Info#1"
|
|
||||||
]
|
|
||||||
|
|
||||||
QID_HEADER = [
|
|
||||||
"QID", "Id", "Title", "Category", "Severity Level", "Groups", "OWASP", "WASC",
|
|
||||||
"CWE", "CVSS Base", "CVSS Temporal", "Description", "Impact", "Solution",
|
|
||||||
"CVSS V3 Base", "CVSS V3 Temporal", "CVSS V3 Attack Vector"
|
|
||||||
]
|
|
||||||
GROUP_HEADER = ['GROUP', 'Name', 'Category']
|
|
||||||
OWASP_HEADER = ['OWASP', 'Code', 'Name']
|
|
||||||
WASC_HEADER = ['WASC', 'Code', 'Name']
|
|
||||||
SCAN_META = [
|
|
||||||
"Web Application Name", "URL", "Owner", "Scope", "ID", "Tags",
|
|
||||||
"Custom Attributes"
|
|
||||||
]
|
|
||||||
CATEGORY_HEADER = ['Category', 'Severity', 'Level', 'Description']
|
|
||||||
|
|
||||||
def __init__(self, config=None, file_in=None,
|
|
||||||
file_stream=False, delimiter=',', quotechar='"'):
|
|
||||||
self.logger = logging.getLogger('qualysScanReport')
|
|
||||||
self.file_in = file_in
|
|
||||||
self.file_stream = file_stream
|
|
||||||
self.report = None
|
|
||||||
self.utils = qualysUtils()
|
|
||||||
|
|
||||||
if config:
|
|
||||||
try:
|
|
||||||
self.qw = qualysWhisperAPI(config=config)
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.error(
|
|
||||||
'Could not load config! Please check settings. Error: {}'.format(
|
|
||||||
str(e)))
|
|
||||||
|
|
||||||
if file_stream:
|
|
||||||
self.open_file = file_in.splitlines()
|
|
||||||
elif file_in:
|
|
||||||
self.open_file = open(file_in, 'rb')
|
|
||||||
|
|
||||||
self.downloaded_file = None
|
|
||||||
|
|
||||||
def grab_sections(self, report):
|
|
||||||
return {
|
|
||||||
'WEB_SCAN_VULN_BLOCK': pd.DataFrame(
|
|
||||||
self.utils.grab_section(
|
|
||||||
report,
|
|
||||||
self.WEB_SCAN_VULN_BLOCK,
|
|
||||||
end=[self.WEB_SCAN_SENSITIVE_BLOCK, self.WEB_SCAN_INFO_BLOCK],
|
|
||||||
pop_last=True),
|
|
||||||
columns=self.WEB_SCAN_HEADER),
|
|
||||||
'WEB_SCAN_SENSITIVE_BLOCK': pd.DataFrame(
|
|
||||||
self.utils.grab_section(report,
|
|
||||||
self.WEB_SCAN_SENSITIVE_BLOCK,
|
|
||||||
end=[self.WEB_SCAN_INFO_BLOCK, self.WEB_SCAN_SENSITIVE_BLOCK],
|
|
||||||
pop_last=True),
|
|
||||||
columns=self.WEB_SCAN_HEADER),
|
|
||||||
'WEB_SCAN_INFO_BLOCK': pd.DataFrame(
|
|
||||||
self.utils.grab_section(
|
|
||||||
report,
|
|
||||||
self.WEB_SCAN_INFO_BLOCK,
|
|
||||||
end=[self.QID_HEADER],
|
|
||||||
pop_last=True),
|
|
||||||
columns=self.WEB_SCAN_INFO_HEADER),
|
|
||||||
|
|
||||||
'QID_HEADER': pd.DataFrame(
|
|
||||||
self.utils.grab_section(
|
|
||||||
report,
|
|
||||||
self.QID_HEADER,
|
|
||||||
end=[self.GROUP_HEADER],
|
|
||||||
pop_last=True),
|
|
||||||
columns=self.QID_HEADER),
|
|
||||||
'GROUP_HEADER': pd.DataFrame(
|
|
||||||
self.utils.grab_section(
|
|
||||||
report,
|
|
||||||
self.GROUP_HEADER,
|
|
||||||
end=[self.OWASP_HEADER],
|
|
||||||
pop_last=True),
|
|
||||||
columns=self.GROUP_HEADER),
|
|
||||||
'OWASP_HEADER': pd.DataFrame(
|
|
||||||
self.utils.grab_section(
|
|
||||||
report,
|
|
||||||
self.OWASP_HEADER,
|
|
||||||
end=[self.WASC_HEADER],
|
|
||||||
pop_last=True),
|
|
||||||
columns=self.OWASP_HEADER),
|
|
||||||
'WASC_HEADER': pd.DataFrame(
|
|
||||||
self.utils.grab_section(
|
|
||||||
report,
|
|
||||||
self.WASC_HEADER,
|
|
||||||
end=[['APPENDIX']],
|
|
||||||
pop_last=True),
|
|
||||||
columns=self.WASC_HEADER),
|
|
||||||
'SCAN_META': pd.DataFrame(
|
|
||||||
self.utils.grab_section(report,
|
|
||||||
self.SCAN_META,
|
|
||||||
end=[self.CATEGORY_HEADER],
|
|
||||||
pop_last=True),
|
|
||||||
columns=self.SCAN_META),
|
|
||||||
'CATEGORY_HEADER': pd.DataFrame(
|
|
||||||
self.utils.grab_section(report,
|
|
||||||
self.CATEGORY_HEADER),
|
|
||||||
columns=self.CATEGORY_HEADER)
|
|
||||||
}
|
|
||||||
|
|
||||||
def data_normalizer(self, dataframes):
|
|
||||||
"""
|
|
||||||
Merge and clean data
|
|
||||||
:param dataframes:
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
df_dict = dataframes
|
|
||||||
merged_df = pd.concat([
|
|
||||||
df_dict['WEB_SCAN_VULN_BLOCK'],
|
|
||||||
df_dict['WEB_SCAN_SENSITIVE_BLOCK'],
|
|
||||||
df_dict['WEB_SCAN_INFO_BLOCK']
|
|
||||||
], axis=0, ignore_index=False)
|
|
||||||
|
|
||||||
merged_df = pd.merge(
|
|
||||||
merged_df,
|
|
||||||
df_dict['QID_HEADER'].drop(
|
|
||||||
#these columns always seem to be the same as what we're merging into
|
|
||||||
['CVSS V3 Attack Vector', 'CVSS V3 Base', 'CVSS V3 Temporal'],
|
|
||||||
axis=1),
|
|
||||||
left_on='QID', right_on='Id'
|
|
||||||
)
|
|
||||||
|
|
||||||
if 'Content' not in merged_df:
|
|
||||||
merged_df['Content'] = ''
|
|
||||||
|
|
||||||
columns_to_cleanse = ['Payload #1', 'Request Method #1', 'Request URL #1',
|
|
||||||
'Request Headers #1', 'Response #1', 'Evidence #1',
|
|
||||||
'Description', 'Impact', 'Solution', 'Url', 'Content']
|
|
||||||
|
|
||||||
for col in columns_to_cleanse:
|
|
||||||
merged_df[col] = merged_df[col].apply(self.utils.cleanser)
|
|
||||||
|
|
||||||
merged_df = merged_df.drop(['QID_y', 'QID_x'], axis=1)
|
|
||||||
merged_df = merged_df.rename(columns={'Id': 'QID'})
|
|
||||||
|
|
||||||
merged_df = merged_df.assign(**df_dict['SCAN_META'].to_dict(orient='records')[0])
|
|
||||||
|
|
||||||
merged_df = pd.merge(
|
|
||||||
merged_df, df_dict['CATEGORY_HEADER'],
|
|
||||||
how='left', left_on=['Category', 'Severity Level'],
|
|
||||||
right_on=['Category', 'Severity'], suffixes=('Severity', 'CatSev')
|
|
||||||
)
|
|
||||||
|
|
||||||
merged_df = merged_df.replace('N/A', '').fillna('')
|
|
||||||
|
|
||||||
try:
|
|
||||||
merged_df = \
|
|
||||||
merged_df[~merged_df.Title.str.contains('Links Crawled|External Links Discovered')]
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.error('Error normalizing: {}'.format(str(e)))
|
|
||||||
return merged_df
|
|
||||||
|
|
||||||
def download_file(self, path='', file_id=None):
|
|
||||||
report = self.qw.download_report(file_id)
|
|
||||||
filename = path + str(file_id) + '.csv'
|
|
||||||
file_out = open(filename, 'w')
|
|
||||||
for line in report.splitlines():
|
|
||||||
file_out.write(line + '\n')
|
|
||||||
file_out.close()
|
|
||||||
self.logger.info('File written to {}'.format(filename))
|
|
||||||
return filename
|
|
||||||
|
|
||||||
def process_data(self, path='', file_id=None, cleanup=True):
|
|
||||||
"""Downloads a file from qualys and normalizes it"""
|
|
||||||
|
|
||||||
download_file = self.download_file(path=path, file_id=file_id)
|
|
||||||
self.logger.info('Downloading file ID: {}'.format(file_id))
|
|
||||||
report_data = self.grab_sections(download_file)
|
|
||||||
merged_data = self.data_normalizer(report_data)
|
|
||||||
merged_data.sort_index(axis=1, inplace=True)
|
|
||||||
|
|
||||||
return merged_data
|
|
@ -1,18 +1,15 @@
|
|||||||
from __future__ import absolute_import
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from datetime import datetime, date
|
from datetime import datetime, date, timedelta
|
||||||
|
|
||||||
from jira import JIRA
|
from jira import JIRA
|
||||||
|
import requests
|
||||||
import logging
|
import logging
|
||||||
from bottle import template
|
from bottle import template
|
||||||
import re
|
import re
|
||||||
from six.moves import range
|
|
||||||
|
|
||||||
|
|
||||||
class JiraAPI(object):
|
class JiraAPI(object):
|
||||||
def __init__(self, hostname=None, username=None, password=None, path="", debug=False, clean_obsolete=True,
|
def __init__(self, hostname=None, username=None, password=None, path="", debug=False, clean_obsolete=True, max_time_window=12, decommission_time_window=3):
|
||||||
max_time_window=12, decommission_time_window=3):
|
|
||||||
self.logger = logging.getLogger('JiraAPI')
|
self.logger = logging.getLogger('JiraAPI')
|
||||||
if debug:
|
if debug:
|
||||||
self.logger.setLevel(logging.DEBUG)
|
self.logger.setLevel(logging.DEBUG)
|
||||||
@ -44,15 +41,10 @@ class JiraAPI(object):
|
|||||||
# deletes the tag "server_decommission" from those tickets closed <=3 months ago
|
# deletes the tag "server_decommission" from those tickets closed <=3 months ago
|
||||||
self.decommission_cleanup()
|
self.decommission_cleanup()
|
||||||
|
|
||||||
self.jira_still_vulnerable_comment = '''This ticket has been reopened due to the vulnerability not having been \
|
self.jira_still_vulnerable_comment = '''This ticket has been reopened due to the vulnerability not having been fixed (if multiple assets are affected, all need to be fixed; if the server is down, lastest known vulnerability might be the one reported).
|
||||||
fixed (if multiple assets are affected, all need to be fixed; if the server is down, lastest known \
|
- In the case of the team accepting the risk and wanting to close the ticket, please add the label "*risk_accepted*" to the ticket before closing it.
|
||||||
vulnerability might be the one reported).
|
- If server has been decommissioned, please add the label "*server_decommission*" to the ticket before closing it.
|
||||||
- In the case of the team accepting the risk and wanting to close the ticket, please add the label \
|
- If when checking the vulnerability it looks like a false positive, _+please elaborate in a comment+_ and add the label "*false_positive*" before closing it; we will review it and report it to the vendor.
|
||||||
"*risk_accepted*" to the ticket before closing it.
|
|
||||||
- If server has been decommissioned, please add the label "*server_decommission*" to the ticket before closing \
|
|
||||||
it.
|
|
||||||
- If when checking the vulnerability it looks like a false positive, _+please elaborate in a comment+_ and add \
|
|
||||||
the label "*false_positive*" before closing it; we will review it and report it to the vendor.
|
|
||||||
|
|
||||||
If you have further doubts, please contact the Security Team.'''
|
If you have further doubts, please contact the Security Team.'''
|
||||||
|
|
||||||
@ -99,15 +91,13 @@ class JiraAPI(object):
|
|||||||
return len(self.jira.search_issues(jql, maxResults=0))
|
return len(self.jira.search_issues(jql, maxResults=0))
|
||||||
|
|
||||||
def metrics_closed_tickets(self, project=None):
|
def metrics_closed_tickets(self, project=None):
|
||||||
jql = "labels= vulnerability_management and NOT resolution = Unresolved AND created >=startOfMonth(-{})".format(
|
jql = "labels= vulnerability_management and NOT resolution = Unresolved AND created >=startOfMonth(-{})".format(self.max_time_tracking)
|
||||||
self.max_time_tracking)
|
|
||||||
if project:
|
if project:
|
||||||
jql += " and (project='{}')".format(project)
|
jql += " and (project='{}')".format(project)
|
||||||
return len(self.jira.search_issues(jql, maxResults=0))
|
return len(self.jira.search_issues(jql, maxResults=0))
|
||||||
|
|
||||||
def sync(self, vulnerabilities, project, components=[]):
|
def sync(self, vulnerabilities, project, components=[]):
|
||||||
# JIRA structure of each vulnerability: [source, scan_name, title, diagnosis, consequence, solution,
|
#JIRA structure of each vulnerability: [source, scan_name, title, diagnosis, consequence, solution, ips, risk, references]
|
||||||
# ips, risk, references]
|
|
||||||
self.logger.info("JIRA Sync started")
|
self.logger.info("JIRA Sync started")
|
||||||
|
|
||||||
for vuln in vulnerabilities:
|
for vuln in vulnerabilities:
|
||||||
@ -116,8 +106,7 @@ class JiraAPI(object):
|
|||||||
if " " in vuln['scan_name']:
|
if " " in vuln['scan_name']:
|
||||||
vuln['scan_name'] = "_".join(vuln['scan_name'].split(" "))
|
vuln['scan_name'] = "_".join(vuln['scan_name'].split(" "))
|
||||||
|
|
||||||
# we exclude from the vulnerabilities to report those assets that already exist
|
# we exclude from the vulnerabilities to report those assets that already exist with *risk_accepted*/*server_decommission*
|
||||||
# with *risk_accepted*/*server_decommission*
|
|
||||||
vuln = self.exclude_accepted_assets(vuln)
|
vuln = self.exclude_accepted_assets(vuln)
|
||||||
|
|
||||||
# make sure after exclusion of risk_accepted assets there are still assets
|
# make sure after exclusion of risk_accepted assets there are still assets
|
||||||
@ -142,17 +131,13 @@ class JiraAPI(object):
|
|||||||
# create local text file with assets, attach it to ticket
|
# create local text file with assets, attach it to ticket
|
||||||
if len(vuln['ips']) > self.max_ips_ticket:
|
if len(vuln['ips']) > self.max_ips_ticket:
|
||||||
attachment_contents = vuln['ips']
|
attachment_contents = vuln['ips']
|
||||||
vuln['ips'] = [
|
vuln['ips'] = ["Affected hosts ({assets}) exceed Jira's allowed character limit, added as an attachment.".format(assets = len(attachment_contents))]
|
||||||
"Affected hosts ({assets}) exceed Jira's allowed character limit, added as an attachment.".format(
|
|
||||||
assets=len(attachment_contents))]
|
|
||||||
try:
|
try:
|
||||||
tpl = template(self.template_path, vuln)
|
tpl = template(self.template_path, vuln)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error('Exception templating: {}'.format(str(e)))
|
self.logger.error('Exception templating: {}'.format(str(e)))
|
||||||
return 0
|
return 0
|
||||||
self.create_ticket(title=vuln['title'], desc=tpl, project=project, components=components,
|
self.create_ticket(title=vuln['title'], desc=tpl, project=project, components=components, tags=[vuln['source'], vuln['scan_name'], 'vulnerability', vuln['risk']], attachment_contents = attachment_contents)
|
||||||
tags=[vuln['source'], vuln['scan_name'], 'vulnerability', vuln['risk']],
|
|
||||||
attachment_contents=attachment_contents)
|
|
||||||
else:
|
else:
|
||||||
self.logger.info("Ignoring vulnerability as all assets are already reported in a risk_accepted ticket")
|
self.logger.info("Ignoring vulnerability as all assets are already reported in a risk_accepted ticket")
|
||||||
|
|
||||||
@ -168,8 +153,7 @@ class JiraAPI(object):
|
|||||||
labels = [vuln['source'], vuln['scan_name'], 'vulnerability_management', 'vulnerability']
|
labels = [vuln['source'], vuln['scan_name'], 'vulnerability_management', 'vulnerability']
|
||||||
|
|
||||||
if not self.excluded_tickets:
|
if not self.excluded_tickets:
|
||||||
jql = "{} AND labels in (risk_accepted,server_decommission, false_positive) AND NOT labels=advisory AND created >=startOfMonth(-{})".format(
|
jql = "{} AND labels in (risk_accepted,server_decommission, false_positive) AND NOT labels=advisory AND created >=startOfMonth(-{})".format(" AND ".join(["labels={}".format(label) for label in labels]), self.max_time_tracking)
|
||||||
" AND ".join(["labels={}".format(label) for label in labels]), self.max_time_tracking)
|
|
||||||
self.excluded_tickets = self.jira.search_issues(jql, maxResults=0)
|
self.excluded_tickets = self.jira.search_issues(jql, maxResults=0)
|
||||||
|
|
||||||
title = vuln['title']
|
title = vuln['title']
|
||||||
@ -179,8 +163,7 @@ class JiraAPI(object):
|
|||||||
assets_to_exclude = []
|
assets_to_exclude = []
|
||||||
tickets_excluded_assets = []
|
tickets_excluded_assets = []
|
||||||
for index in range(len(self.excluded_tickets)):
|
for index in range(len(self.excluded_tickets)):
|
||||||
checking_ticketid, checking_title, checking_assets = self.ticket_get_unique_fields(
|
checking_ticketid, checking_title, checking_assets = self.ticket_get_unique_fields(self.excluded_tickets[index])
|
||||||
self.excluded_tickets[index])
|
|
||||||
if title.encode('ascii') == checking_title.encode('ascii'):
|
if title.encode('ascii') == checking_title.encode('ascii'):
|
||||||
if checking_assets:
|
if checking_assets:
|
||||||
#checking_assets is a list, we add to our full list for later delete all assets
|
#checking_assets is a list, we add to our full list for later delete all assets
|
||||||
@ -189,8 +172,7 @@ class JiraAPI(object):
|
|||||||
|
|
||||||
if assets_to_exclude:
|
if assets_to_exclude:
|
||||||
assets_to_remove = []
|
assets_to_remove = []
|
||||||
self.logger.warn("Vulnerable Assets seen on an already existing risk_accepted Jira ticket: {}".format(
|
self.logger.warn("Vulnerable Assets seen on an already existing risk_accepted Jira ticket: {}".format(', '.join(tickets_excluded_assets)))
|
||||||
', '.join(tickets_excluded_assets)))
|
|
||||||
self.logger.debug("Original assets: {}".format(vuln['ips']))
|
self.logger.debug("Original assets: {}".format(vuln['ips']))
|
||||||
#assets in vulnerability have the structure "ip - hostname - port", so we need to match by partial
|
#assets in vulnerability have the structure "ip - hostname - port", so we need to match by partial
|
||||||
for exclusion in assets_to_exclude:
|
for exclusion in assets_to_exclude:
|
||||||
@ -198,9 +180,7 @@ class JiraAPI(object):
|
|||||||
# and we don't want it to affect the rest of the processing (otherwise, it would miss the asset right after the removed one)
|
# and we don't want it to affect the rest of the processing (otherwise, it would miss the asset right after the removed one)
|
||||||
for index in range(len(vuln['ips']))[::-1]:
|
for index in range(len(vuln['ips']))[::-1]:
|
||||||
if exclusion == vuln['ips'][index].split(" - ")[0]:
|
if exclusion == vuln['ips'][index].split(" - ")[0]:
|
||||||
self.logger.debug(
|
self.logger.debug("Deleting asset {} from vulnerability {}, seen in risk_accepted.".format(vuln['ips'][index], title))
|
||||||
"Deleting asset {} from vulnerability {}, seen in risk_accepted.".format(vuln['ips'][index],
|
|
||||||
title))
|
|
||||||
vuln['ips'].pop(index)
|
vuln['ips'].pop(index)
|
||||||
self.logger.debug("Modified assets: {}".format(vuln['ips']))
|
self.logger.debug("Modified assets: {}".format(vuln['ips']))
|
||||||
|
|
||||||
@ -222,8 +202,7 @@ class JiraAPI(object):
|
|||||||
self.logger.info("Retrieving all JIRA tickets with the following tags {}".format(labels))
|
self.logger.info("Retrieving all JIRA tickets with the following tags {}".format(labels))
|
||||||
# we want to check all JIRA tickets, to include tickets moved to other queues
|
# we want to check all JIRA tickets, to include tickets moved to other queues
|
||||||
# will exclude tickets older than 12 months, old tickets will get closed for higiene and recreated if still vulnerable
|
# will exclude tickets older than 12 months, old tickets will get closed for higiene and recreated if still vulnerable
|
||||||
jql = "{} AND NOT labels=advisory AND created >=startOfMonth(-{})".format(
|
jql = "{} AND NOT labels=advisory AND created >=startOfMonth(-{})".format(" AND ".join(["labels={}".format(label) for label in labels]), self.max_time_tracking)
|
||||||
" AND ".join(["labels={}".format(label) for label in labels]), self.max_time_tracking)
|
|
||||||
|
|
||||||
self.all_tickets = self.jira.search_issues(jql, maxResults=0)
|
self.all_tickets = self.jira.search_issues(jql, maxResults=0)
|
||||||
|
|
||||||
@ -233,8 +212,7 @@ class JiraAPI(object):
|
|||||||
for index in range(len(self.all_tickets)):
|
for index in range(len(self.all_tickets)):
|
||||||
checking_ticketid, checking_title, checking_assets = self.ticket_get_unique_fields(self.all_tickets[index])
|
checking_ticketid, checking_title, checking_assets = self.ticket_get_unique_fields(self.all_tickets[index])
|
||||||
# added "not risk_accepted", as if it is risk_accepted, we will create a new ticket excluding the accepted assets
|
# added "not risk_accepted", as if it is risk_accepted, we will create a new ticket excluding the accepted assets
|
||||||
if title.encode('ascii') == checking_title.encode('ascii') and not self.is_risk_accepted(
|
if title.encode('ascii') == checking_title.encode('ascii') and not self.is_risk_accepted(self.jira.issue(checking_ticketid)):
|
||||||
self.jira.issue(checking_ticketid)):
|
|
||||||
difference = list(set(assets).symmetric_difference(checking_assets))
|
difference = list(set(assets).symmetric_difference(checking_assets))
|
||||||
#to check intersection - set(assets) & set(checking_assets)
|
#to check intersection - set(assets) & set(checking_assets)
|
||||||
if difference:
|
if difference:
|
||||||
@ -248,47 +226,32 @@ class JiraAPI(object):
|
|||||||
def ticket_get_unique_fields(self, ticket):
|
def ticket_get_unique_fields(self, ticket):
|
||||||
title = ticket.raw.get('fields', {}).get('summary').encode("ascii").strip()
|
title = ticket.raw.get('fields', {}).get('summary').encode("ascii").strip()
|
||||||
ticketid = ticket.key.encode("ascii")
|
ticketid = ticket.key.encode("ascii")
|
||||||
|
assets = []
|
||||||
|
try:
|
||||||
|
affected_assets_section = ticket.raw.get('fields', {}).get('description').encode("ascii").split("{panel:title=Affected Assets}")[1].split("{panel}")[0]
|
||||||
|
assets = list(set(re.findall(r"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b", affected_assets_section)))
|
||||||
|
|
||||||
assets = self.get_assets_from_description(ticket)
|
except Exception as e:
|
||||||
|
self.logger.error("Ticket IPs regex failed. Ticket ID: {}. Reason: {}".format(ticketid, e))
|
||||||
|
assets = []
|
||||||
|
|
||||||
|
try:
|
||||||
if not assets:
|
if not assets:
|
||||||
#check if attachment, if so, get assets from attachment
|
#check if attachment, if so, get assets from attachment
|
||||||
assets = self.get_assets_from_attachment(ticket)
|
affected_assets_section = self.check_ips_attachment(ticket)
|
||||||
|
if affected_assets_section:
|
||||||
|
assets = list(set(re.findall(r"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b", affected_assets_section)))
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error("Ticket IPs Attachment regex failed. Ticket ID: {}. Reason: {}".format(ticketid, e))
|
||||||
|
|
||||||
return ticketid, title, assets
|
return ticketid, title, assets
|
||||||
|
|
||||||
def get_assets_from_description(self, ticket, _raw=False):
|
def check_ips_attachment(self, ticket):
|
||||||
# Get the assets as a string "host - protocol/port - hostname" separated by "\n"
|
affected_assets_section = []
|
||||||
# structure the text to have the same structure as the assets from the attachment
|
|
||||||
affected_assets = ""
|
|
||||||
try:
|
|
||||||
affected_assets = \
|
|
||||||
ticket.raw.get('fields', {}).get('description').encode("ascii").split("{panel:title=Affected Assets}")[
|
|
||||||
1].split("{panel}")[0].replace('\n', '').replace(' * ', '\n').replace('\n', '', 1)
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.error(
|
|
||||||
"Unable to process the Ticket's 'Affected Assets'. Ticket ID: {}. Reason: {}".format(ticket, e))
|
|
||||||
|
|
||||||
if affected_assets:
|
|
||||||
if _raw:
|
|
||||||
# from line 406 check if the text in the panel corresponds to having added an attachment
|
|
||||||
if "added as an attachment" in affected_assets:
|
|
||||||
return False
|
|
||||||
return affected_assets
|
|
||||||
|
|
||||||
try:
|
|
||||||
# if _raw is not true, we return only the IPs of the affected assets
|
|
||||||
return list(set(re.findall(r"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b", affected_assets)))
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.error("Ticket IPs regex failed. Ticket ID: {}. Reason: {}".format(ticket, e))
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get_assets_from_attachment(self, ticket, _raw=False):
|
|
||||||
# Get the assets as a string "host - protocol/port - hostname" separated by "\n"
|
|
||||||
affected_assets = []
|
|
||||||
try:
|
try:
|
||||||
fields = self.jira.issue(ticket.key).raw.get('fields', {})
|
fields = self.jira.issue(ticket.key).raw.get('fields', {})
|
||||||
attachments = fields.get('attachment', {})
|
attachments = fields.get('attachment', {})
|
||||||
affected_assets = ""
|
affected_assets_section = ""
|
||||||
#we will make sure we get the latest version of the file
|
#we will make sure we get the latest version of the file
|
||||||
latest = ''
|
latest = ''
|
||||||
attachment_id = ''
|
attachment_id = ''
|
||||||
@ -302,45 +265,12 @@ class JiraAPI(object):
|
|||||||
if latest < item.get('created'):
|
if latest < item.get('created'):
|
||||||
latest = item.get('created')
|
latest = item.get('created')
|
||||||
attachment_id = item.get('id')
|
attachment_id = item.get('id')
|
||||||
affected_assets = self.jira.attachment(attachment_id).get()
|
affected_assets_section = self.jira.attachment(attachment_id).get()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(
|
self.logger.error("Failed to get assets from ticket attachment. Ticket ID: {}. Reason: {}".format(ticket, e))
|
||||||
"Failed to get assets from ticket attachment. Ticket ID: {}. Reason: {}".format(ticket, e))
|
|
||||||
|
|
||||||
if affected_assets:
|
return affected_assets_section
|
||||||
if _raw:
|
|
||||||
return affected_assets
|
|
||||||
|
|
||||||
try:
|
|
||||||
# if _raw is not true, we return only the IPs of the affected assets
|
|
||||||
affected_assets = list(set(re.findall(r"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b", affected_assets)))
|
|
||||||
return affected_assets
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.error("Ticket IPs Attachment regex failed. Ticket ID: {}. Reason: {}".format(ticket, e))
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
def parse_asset_to_json(self, asset):
|
|
||||||
hostname, protocol, port = "", "", ""
|
|
||||||
asset_info = asset.split(" - ")
|
|
||||||
ip = asset_info[0]
|
|
||||||
proto_port = asset_info[1]
|
|
||||||
# in case there is some case where hostname is not reported at all
|
|
||||||
if len(asset_info) == 3:
|
|
||||||
hostname = asset_info[2]
|
|
||||||
if proto_port != "N/A/N/A":
|
|
||||||
protocol, port = proto_port.split("/")
|
|
||||||
port = int(float(port))
|
|
||||||
|
|
||||||
asset_dict = {
|
|
||||||
"host": ip,
|
|
||||||
"protocol": protocol,
|
|
||||||
"port": port,
|
|
||||||
"hostname": hostname
|
|
||||||
}
|
|
||||||
|
|
||||||
return asset_dict
|
|
||||||
|
|
||||||
def clean_old_attachments(self, ticket):
|
def clean_old_attachments(self, ticket):
|
||||||
fields = ticket.raw.get('fields')
|
fields = ticket.raw.get('fields')
|
||||||
@ -379,10 +309,8 @@ class JiraAPI(object):
|
|||||||
if self.is_ticket_resolved(ticket_obj):
|
if self.is_ticket_resolved(ticket_obj):
|
||||||
ticket_data = ticket_obj.raw.get('fields')
|
ticket_data = ticket_obj.raw.get('fields')
|
||||||
#dates follow format '2018-11-06T10:36:13.849+0100'
|
#dates follow format '2018-11-06T10:36:13.849+0100'
|
||||||
created = [int(x) for x in
|
created = [int(x) for x in ticket_data['created'].split('.')[0].replace('T', '-').replace(':','-').split('-')]
|
||||||
ticket_data['created'].split('.')[0].replace('T', '-').replace(':', '-').split('-')]
|
resolved =[int(x) for x in ticket_data['resolutiondate'].split('.')[0].replace('T', '-').replace(':','-').split('-')]
|
||||||
resolved = [int(x) for x in
|
|
||||||
ticket_data['resolutiondate'].split('.')[0].replace('T', '-').replace(':', '-').split('-')]
|
|
||||||
|
|
||||||
start = datetime(created[0],created[1],created[2],created[3],created[4],created[5])
|
start = datetime(created[0],created[1],created[2],created[3],created[4],created[5])
|
||||||
end = datetime(resolved[0],resolved[1],resolved[2],resolved[3],resolved[4],resolved[5])
|
end = datetime(resolved[0],resolved[1],resolved[2],resolved[3],resolved[4],resolved[5])
|
||||||
@ -433,9 +361,7 @@ class JiraAPI(object):
|
|||||||
attachment_contents = []
|
attachment_contents = []
|
||||||
if len(vuln['ips']) > self.max_ips_ticket:
|
if len(vuln['ips']) > self.max_ips_ticket:
|
||||||
attachment_contents = vuln['ips']
|
attachment_contents = vuln['ips']
|
||||||
vuln['ips'] = [
|
vuln['ips'] = ["Affected hosts ({assets}) exceed Jira's allowed character limit, added as an attachment.".format(assets = len(attachment_contents))]
|
||||||
"Affected hosts ({assets}) exceed Jira's allowed character limit, added as an attachment.".format(
|
|
||||||
assets=len(attachment_contents))]
|
|
||||||
|
|
||||||
#fill the ticket description template
|
#fill the ticket description template
|
||||||
try:
|
try:
|
||||||
@ -455,8 +381,7 @@ class JiraAPI(object):
|
|||||||
self.logger.info("Ticket {} updated successfully".format(ticketid))
|
self.logger.info("Ticket {} updated successfully".format(ticketid))
|
||||||
self.add_label(ticketid, 'updated')
|
self.add_label(ticketid, 'updated')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(
|
self.logger.error("Error while trying up update ticket {ticketid}.\nReason: {e}".format(ticketid = ticketid, e=e))
|
||||||
"Error while trying up update ticket {ticketid}.\nReason: {e}".format(ticketid=ticketid, e=e))
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def add_label(self, ticketid, label):
|
def add_label(self, ticketid, label):
|
||||||
@ -468,9 +393,8 @@ class JiraAPI(object):
|
|||||||
try:
|
try:
|
||||||
ticket_obj.update(fields={"labels":ticket_obj.fields.labels})
|
ticket_obj.update(fields={"labels":ticket_obj.fields.labels})
|
||||||
self.logger.info("Added label {label} to ticket {ticket}".format(label=label, ticket=ticketid))
|
self.logger.info("Added label {label} to ticket {ticket}".format(label=label, ticket=ticketid))
|
||||||
except Exception as e:
|
except:
|
||||||
self.logger.error(
|
self.logger.error("Error while trying to add label {label} to ticket {ticket}".format(label=label, ticket=ticketid))
|
||||||
"Error while trying to add label {label} to ticket {ticket}".format(label=label, ticket=ticketid))
|
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@ -483,9 +407,8 @@ class JiraAPI(object):
|
|||||||
try:
|
try:
|
||||||
ticket_obj.update(fields={"labels":ticket_obj.fields.labels})
|
ticket_obj.update(fields={"labels":ticket_obj.fields.labels})
|
||||||
self.logger.info("Removed label {label} from ticket {ticket}".format(label=label, ticket=ticketid))
|
self.logger.info("Removed label {label} from ticket {ticket}".format(label=label, ticket=ticketid))
|
||||||
except Exception as e:
|
except:
|
||||||
self.logger.error("Error while trying to remove label {label} to ticket {ticket}".format(label=label,
|
self.logger.error("Error while trying to remove label {label} to ticket {ticket}".format(label=label, ticket=ticketid))
|
||||||
ticket=ticketid))
|
|
||||||
else:
|
else:
|
||||||
self.logger.error("Error: label {label} not in ticket {ticket}".format(label=label, ticket=ticketid))
|
self.logger.error("Error: label {label} not in ticket {ticket}".format(label=label, ticket=ticketid))
|
||||||
|
|
||||||
@ -511,6 +434,7 @@ class JiraAPI(object):
|
|||||||
self.close_ticket(ticket, self.JIRA_RESOLUTION_FIXED, comment)
|
self.close_ticket(ticket, self.JIRA_RESOLUTION_FIXED, comment)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def is_ticket_reopenable(self, ticket_obj):
|
def is_ticket_reopenable(self, ticket_obj):
|
||||||
transitions = self.jira.transitions(ticket_obj)
|
transitions = self.jira.transitions(ticket_obj)
|
||||||
for transition in transitions:
|
for transition in transitions:
|
||||||
@ -539,6 +463,7 @@ class JiraAPI(object):
|
|||||||
self.logger.debug("Checked ticket {} is already open".format(ticket_obj))
|
self.logger.debug("Checked ticket {} is already open".format(ticket_obj))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def is_risk_accepted(self, ticket_obj):
|
def is_risk_accepted(self, ticket_obj):
|
||||||
if ticket_obj is not None:
|
if ticket_obj is not None:
|
||||||
if ticket_obj.raw['fields'].get('labels') is not None:
|
if ticket_obj.raw['fields'].get('labels') is not None:
|
||||||
@ -564,8 +489,7 @@ class JiraAPI(object):
|
|||||||
if (not self.is_risk_accepted(ticket_obj) or ignore_labels):
|
if (not self.is_risk_accepted(ticket_obj) or ignore_labels):
|
||||||
try:
|
try:
|
||||||
if self.is_ticket_reopenable(ticket_obj):
|
if self.is_ticket_reopenable(ticket_obj):
|
||||||
error = self.jira.transition_issue(issue=ticketid, transition=self.JIRA_REOPEN_ISSUE,
|
error = self.jira.transition_issue(issue=ticketid, transition=self.JIRA_REOPEN_ISSUE, comment = comment)
|
||||||
comment=comment)
|
|
||||||
self.logger.info("Ticket {} reopened successfully".format(ticketid))
|
self.logger.info("Ticket {} reopened successfully".format(ticketid))
|
||||||
if not ignore_labels:
|
if not ignore_labels:
|
||||||
self.add_label(ticketid, 'reopened')
|
self.add_label(ticketid, 'reopened')
|
||||||
@ -585,8 +509,7 @@ class JiraAPI(object):
|
|||||||
if self.is_ticket_closeable(ticket_obj):
|
if self.is_ticket_closeable(ticket_obj):
|
||||||
#need to add the label before closing the ticket
|
#need to add the label before closing the ticket
|
||||||
self.add_label(ticketid, 'closed')
|
self.add_label(ticketid, 'closed')
|
||||||
error = self.jira.transition_issue(issue=ticketid, transition=self.JIRA_CLOSE_ISSUE,
|
error = self.jira.transition_issue(issue=ticketid, transition=self.JIRA_CLOSE_ISSUE, comment = comment, resolution = {"name": resolution })
|
||||||
comment=comment, resolution={"name": resolution})
|
|
||||||
self.logger.info("Ticket {} closed successfully".format(ticketid))
|
self.logger.info("Ticket {} closed successfully".format(ticketid))
|
||||||
return 1
|
return 1
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -599,8 +522,7 @@ class JiraAPI(object):
|
|||||||
def close_obsolete_tickets(self):
|
def close_obsolete_tickets(self):
|
||||||
# Close tickets older than 12 months, vulnerabilities not solved will get created a new ticket
|
# Close tickets older than 12 months, vulnerabilities not solved will get created a new ticket
|
||||||
self.logger.info("Closing obsolete tickets older than {} months".format(self.max_time_tracking))
|
self.logger.info("Closing obsolete tickets older than {} months".format(self.max_time_tracking))
|
||||||
jql = "labels=vulnerability_management AND NOT labels=advisory AND created <startOfMonth(-{}) and resolution=Unresolved".format(
|
jql = "labels=vulnerability_management AND created <startOfMonth(-{}) and resolution=Unresolved".format(self.max_time_tracking)
|
||||||
self.max_time_tracking)
|
|
||||||
tickets_to_close = self.jira.search_issues(jql, maxResults=0)
|
tickets_to_close = self.jira.search_issues(jql, maxResults=0)
|
||||||
|
|
||||||
comment = '''This ticket is being closed for hygiene, as it is more than {} months old.
|
comment = '''This ticket is being closed for hygiene, as it is more than {} months old.
|
||||||
@ -631,36 +553,9 @@ class JiraAPI(object):
|
|||||||
return True
|
return True
|
||||||
try:
|
try:
|
||||||
self.logger.info("Saving locally tickets from the last {} months".format(self.max_time_tracking))
|
self.logger.info("Saving locally tickets from the last {} months".format(self.max_time_tracking))
|
||||||
jql = "labels=vulnerability_management AND NOT labels=advisory AND created >=startOfMonth(-{})".format(
|
jql = "labels=vulnerability_management AND created >=startOfMonth(-{})".format(self.max_time_tracking)
|
||||||
self.max_time_tracking)
|
|
||||||
tickets_data = self.jira.search_issues(jql, maxResults=0)
|
tickets_data = self.jira.search_issues(jql, maxResults=0)
|
||||||
|
|
||||||
# TODO process tickets, creating a new field called "_metadata" with all the affected assets well structured
|
|
||||||
# for future processing in ELK/Splunk; this includes downloading attachments with assets and processing them
|
|
||||||
|
|
||||||
processed_tickets = []
|
|
||||||
|
|
||||||
for ticket in tickets_data:
|
|
||||||
assets = self.get_assets_from_description(ticket, _raw=True)
|
|
||||||
if not assets:
|
|
||||||
# check if attachment, if so, get assets from attachment
|
|
||||||
assets = self.get_assets_from_attachment(ticket, _raw=True)
|
|
||||||
# process the affected assets to save them as json structure on a new field from the JSON
|
|
||||||
_metadata = {"affected_hosts": []}
|
|
||||||
if assets:
|
|
||||||
if "\n" in assets:
|
|
||||||
for asset in assets.split("\n"):
|
|
||||||
assets_json = self.parse_asset_to_json(asset)
|
|
||||||
_metadata["affected_hosts"].append(assets_json)
|
|
||||||
else:
|
|
||||||
assets_json = self.parse_asset_to_json(assets)
|
|
||||||
_metadata["affected_hosts"].append(assets_json)
|
|
||||||
|
|
||||||
temp_ticket = ticket.raw.get('fields')
|
|
||||||
temp_ticket['_metadata'] = _metadata
|
|
||||||
|
|
||||||
processed_tickets.append(temp_ticket)
|
|
||||||
|
|
||||||
#end of line needed, as writelines() doesn't add it automatically, otherwise one big line
|
#end of line needed, as writelines() doesn't add it automatically, otherwise one big line
|
||||||
to_save = [json.dumps(ticket.raw.get('fields'))+"\n" for ticket in tickets_data]
|
to_save = [json.dumps(ticket.raw.get('fields'))+"\n" for ticket in tickets_data]
|
||||||
with open(fname, 'w') as outfile:
|
with open(fname, 'w') as outfile:
|
||||||
@ -680,16 +575,13 @@ class JiraAPI(object):
|
|||||||
closed already for more than x months (default is 3 months) in order to clean solved issues
|
closed already for more than x months (default is 3 months) in order to clean solved issues
|
||||||
for statistics purposes
|
for statistics purposes
|
||||||
'''
|
'''
|
||||||
self.logger.info("Deleting 'server_decommission' tag from tickets closed more than {} months ago".format(
|
self.logger.info("Deleting 'server_decommission' tag from tickets closed more than {} months ago".format(self.max_decommission_time))
|
||||||
self.max_decommission_time))
|
|
||||||
|
|
||||||
jql = "labels=vulnerability_management AND labels=server_decommission and resolutiondate <=startOfMonth(-{})".format(
|
jql = "labels=vulnerability_management AND labels=server_decommission and resolutiondate <=startOfMonth(-{})".format(self.max_decommission_time)
|
||||||
self.max_decommission_time)
|
|
||||||
decommissioned_tickets = self.jira.search_issues(jql, maxResults=0)
|
decommissioned_tickets = self.jira.search_issues(jql, maxResults=0)
|
||||||
|
|
||||||
comment = '''This ticket is having deleted the *server_decommission* tag, as it is more than {} months old and is expected to already have been decommissioned.
|
comment = '''This ticket is having deleted the *server_decommission* tag, as it is more than {} months old and is expected to already have been decommissioned.
|
||||||
If that is not the case and the vulnerability still exists, the vulnerability will be opened again.'''.format(
|
If that is not the case and the vulnerability still exists, the vulnerability will be opened again.'''.format(self.max_decommission_time)
|
||||||
self.max_decommission_time)
|
|
||||||
|
|
||||||
for ticket in decommissioned_tickets:
|
for ticket in decommissioned_tickets:
|
||||||
#we open first the ticket, as we want to make sure the process is not blocked due to
|
#we open first the ticket, as we want to make sure the process is not blocked due to
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from __future__ import absolute_import
|
|
||||||
import os
|
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
import httpretty
|
import httpretty
|
||||||
|
|
||||||
|
|
||||||
@ -17,28 +17,36 @@ class mockAPI(object):
|
|||||||
self.logger.setLevel(logging.DEBUG)
|
self.logger.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
self.logger.info('mockAPI initialised, API requests will be mocked')
|
self.logger.info('mockAPI initialised, API requests will be mocked')
|
||||||
self.logger.debug('Test path resolved as {}'.format(self.mock_dir))
|
self.logger.info('Test path resolved as {}'.format(self.mock_dir))
|
||||||
|
|
||||||
def get_directories(self, path):
|
def get_directories(self, path):
|
||||||
dir, subdirs, files = next(os.walk(path))
|
dir, subdirs, files = next(os.walk(path))
|
||||||
self.logger.debug('Subdirectories found: {}'.format(subdirs))
|
return sorted(subdirs)
|
||||||
return subdirs
|
|
||||||
|
|
||||||
def get_files(self, path):
|
def get_files(self, path):
|
||||||
dir, subdirs, files = next(os.walk(path))
|
dir, subdirs, files = next(os.walk(path))
|
||||||
self.logger.debug('Files found: {}'.format(files))
|
return sorted(files)
|
||||||
return files
|
|
||||||
|
|
||||||
def qualys_vuln_callback(self, request, uri, response_headers):
|
def create_nessus_resource(self, framework):
|
||||||
self.logger.debug('Simulating response for {} ({})'.format(uri, request.body))
|
for filename in self.get_files('{}/{}'.format(self.mock_dir, framework)):
|
||||||
|
method, resource = filename.split('_', 1)
|
||||||
|
resource = resource.replace('_', '/')
|
||||||
|
self.logger.info('Adding mocked {} endpoint {} {}'.format(framework, method, resource))
|
||||||
|
httpretty.register_uri(
|
||||||
|
getattr(httpretty, method), 'https://{}:443/{}'.format(framework, resource),
|
||||||
|
body=open('{}/{}/{}'.format(self.mock_dir, framework, filename)).read()
|
||||||
|
)
|
||||||
|
|
||||||
|
def qualys_vm_callback(self, request, uri, response_headers):
|
||||||
|
self.logger.info('Simulating response for {} ({})'.format(uri, request.body))
|
||||||
if 'list' in request.parsed_body['action']:
|
if 'list' in request.parsed_body['action']:
|
||||||
return [200,
|
return [200,
|
||||||
response_headers,
|
response_headers,
|
||||||
open('{}/{}'.format(self.qualys_vuln_path, 'scans')).read()]
|
open(self.qualys_vm_path + '/scans').read()]
|
||||||
elif 'fetch' in request.parsed_body['action']:
|
elif 'fetch' in request.parsed_body['action']:
|
||||||
try:
|
try:
|
||||||
response_body = open('{}/{}'.format(
|
response_body = open('{}/{}'.format(
|
||||||
self.qualys_vuln_path,
|
self.qualys_vm_path,
|
||||||
request.parsed_body['scan_ref'][0].replace('/', '_'))
|
request.parsed_body['scan_ref'][0].replace('/', '_'))
|
||||||
).read()
|
).read()
|
||||||
except:
|
except:
|
||||||
@ -46,34 +54,75 @@ class mockAPI(object):
|
|||||||
response_body = ''
|
response_body = ''
|
||||||
return [200, response_headers, response_body]
|
return [200, response_headers, response_body]
|
||||||
|
|
||||||
def create_nessus_resource(self, framework):
|
def create_qualys_vm_resource(self, framework):
|
||||||
|
# Create health check endpoint
|
||||||
|
self.logger.info('Adding mocked {} endpoint GET msp/about.php'.format(framework))
|
||||||
|
httpretty.register_uri(
|
||||||
|
httpretty.GET,
|
||||||
|
'https://{}:443/msp/about.php'.format(framework),
|
||||||
|
body='')
|
||||||
|
|
||||||
|
self.logger.info('Adding mocked {} endpoint {} {}'.format(framework, 'POST', 'api/2.0/fo/scan'))
|
||||||
|
httpretty.register_uri(
|
||||||
|
httpretty.POST, 'https://{}:443/api/2.0/fo/scan/'.format(framework),
|
||||||
|
body=self.qualys_vm_callback)
|
||||||
|
|
||||||
|
def qualys_was_callback(self, request, uri, response_headers):
|
||||||
|
self.logger.info('Simulating response for {} ({})'.format(uri, request.body))
|
||||||
|
report_id = request.parsed_body.split('<WasScan><id>')[1].split('<')[0]
|
||||||
|
response_body = open('{}/create_{}'.format(self.qualys_was_path, report_id)).read()
|
||||||
|
return [200, response_headers, response_body]
|
||||||
|
|
||||||
|
def create_qualys_was_resource(self, framework):
|
||||||
for filename in self.get_files('{}/{}'.format(self.mock_dir, framework)):
|
for filename in self.get_files('{}/{}'.format(self.mock_dir, framework)):
|
||||||
|
if filename.startswith('POST') or filename.startswith('GET'):
|
||||||
method, resource = filename.split('_', 1)
|
method, resource = filename.split('_', 1)
|
||||||
resource = resource.replace('_', '/')
|
resource = resource.replace('_', '/')
|
||||||
self.logger.debug('Adding mocked {} endpoint {} {}'.format(framework, method, resource))
|
self.logger.info('Adding mocked {} endpoint {} {}'.format(framework, method, resource))
|
||||||
httpretty.register_uri(
|
httpretty.register_uri(
|
||||||
getattr(httpretty, method), 'https://{}:443/{}'.format(framework, resource),
|
getattr(httpretty, method), 'https://{}:443/{}'.format(framework, resource),
|
||||||
body=open('{}/{}/{}'.format(self.mock_dir, framework, filename)).read()
|
body=open('{}/{}/{}'.format(self.mock_dir, framework, filename)).read()
|
||||||
)
|
)
|
||||||
|
|
||||||
def create_qualys_vuln_resource(self, framework):
|
self.logger.info('Adding mocked {} endpoint {} {}'.format(framework, 'POST', 'qps/rest/3.0/create/was/report'))
|
||||||
# Create health check endpoint
|
|
||||||
self.logger.debug('Adding mocked {} endpoint {} {}'.format(framework, 'GET', 'msp/about.php'))
|
|
||||||
httpretty.register_uri(
|
httpretty.register_uri(
|
||||||
httpretty.GET,
|
httpretty.POST, 'https://{}:443/qps/rest/3.0/create/was/report'.format(framework),
|
||||||
'https://{}:443/{}'.format(framework, 'msp/about.php'),
|
body=self.qualys_was_callback)
|
||||||
body='')
|
|
||||||
|
|
||||||
self.logger.debug('Adding mocked {} endpoint {} {}'.format(framework, 'POST', 'api/2.0/fo/scan'))
|
def openvas_callback(self, request, uri, response_headers):
|
||||||
|
self.logger.info('Simulating response for {} ({})'.format(uri, request.body))
|
||||||
|
if request.querystring['cmd'][0] in ['get_reports', 'get_report_formats']:
|
||||||
|
response_body = open('{}/{}'.format(self.openvas_path, request.querystring['cmd'][0])).read()
|
||||||
|
|
||||||
|
if request.querystring['cmd'][0] == 'get_report':
|
||||||
|
response_body = open('{}/report_{}'.format(self.openvas_path, request.querystring['report_id'][0])).read()
|
||||||
|
|
||||||
|
return [200, response_headers, response_body]
|
||||||
|
|
||||||
|
def create_openvas_resource(self, framework):
|
||||||
|
# Create login endpoint
|
||||||
httpretty.register_uri(
|
httpretty.register_uri(
|
||||||
httpretty.POST, 'https://{}:443/{}'.format(framework, 'api/2.0/fo/scan/'),
|
httpretty.POST, 'https://{}:4000/omp'.format(framework),
|
||||||
body=self.qualys_vuln_callback)
|
body=open('{}/{}/{}'.format(self.mock_dir, framework, 'login')).read()
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create GET requests endpoint
|
||||||
|
httpretty.register_uri(
|
||||||
|
httpretty.GET, 'https://{}:4000/omp'.format(framework),
|
||||||
|
body=self.openvas_callback
|
||||||
|
)
|
||||||
|
|
||||||
def mock_endpoints(self):
|
def mock_endpoints(self):
|
||||||
for framework in self.get_directories(self.mock_dir):
|
for framework in self.get_directories(self.mock_dir):
|
||||||
if framework in ['nessus', 'tenable']:
|
if framework in ['nessus', 'tenable']:
|
||||||
self.create_nessus_resource(framework)
|
self.create_nessus_resource(framework)
|
||||||
elif framework == 'qualys_vuln':
|
elif framework == 'qualys_vm':
|
||||||
self.qualys_vuln_path = self.mock_dir + '/' + framework
|
self.qualys_vm_path = self.mock_dir + '/' + framework
|
||||||
self.create_qualys_vuln_resource(framework)
|
self.create_qualys_vm_resource(framework)
|
||||||
|
elif framework == 'qualys_was':
|
||||||
|
self.qualys_was_path = self.mock_dir + '/' + framework
|
||||||
|
self.create_qualys_was_resource(framework)
|
||||||
|
elif framework == 'openvas':
|
||||||
|
self.openvas_path = self.mock_dir + '/' + framework
|
||||||
|
self.create_openvas_resource(framework)
|
||||||
httpretty.enable()
|
httpretty.enable()
|
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user