From eecd08bd7cbe0ffaf4df4db0746c1c1440ec3f14 Mon Sep 17 00:00:00 2001 From: SamoKopecky Date: Tue, 27 Jul 2021 09:56:50 +0200 Subject: [PATCH 1/8] Make adding new tests easier Moved a dict out a function, so that when a new tests is added there is only one place where it needs to be added to. --- restapi.py | 2 +- tlstest.py => ssltest.py | 42 +++++++++++++++++++--------------------- 2 files changed, 21 insertions(+), 23 deletions(-) rename tlstest.py => ssltest.py (88%) diff --git a/restapi.py b/restapi.py index bd993e0..a6a9990 100755 --- a/restapi.py +++ b/restapi.py @@ -1,5 +1,5 @@ import json -from tlstest import tls_test +from ssltest import tls_test from flask import Flask from flask_restful import Resource, Api, reqparse diff --git a/tlstest.py b/ssltest.py similarity index 88% rename from tlstest.py rename to ssltest.py index 8f2095b..28cf5db 100755 --- a/tlstest.py +++ b/ssltest.py @@ -21,6 +21,16 @@ from scan_vulnerabilities.multitheard_scan import scan_vulnerabilities from fix_openssl_config import fix_openssl_config +tests_switcher = { + 1: (heartbleed.scan, 'Heartbleed'), + 2: (ccs_injection.scan, 'CCS injection'), + 3: (rene.scan, 'Insecure renegotiation'), + 4: (poodle.scan, 'ZombiePOODLE/GOLDENDOOLDE'), + 5: (session_ticket.scan, 'Session ticket support'), + 6: (crime.scan, 'CRIME'), + 7: (rc4_support.scan, 'RC4 support') +} + def tls_test(program_args): args = parse_options(program_args) @@ -61,17 +71,8 @@ def vulnerability_scan(address, tests, version): if not tests: return {} scans = [] - switcher = { - 1: (heartbleed.scan, 'Heartbleed'), - 2: (ccs_injection.scan, 'CCS injection'), - 3: (rene.scan, 'Insecure renegotiation'), - 4: (poodle.scan, 'ZombiePOODLE/GOLDENDOOLDE'), - 5: (session_ticket.scan, 'Session ticket support'), - 6: (crime.scan, 'CRIME'), - 7: (rc4_support.scan, 'RC4 support'), - } for test in tests: - scans.append(switcher.get(test)) + scans.append(tests_switcher.get(test)) return scan_vulnerabilities(scans, address, version) @@ -149,6 +150,12 @@ def parse_options(program_args): :return: object of parsed arguments """ + tests_help = 'test the server for a specified vulnerability\npossible vulnerabilities (separate with spaces):\n' + for key, value in tests_switcher.items(): + test_number = key + test_desc = value[1] + tests_help += f'{" " * 4}{test_number}: {test_desc}\n' + parser = argparse.ArgumentParser( usage='use -h or --help for more information', description='Script that scans a webservers cryptographic parameters and vulnerabilities', @@ -168,17 +175,7 @@ def parse_options(program_args): output is written to the given file ''')) parser.add_argument('-t', '--test', type=int, metavar='test_num', nargs='+', - help=textwrap.dedent('''\ - test the server for a specified vulnerability - possible vulnerabilities (separate with spaces): - 1: Heartbleed - 2: ChangeCipherSpec Injection - 3: Insecure renegotiation - 4: ZombiePOODLE/GOLDENDOODLE - 5: Session ticket support - 6: CRIME - 7: RC4 support - ''')) + help=textwrap.dedent(tests_help)) parser.add_argument('-fc', '--fix-conf', action='store_true', default=False, help=textwrap.dedent('''\ allow the use of older versions of TLS protocol @@ -199,7 +196,8 @@ def parse_options(program_args): def check_test_numbers(args, parser): if not args.test: return - unknown_tests = list(filter(lambda test: test not in [1, 2, 3, 4, 5, 6, 7], args.test)) + test_numbers = [test for test in tests_switcher.keys()] + unknown_tests = list(filter(lambda test: test not in test_numbers, args.test)) if unknown_tests: parser.print_usage() if len(unknown_tests) > 1: From 7b1cb205644cb1d0005e39f71392fe98466ca431 Mon Sep 17 00:00:00 2001 From: SamoKopecky Date: Tue, 27 Jul 2021 10:21:37 +0200 Subject: [PATCH 2/8] Remove webserver files and move test files Removed all of the web server stuff such as docker, restapi and flask Move tests to a separate folder for clarity --- Dockerfile | 16 ---- README.md | 4 +- README_SK.md | 40 --------- docker-compose.yaml | 8 -- restapi.py | 24 ----- .../tests}/__init__.py | 0 .../{ => tests}/ccs_injection.py | 2 +- scan_vulnerabilities/{ => tests}/crime.py | 2 +- .../{ => tests}/heartbleed.py | 2 +- .../{ => tests}/insec_renegotiation.py | 2 +- scan_vulnerabilities/{ => tests}/poodle.py | 5 +- .../{ => tests}/rc4_support.py | 2 +- .../{ => tests}/session_ticket.py | 2 +- server_app/server.py | 51 ----------- server_app/static/styles/style_query.css | 16 ---- server_app/static/styles/style_result.css | 8 -- server_app/templates/query_form.html | 90 ------------------- server_app/templates/query_result.html | 51 ----------- server_app/utils.py | 57 ------------ ssltest.py | 44 +++++---- start.sh | 66 -------------- 21 files changed, 36 insertions(+), 456 deletions(-) delete mode 100644 Dockerfile delete mode 100644 README_SK.md delete mode 100644 docker-compose.yaml delete mode 100755 restapi.py rename {server_app => scan_vulnerabilities/tests}/__init__.py (100%) rename scan_vulnerabilities/{ => tests}/ccs_injection.py (99%) rename scan_vulnerabilities/{ => tests}/crime.py (99%) rename scan_vulnerabilities/{ => tests}/heartbleed.py (99%) rename scan_vulnerabilities/{ => tests}/insec_renegotiation.py (99%) rename scan_vulnerabilities/{ => tests}/poodle.py (98%) rename scan_vulnerabilities/{ => tests}/rc4_support.py (99%) rename scan_vulnerabilities/{ => tests}/session_ticket.py (99%) delete mode 100755 server_app/server.py delete mode 100644 server_app/static/styles/style_query.css delete mode 100644 server_app/static/styles/style_result.css delete mode 100644 server_app/templates/query_form.html delete mode 100644 server_app/templates/query_result.html delete mode 100644 server_app/utils.py delete mode 100755 start.sh diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index bbe9554..0000000 --- a/Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -FROM python:3 - -WORKDIR /usr/src/app -SHELL ["/bin/bash", "-c"] -RUN rm /usr/bin/python3 && \ -ln -s /usr/local/bin/python3 /usr/bin/python3 - -COPY . ./ -RUN pip3 install --no-cache-dir -r requirements.txt -RUN apt-get update && \ -apt-get install -y nmap - -EXPOSE 5001 -EXPOSE 5000 - -CMD ["./start.sh", "-d", "-c"] diff --git a/README.md b/README.md index 6b2d55b..9a74a81 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,9 @@ # Run the standalone script -- Run the main file `tlstest.py` with option `-u` to enter the url. +- Run the main file `ssltest.py` with option `-u` to enter the url. - Use `-h` or `--help` for more help. -- Example: `./tlstest.py -u vutbr.cz` +- Example: `./ssltest.py -u vutbr.cz` # Prepare the environment diff --git a/README_SK.md b/README_SK.md deleted file mode 100644 index ba5e54c..0000000 --- a/README_SK.md +++ /dev/null @@ -1,40 +0,0 @@ -# Prehľad - -- Tento repozitár obsahuje: - - Samostatný skript, ktorý je možné spustiť v konzoli - - Aplikácia webového servera, ktorá umožňuje používať skript s GUI (ak je hostovaný lokálne na tejto [url](http://localhost:5000)) -- Súbor `resources/security_levels.json` je môžné editovať ak je potreba zmeniť bezpečnostné hodnoty parametrov - -# Spustenie samostatného skriptu - -- Spustenie hlavného súboru je možné z príkazom `tlstest.py` a z prepínačom `-u` po zadaní url web servera -- Prepínač `-h` alebo `--help` slúži na zobrazenie viacej informácií -- Príklad spustenia: `./tlstest.py -u vutbr.cz` - -# Pripravenie prostredia - -- Potrebné závislosti sa dajú nainštalovať buď na hostovaciom OS alebo cez vytvorenie docker kontajneru - -## Príprava hostovacieho OS - -- Pre spustenie skriptu alebo webovej aplikácie na hostovaciom OS, tieto závislosti sú potrebné -- Inštalácia potrebných python balíčkov je možná pomocou príkazu `pip3 install -r requirements.txt`, ktorý inštaluje: - - [cryptography](https://pypi.org/project/cryptography/) - - [pyopenssl](https://pypi.org/project/pyOpenSSL/) - - [python3-nmap](https://pypi.org/project/python3-nmap/) - - [requests](https://pypi.org/project/requests/) - - [urllib3](https://pypi.org/project/urllib3/) - - [Flask](https://pypi.org/project/Flask/) - - [flask-restful](https://pypi.org/project/Flask-RESTful/) -- Program nmap je potrebný pre niektoré funkcie programu, inštalácia je možná pomocou príkazu `apt install -y nmap` -- Spustenie webovej aplikácie spolu s rest api je možné pomocou spustenia skriptu príkazom `start.sh` alebo príkazom `script.sh -h` pre zobrazenie viacej informácií -- Spustenie samostatného skriptu je popísane na začiatku - -## Vytvorenie prostredia pomocou dockeru - -- Pre vytvorenie prostredia je potrebné nainštalovať, docker engine ([návod](https://docs.docker.com/engine/install/)) a docker - compose ([návod](https://docs.docker.com/compose/install/)) -- Vytvorenie docker kontajneru je možné pomocou príkazu `docker-compose up -d` spustením v root zložke projektu -- Webová aplikácie bude automaticky spustená a prostredie s potrebnými závislostami je prístupné pomocou príkazu `docker exec -it bp_flask_server /bin/bash` -- Samostatný skript je možné spustiť vo vytvorenom prostredí, spustenie skriptu je popísane na začiatku - diff --git a/docker-compose.yaml b/docker-compose.yaml deleted file mode 100644 index 8fc71ee..0000000 --- a/docker-compose.yaml +++ /dev/null @@ -1,8 +0,0 @@ -version: '3' -services: - flask_server: - build: - context: . - ports: - - 5000:5000 - - 5001:5001 diff --git a/restapi.py b/restapi.py deleted file mode 100755 index a6a9990..0000000 --- a/restapi.py +++ /dev/null @@ -1,24 +0,0 @@ -import json -from ssltest import tls_test -from flask import Flask -from flask_restful import Resource, Api, reqparse - -app = Flask(__name__) -api = Api(app) - - -class TlsTest(Resource): - def get(self): - args = parser.parse_args() - args = args['args'].split(' ') - args.append('-j') - out = tls_test(args) - return json.loads(out) - - -api.add_resource(TlsTest, '/') -parser = reqparse.RequestParser() -parser.add_argument('args', type=str) - -if __name__ == '__main__': - app.run(port=5001, host='0.0.0.0') diff --git a/server_app/__init__.py b/scan_vulnerabilities/tests/__init__.py similarity index 100% rename from server_app/__init__.py rename to scan_vulnerabilities/tests/__init__.py diff --git a/scan_vulnerabilities/ccs_injection.py b/scan_vulnerabilities/tests/ccs_injection.py similarity index 99% rename from scan_vulnerabilities/ccs_injection.py rename to scan_vulnerabilities/tests/ccs_injection.py index 4cb3eef..e5f3dd7 100644 --- a/scan_vulnerabilities/ccs_injection.py +++ b/scan_vulnerabilities/tests/ccs_injection.py @@ -1,4 +1,4 @@ -from .utils import * +from ..utils import * def construct_client_hello(version): diff --git a/scan_vulnerabilities/crime.py b/scan_vulnerabilities/tests/crime.py similarity index 99% rename from scan_vulnerabilities/crime.py rename to scan_vulnerabilities/tests/crime.py index fd1cf21..d3ae115 100644 --- a/scan_vulnerabilities/crime.py +++ b/scan_vulnerabilities/tests/crime.py @@ -1,4 +1,4 @@ -from .utils import * +from ..utils import * def construct_client_hello(version): diff --git a/scan_vulnerabilities/heartbleed.py b/scan_vulnerabilities/tests/heartbleed.py similarity index 99% rename from scan_vulnerabilities/heartbleed.py rename to scan_vulnerabilities/tests/heartbleed.py index a2b2eaf..0a6541a 100644 --- a/scan_vulnerabilities/heartbleed.py +++ b/scan_vulnerabilities/tests/heartbleed.py @@ -1,4 +1,4 @@ -from .utils import * +from ..utils import * def construct_client_hello(version): diff --git a/scan_vulnerabilities/insec_renegotiation.py b/scan_vulnerabilities/tests/insec_renegotiation.py similarity index 99% rename from scan_vulnerabilities/insec_renegotiation.py rename to scan_vulnerabilities/tests/insec_renegotiation.py index 12de157..4380986 100644 --- a/scan_vulnerabilities/insec_renegotiation.py +++ b/scan_vulnerabilities/tests/insec_renegotiation.py @@ -1,4 +1,4 @@ -from .utils import * +from ..utils import * renegotiation_extension = bytes([ 0xff, 0x01, 0x00, 0x01, 0x00 diff --git a/scan_vulnerabilities/poodle.py b/scan_vulnerabilities/tests/poodle.py similarity index 98% rename from scan_vulnerabilities/poodle.py rename to scan_vulnerabilities/tests/poodle.py index 68e79c2..a5539b6 100644 --- a/scan_vulnerabilities/poodle.py +++ b/scan_vulnerabilities/tests/poodle.py @@ -1,8 +1,5 @@ -import OpenSSL.SSL - -from .utils import * +from ..utils import * from OpenSSL import SSL -from time import sleep def construct_client_hello(version): diff --git a/scan_vulnerabilities/rc4_support.py b/scan_vulnerabilities/tests/rc4_support.py similarity index 99% rename from scan_vulnerabilities/rc4_support.py rename to scan_vulnerabilities/tests/rc4_support.py index 00cd41b..ed8350d 100644 --- a/scan_vulnerabilities/rc4_support.py +++ b/scan_vulnerabilities/tests/rc4_support.py @@ -1,4 +1,4 @@ -from .utils import * +from ..utils import * def construct_client_hello(version): diff --git a/scan_vulnerabilities/session_ticket.py b/scan_vulnerabilities/tests/session_ticket.py similarity index 99% rename from scan_vulnerabilities/session_ticket.py rename to scan_vulnerabilities/tests/session_ticket.py index 8a1b6ed..c83dba3 100644 --- a/scan_vulnerabilities/session_ticket.py +++ b/scan_vulnerabilities/tests/session_ticket.py @@ -1,4 +1,4 @@ -from .utils import * +from ..utils import * def construct_client_hello(version): diff --git a/server_app/server.py b/server_app/server.py deleted file mode 100755 index e698997..0000000 --- a/server_app/server.py +++ /dev/null @@ -1,51 +0,0 @@ -from utils import * -from flask import Flask, render_template, request, redirect, url_for -import json -import requests - -app = Flask(__name__) - - -@app.route('/', methods=['GET', 'POST']) -def index(): - args = ['-u'] - other_options = { - 'nmap_scan': '-ns', - 'nmap_discover': '-nd' - } - tests = { - 'heartbleed': '1', - 'ccsinjection': '2', - 'insecrene': '3', - 'poodle': '4', - 'sessionticket': '5', - 'crime': '6', - 'rc4support': '7', - } - if request.method == 'POST': - url = request.form['url'] - args.append(url) - args.extend(parse_checkboxes(other_options)) - parsed_tests = parse_checkboxes(tests) - if len(parsed_tests) > 0: - args.append('-t') - args.extend(parsed_tests) - args.extend(parse_list('ports', '-p')) - return redirect(url_for('result', args=' '.join(args))) - return render_template('query_form.html') - - -@app.route('/result/', methods=['GET', 'POST']) -def result(args=None): - if request.method == 'POST': - return redirect(url_for('index')) - if args is None: - return '' - response = requests.get(f'http://localhost:5001/?args={args}') - json_data = json.loads(response.content, object_hook=translate_keys) - remove_invalid_values(json_data) - return render_template('query_result.html', json_response=json_data) - - -if __name__ == '__main__': - app.run(port=5000, host='0.0.0.0') diff --git a/server_app/static/styles/style_query.css b/server_app/static/styles/style_query.css deleted file mode 100644 index 4854d36..0000000 --- a/server_app/static/styles/style_query.css +++ /dev/null @@ -1,16 +0,0 @@ -body { - width: 700px; - margin: auto; -} - -h2, h6 { - text-align: center; -} - -h2 { - margin-top: 10px; -} - -h6 { - margin-bottom: 20px ; -} \ No newline at end of file diff --git a/server_app/static/styles/style_result.css b/server_app/static/styles/style_result.css deleted file mode 100644 index a5acd5b..0000000 --- a/server_app/static/styles/style_result.css +++ /dev/null @@ -1,8 +0,0 @@ -body { - width: 1000px; - margin: auto; -} - -button { - margin-top: 5px; -} \ No newline at end of file diff --git a/server_app/templates/query_form.html b/server_app/templates/query_form.html deleted file mode 100644 index f260ebb..0000000 --- a/server_app/templates/query_form.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Web server security scanner - - -

Web server security analyzation tool

-
Bachelor thesis project, made by Samuel Kopecký
- -
-
- - -
Url of the web server you want to scan
-
-
- - -
Default scan port is 443
-
-
- - -
-
- - -
-
- Tests -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
-
-
- -
-
- - - \ No newline at end of file diff --git a/server_app/templates/query_result.html b/server_app/templates/query_result.html deleted file mode 100644 index 0bb014e..0000000 --- a/server_app/templates/query_result.html +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - Web server security scanner - - -
    -{% for key, value in json_response.items() recursive %} - {% if value is mapping %} -
  • - {{ key }} -
      {{ loop(value.items()) }}
    - {% else %} - {% if value is iterable and (value is not string) %} -
  • - {{ key }} -
      - {% for l_value in value %} -
    • {{ l_value }}
    • - {% endfor %} -
    - {% else %} - {% if value == '1' %} -
  • - {% elif value == '2' %} -
  • - {% elif value == '4' or value == '3' %} -
  • - {% else %} -
  • - {% endif %} - {{ key }}->{{ value }} - {% endif %} - {% endif %} -
  • -{% endfor %} -
-
- -
- - - - \ No newline at end of file diff --git a/server_app/utils.py b/server_app/utils.py deleted file mode 100644 index 485e6e6..0000000 --- a/server_app/utils.py +++ /dev/null @@ -1,57 +0,0 @@ -from flask import request -import json -import os - - -def parse_list(long_key, short_key): - args = [] - values = request.form[long_key] - if values != '': - args.append(short_key) - args.append(values) - return args - - -def read_json(file_name: str): - root_dir = os.path.dirname(os.path.abspath(__file__)) - file = open(f'{root_dir}/../resources/{file_name}', 'r') - json_data = json.loads(file.read()) - file.close() - return json_data - - -def translate_keys(obj): - names = read_json('type_names.json') - for key in list(obj.keys()): - if key not in names.keys(): - continue - new_key = names[key] - if new_key != key: - obj[new_key] = obj[key] - del obj[key] - return obj - - -def remove_invalid_values(data): - for key, value in list(data.items()): - if type(value) is dict: - if not data[key]: - del data[key] - continue - keys_list = list(value.keys()) - if len(keys_list) > 0 and keys_list[0] == "N/A": - del data[key] - continue - remove_invalid_values(value) - else: - return - - -def parse_checkboxes(switcher): - checked = [] - for value in list(switcher.keys()): - if value not in request.form: - continue - elif request.form[value] == 'on': - checked.append(switcher[value]) - return checked diff --git a/ssltest.py b/ssltest.py index 28cf5db..f0f8625 100755 --- a/ssltest.py +++ b/ssltest.py @@ -2,13 +2,13 @@ import argparse, sys, logging, json, textwrap, traceback, os -from scan_vulnerabilities import heartbleed -from scan_vulnerabilities import ccs_injection -from scan_vulnerabilities import insec_renegotiation as rene -from scan_vulnerabilities import poodle -from scan_vulnerabilities import session_ticket -from scan_vulnerabilities import crime -from scan_vulnerabilities import rc4_support +from scan_vulnerabilities.tests import heartbleed +from scan_vulnerabilities.tests import ccs_injection +from scan_vulnerabilities.tests import insec_renegotiation as rene +from scan_vulnerabilities.tests import poodle +from scan_vulnerabilities.tests import session_ticket +from scan_vulnerabilities.tests import crime +from scan_vulnerabilities.tests import rc4_support from scan_parameters.ratable.CipherSuite import CipherSuite from scan_parameters.ratable.Certificate import Certificate from scan_parameters.non_ratable.ProtocolSupport import ProtocolSupport @@ -64,15 +64,16 @@ def vulnerability_scan(address, tests, version): """ Forwards the appropriate tests to multithreading function + :param version: ssl protocol version :param address: tuple of an url and port :param tests: input option for tests :return: dictionary of scanned results """ + # if no -t argument is present if not tests: - return {} - scans = [] - for test in tests: - scans.append(tests_switcher.get(test)) + scans = [value for value in tests_switcher.values()] + else: + scans = [tests_switcher.get(test) for test in tests] return scan_vulnerabilities(scans, address, version) @@ -150,11 +151,13 @@ def parse_options(program_args): :return: object of parsed arguments """ - tests_help = 'test the server for a specified vulnerability\npossible vulnerabilities (separate with spaces):\n' + tests_help = 'test the server for a specified vulnerability' \ + '\npossible vulnerabilities (separate with spaces):\n' for key, value in tests_switcher.items(): test_number = key test_desc = value[1] tests_help += f'{" " * 4}{test_number}: {test_desc}\n' + tests_help += 'if this argument isn\'t specified all tests will be ran' parser = argparse.ArgumentParser( usage='use -h or --help for more information', @@ -189,17 +192,24 @@ def parse_options(program_args): parser.add_argument('-v', '--verbose', action='store_true', default=False, help='output more information') args = parser.parse_args(program_args) - check_test_numbers(args, parser) + check_test_numbers(args.test, parser.usage) return args -def check_test_numbers(args, parser): - if not args.test: +def check_test_numbers(tests, usage): + """ + Check if the tests numbers are actually tests + + :param tests: test argument + :param usage: usage string + :return: + """ + if not tests: return test_numbers = [test for test in tests_switcher.keys()] - unknown_tests = list(filter(lambda test: test not in test_numbers, args.test)) + unknown_tests = list(filter(lambda test: test not in test_numbers, tests)) if unknown_tests: - parser.print_usage() + print(f'usage: {usage}') if len(unknown_tests) > 1: unknown_tests = list(map(str, unknown_tests)) print(f'Numbers {", ".join(unknown_tests)} are not test numbers.', file=sys.stderr) diff --git a/start.sh b/start.sh deleted file mode 100755 index c5725f4..0000000 --- a/start.sh +++ /dev/null @@ -1,66 +0,0 @@ -#!/bin/bash - - -DOCKER=0 -while getopts ":hcd" arg; do - case ${arg} in - h) - echo "usage: use -h for more information" - echo - echo "This script runs the essential python files to deploy" - echo "a web server for tlstest tool. Script kills all the created" - echo "processes when user enters anything into the script." - echo "Termination of child processes may not work if script" - echo "is ran as root!" - echo - echo "optional arguments: - -h displays this message - -c allow the use of older versions of TLS protocol - (TLSv1 and TLSv1.1) in order to scan a server which - still run on these versions. - !WARNING!: this may rewrite the contents of a - configuration file located at /etc/ssl/openssl.cnf - backup is recommended, root permission required - -d run the script for docker, only use if running in docker" - exit 0;; - c) - python3 fix_openssl_config.py;; - d) - DOCKER=1;; - ?) - echo "usage: use -h for more information" - exit 1;; - esac -done - -# Trap exit signals to termite child processes -trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT - -# Change to the directory of the script so that other -# scripts can be ran -cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null - -LOGDIR="./logs" -RESTAPI_LOGFILE="${LOGDIR}/restapi.log" -SERVERAPP_LOGFILE="${LOGDIR}/server_app.log" - -# Check if directory already exists -if ! [ -d "${LOGDIR}" ]; then - mkdir $LOGDIR -fi - -echo "Starting restapi and the server app in background ..." -echo "Storing logs for restapi in ${RESTAPI_LOGFILE} ..." -echo "Storing logs for server app in ${SERVERAPP_LOGFILE} ..." - -# Redirect stdout and stderr to a log file -python3 ./restapi.py >$RESTAPI_LOGFILE 2>&1 & -if [ 1 -eq $DOCKER ]; then - python3 ./server_app/server.py >$SERVERAPP_LOGFILE 2>&1 -else - python3 ./server_app/server.py >$SERVERAPP_LOGFILE 2>&1 & - echo "To terminate server processes press enter" - read -fi - - From b9736e421f6017a25de7d281b7aca5c8fb6874e6 Mon Sep 17 00:00:00 2001 From: SamoKopecky Date: Tue, 27 Jul 2021 10:26:56 +0200 Subject: [PATCH 3/8] Update README.md --- README.md | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 9a74a81..fb66b09 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,17 @@ # Overview -- This repository contains: - - The standalone script that can be run in a console - - Web server app that allows to use the script with a GUI (if hosted locally on this [url](http://localhost:5000)) +- A standalone script that can be run in a console - File `resources/security_levels.json` can be edited to change the parameter rating values -# Run the standalone script +# Run the script - Run the main file `ssltest.py` with option `-u` to enter the url. - Use `-h` or `--help` for more help. - Example: `./ssltest.py -u vutbr.cz` -# Prepare the environment - -- The required dependencies can be installed either on the hosting OS or by creating a docker container. - ## Prepare hosting OS environment -- If you are going to run the script, or the web app on the hosting OS these dependencies are required +- If you are going to run the script these dependencies are required - To install required python packages use `pip3 install -r requirements.txt` command which installs: - [cryptography](https://pypi.org/project/cryptography/) - [pyopenssl](https://pypi.org/project/pyOpenSSL/) @@ -27,17 +21,14 @@ - [Flask](https://pypi.org/project/Flask/) - [flask-restful](https://pypi.org/project/Flask-RESTful/) - Nmap is required to for some functions, install with `apt install -y nmap` -- To start the server app with the rest api server run the `start.sh` script or `script.sh -h` for more information - To run the tool script refer to the section at the start -## Create environment using docker - -- To create the environment docker engine ([guide here](https://docs.docker.com/engine/install/)) and docker - compose ([guide here](https://docs.docker.com/compose/install/)) need to be installed. -- Create the docker container with this command `docker-compose up -d` ran in the root project directory -- The web app will be automatically deployed, and the environment with required dependencies can be accessed via this - command: - `docker exec -it bp_flask_server /bin/bash` -- The standalone script can be then ran inside the created environment, refer to the section at the begging to see how - to run the script +## Supported vulnerability tests +- Heartbleed +- CCS Injection +- Insecure renegotiation +- ZombiePOODLE/GOLDENDOODLE +- Session ticker support +- CRIME +- RC4 Support \ No newline at end of file From dd98a94bf998c738c7afa219697daffc8f3bd92e Mon Sep 17 00:00:00 2001 From: SamoKopecky Date: Mon, 2 Aug 2021 10:48:11 +0200 Subject: [PATCH 4/8] Add SSLv3 scanning and a small refractor --- .../connection/connection_utils.py | 1 - .../non_ratable/ProtocolSupport.py | 12 +++-- scan_parameters/ratable/Certificate.py | 4 -- scan_parameters/ratable/CipherSuite.py | 5 -- scan_vulnerabilities/tests/sslv3.py | 49 +++++++++++++++++++ ssltest.py | 11 +++-- 6 files changed, 66 insertions(+), 16 deletions(-) create mode 100644 scan_vulnerabilities/tests/sslv3.py diff --git a/scan_parameters/connection/connection_utils.py b/scan_parameters/connection/connection_utils.py index 3a6231d..61ea852 100644 --- a/scan_parameters/connection/connection_utils.py +++ b/scan_parameters/connection/connection_utils.py @@ -102,7 +102,6 @@ def create_session(url: str, port: int, context: ssl.SSLContext = ssl.create_def while True: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(5) # in seconds - # context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) ssl_socket = context.wrap_socket(sock, server_hostname=url) try: logging.debug(f'connecting... (main connection)') diff --git a/scan_parameters/non_ratable/ProtocolSupport.py b/scan_parameters/non_ratable/ProtocolSupport.py index 2ecffb5..5de3767 100644 --- a/scan_parameters/non_ratable/ProtocolSupport.py +++ b/scan_parameters/non_ratable/ProtocolSupport.py @@ -4,6 +4,7 @@ from ..utils import rate_parameter from ..ratable.PType import PType from ..connection.connection_utils import create_session_pyopenssl +from scan_vulnerabilities.tests import sslv3 class ProtocolSupport: @@ -20,6 +21,7 @@ def scan_protocols(self): :return: list of the supported protocols. """ + logging.info('Scanning TLS versions...') ssl_versions = { SSL.TLSv1_METHOD: "TLSv1", SSL.TLSv1_1_METHOD: "TLSv1.1", @@ -43,14 +45,18 @@ def scan_protocols(self): # Need to do this since there is no explicit option for TLSv1.3 if 'TLSv1.3' not in supported_protocols: unsupported_protocols.append('TLSv1.3') + # SSLv3 scanning + result = sslv3.scan((self.url, self.port)) + if result: + supported_protocols.append("SSLv3") + else: + unsupported_protocols.append("SSLv3") return supported_protocols, unsupported_protocols - def rate_protocols(self): + def rate_protocols(self, supported_protocols, unsupported_protocols): """ Rate the scanned protocols. """ - logging.info('Scanning TLS versions...') - supported_protocols, unsupported_protocols = self.scan_protocols() for protocol in supported_protocols: self.versions[PType.protocols][protocol] = rate_parameter(PType.protocol, protocol) for no_protocol in unsupported_protocols: diff --git a/scan_parameters/ratable/Certificate.py b/scan_parameters/ratable/Certificate.py index ec3edca..0ce3b82 100644 --- a/scan_parameters/ratable/Certificate.py +++ b/scan_parameters/ratable/Certificate.py @@ -73,7 +73,3 @@ def rate_certificate(self): rateable_parameters = list(self.parameters.keys()) key_types = [PType.cert_pub_key_length] self.rate_parameters(rateable_parameters, key_types) - - def rate(self): - self.parse_certificate() - self.rate_certificate() diff --git a/scan_parameters/ratable/CipherSuite.py b/scan_parameters/ratable/CipherSuite.py index 8dcd73b..f8ed081 100644 --- a/scan_parameters/ratable/CipherSuite.py +++ b/scan_parameters/ratable/CipherSuite.py @@ -53,8 +53,3 @@ def parse_protocol_version(self): self.parameters[PType.protocol] = {self.protocol: 0} if self.protocol == 'TLSv1.3': self.parameters[PType.kex_algorithm] = {'ECDHE': 0} - - def rate(self): - self.parse_cipher_suite() - self.parse_protocol_version() - self.rate_cipher_suite() diff --git a/scan_vulnerabilities/tests/sslv3.py b/scan_vulnerabilities/tests/sslv3.py new file mode 100644 index 0000000..46b579a --- /dev/null +++ b/scan_vulnerabilities/tests/sslv3.py @@ -0,0 +1,49 @@ +from ..utils import * + + +def construct_client_hello(): + client_hello = bytes([ + # Record protocol + 0x16, # Content type (Handshake) + 0x03, 0x00, # Version (SSLv3) + 0x00, 0x8f, # Length + # Handshake protocol + 0x01, # Handshake type + 0x00, 0x00, 0x8b, # Length + 0x03, 0x00, # Version + # Random bytes + 0xa9, 0x09, 0x3f, 0x70, 0xad, 0xdc, 0xde, 0x4f, + 0xb1, 0x78, 0x47, 0xe5, 0xf3, 0x35, 0xea, 0xc9, + 0x1b, 0x3b, 0x34, 0x37, 0x23, 0xd8, 0xd4, 0x5d, + 0x92, 0x40, 0x4b, 0x01, 0x9e, 0x55, 0xf7, 0x2f, + 0x00, # Session id length + 0x00, 0x64, # Cipher suites length + # Cipher suites + 0xc0, 0x14, 0xc0, 0x0a, 0x00, 0x39, 0x00, 0x38, + 0x00, 0x37, 0x00, 0x36, 0x00, 0x88, 0x00, 0x87, + 0x00, 0x86, 0x00, 0x85, 0xc0, 0x0f, 0xc0, 0x05, + 0x00, 0x35, 0x00, 0x84, 0xc0, 0x13, 0xc0, 0x09, + 0x00, 0x33, 0x00, 0x32, 0x00, 0x31, 0x00, 0x30, + 0x00, 0x9a, 0x00, 0x99, 0x00, 0x98, 0x00, 0x97, + 0x00, 0x45, 0x00, 0x44, 0x00, 0x43, 0x00, 0x42, + 0xc0, 0x0e, 0xc0, 0x04, 0x00, 0x2f, 0x00, 0x96, + 0x00, 0x41, 0x00, 0x07, 0xc0, 0x11, 0xc0, 0x07, + 0xc0, 0x0c, 0xc0, 0x02, 0x00, 0x05, 0x00, 0x04, + 0xc0, 0x12, 0xc0, 0x08, 0x00, 0x16, 0x00, 0x13, + 0x00, 0x10, 0x00, 0x0d, 0xc0, 0x0d, 0xc0, 0x03, + 0x00, 0x0a, 0x00, 0xff, + 0x01, # Compression methods length + 0x00 # Compression methods + ]) + return client_hello + + +def scan(address): + client_hello = construct_client_hello() + timeout = 2 + server_hello, sock = send_client_hello(address, client_hello, timeout) + # Test if the response is Content type Alert (0x15) + # and test if the alert message is handshake failure (0x28) + if server_hello[0] == 0x15 and server_hello[6] == 0x28: + return False + return True diff --git a/ssltest.py b/ssltest.py index f0f8625..29ebb95 100755 --- a/ssltest.py +++ b/ssltest.py @@ -230,17 +230,22 @@ def scan(args, port: int): certificate, cert_verified, cipher_suite, protocol = get_website_info(args.url, port) cipher_suite = CipherSuite(cipher_suite, protocol) - cipher_suite.rate() + cipher_suite.parse_cipher_suite() + cipher_suite.parse_protocol_version() + cipher_suite.rate_cipher_suite() certificate = Certificate(certificate, cert_verified) - certificate.rate() + certificate.parse_certificate() + certificate.rate_certificate() protocol_support = ProtocolSupport(args.url, port) - protocol_support.rate_protocols() + supported_protocols, unsupported_protocols = protocol_support.scan_protocols() + protocol_support.rate_protocols(supported_protocols, unsupported_protocols) versions = WebServerSoft(args.url, port, args.nmap_scan) versions.scan_server_software() + # Get the version the initial connection was made on main_version = list(cipher_suite.parameters[PType.protocol].keys())[0] vulnerabilities = vulnerability_scan((args.url, port), args.test, main_version) From 42af645aa4aeea2f6dac4c1285274d68bc620756 Mon Sep 17 00:00:00 2001 From: SamoKopecky Date: Mon, 2 Aug 2021 11:05:01 +0200 Subject: [PATCH 5/8] Add a test argument to not scan any tests and fix TLSv1 version If the argument -t 0 is given no vulnerability tests will be ran A correct value is now shown in the output for the protocol TLSv1.0, before it was TLSv1 --- resources/security_levels.json | 4 ++-- scan_parameters/non_ratable/ProtocolSupport.py | 2 +- ssltest.py | 11 +++++++---- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/resources/security_levels.json b/resources/security_levels.json index 90d2940..d2c1985 100644 --- a/resources/security_levels.json +++ b/resources/security_levels.json @@ -49,12 +49,12 @@ }, "protocol": { "1": "TLSv1.3,TLSv1.2", - "2": "TLSv1.1,TLSv1", + "2": "TLSv1.1,TLSv1.0", "3": "", "4": "SSLv3,SSLv2" }, "no_protocol": { - "1": "SSLv3,SSLv2,TLSv1,TLSv1.1", + "1": "SSLv3,SSLv2,TLSv1.0,TLSv1.1", "2": "TLSv1.3", "3": "", "4": "TLSv1.2" diff --git a/scan_parameters/non_ratable/ProtocolSupport.py b/scan_parameters/non_ratable/ProtocolSupport.py index 5de3767..0a8581d 100644 --- a/scan_parameters/non_ratable/ProtocolSupport.py +++ b/scan_parameters/non_ratable/ProtocolSupport.py @@ -23,7 +23,7 @@ def scan_protocols(self): """ logging.info('Scanning TLS versions...') ssl_versions = { - SSL.TLSv1_METHOD: "TLSv1", + SSL.TLSv1_METHOD: "TLSv1.0", SSL.TLSv1_1_METHOD: "TLSv1.1", SSL.TLSv1_2_METHOD: "TLSv1.2", SSL.SSLv23_METHOD: "" diff --git a/ssltest.py b/ssltest.py index 29ebb95..8bc4d1d 100755 --- a/ssltest.py +++ b/ssltest.py @@ -72,6 +72,8 @@ def vulnerability_scan(address, tests, version): # if no -t argument is present if not tests: scans = [value for value in tests_switcher.values()] + elif tests[0] == 0: + return {} else: scans = [tests_switcher.get(test) for test in tests] return scan_vulnerabilities(scans, address, version) @@ -151,13 +153,14 @@ def parse_options(program_args): :return: object of parsed arguments """ - tests_help = 'test the server for a specified vulnerability' \ - '\npossible vulnerabilities (separate with spaces):\n' + tests_help = 'test the server for a specified vulnerability\n' \ + 'possible vulnerabilities (separate with spaces):\n' for key, value in tests_switcher.items(): test_number = key test_desc = value[1] tests_help += f'{" " * 4}{test_number}: {test_desc}\n' - tests_help += 'if this argument isn\'t specified all tests will be ran' + tests_help += 'if this argument isn\'t specified all tests will be ran\n' \ + 'if 0 is given as a test number no tests will be ran' parser = argparse.ArgumentParser( usage='use -h or --help for more information', @@ -204,7 +207,7 @@ def check_test_numbers(tests, usage): :param usage: usage string :return: """ - if not tests: + if not tests or tests[0] == 0: return test_numbers = [test for test in tests_switcher.keys()] unknown_tests = list(filter(lambda test: test not in test_numbers, tests)) From 2e2a31ca341b3256df92ae72476e177aa80b27fb Mon Sep 17 00:00:00 2001 From: SamoKopecky Date: Tue, 3 Aug 2021 13:14:38 +0200 Subject: [PATCH 6/8] Add sslv3 cipher suite and certificate scanning Scanning is done by manualy sending client hello to the server and extracting the cipher suite and the certificate from the server response Also add cipher suite json file for translating from cipher suite bytes to string representation of cipher suites, origin of resource is from the official IANA TLS parameteres website --- resources/iana_cipher_suites.json | 352 ++++++++++++++++++ .../connection/connection_utils.py | 19 +- .../non_ratable/ProtocolSupport.py | 17 +- scan_vulnerabilities/tests/sslv3.py | 49 --- ssl_scan/SSLv3.py | 84 +++++ ssl_scan/__init__.py | 0 ssl_scan/utils.py | 27 ++ ssltest.py | 5 +- 8 files changed, 491 insertions(+), 62 deletions(-) create mode 100644 resources/iana_cipher_suites.json delete mode 100644 scan_vulnerabilities/tests/sslv3.py create mode 100644 ssl_scan/SSLv3.py create mode 100644 ssl_scan/__init__.py create mode 100644 ssl_scan/utils.py diff --git a/resources/iana_cipher_suites.json b/resources/iana_cipher_suites.json new file mode 100644 index 0000000..767b6f7 --- /dev/null +++ b/resources/iana_cipher_suites.json @@ -0,0 +1,352 @@ +{ + "0x00,0x00": "TLS_NULL_WITH_NULL_NULL", + "0x00,0x01": "TLS_RSA_WITH_NULL_MD5", + "0x00,0x02": "TLS_RSA_WITH_NULL_SHA", + "0x00,0x03": "TLS_RSA_EXPORT_WITH_RC4_40_MD5", + "0x00,0x04": "TLS_RSA_WITH_RC4_128_MD5", + "0x00,0x05": "TLS_RSA_WITH_RC4_128_SHA", + "0x00,0x06": "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5", + "0x00,0x07": "TLS_RSA_WITH_IDEA_CBC_SHA", + "0x00,0x08": "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA", + "0x00,0x09": "TLS_RSA_WITH_DES_CBC_SHA", + "0x00,0x0A": "TLS_RSA_WITH_3DES_EDE_CBC_SHA", + "0x00,0x0B": "TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA", + "0x00,0x0C": "TLS_DH_DSS_WITH_DES_CBC_SHA", + "0x00,0x0D": "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", + "0x00,0x0E": "TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA", + "0x00,0x0F": "TLS_DH_RSA_WITH_DES_CBC_SHA", + "0x00,0x10": "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", + "0x00,0x11": "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", + "0x00,0x12": "TLS_DHE_DSS_WITH_DES_CBC_SHA", + "0x00,0x13": "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", + "0x00,0x14": "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", + "0x00,0x15": "TLS_DHE_RSA_WITH_DES_CBC_SHA", + "0x00,0x16": "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", + "0x00,0x17": "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5", + "0x00,0x18": "TLS_DH_anon_WITH_RC4_128_MD5", + "0x00,0x19": "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA", + "0x00,0x1A": "TLS_DH_anon_WITH_DES_CBC_SHA", + "0x00,0x1B": "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", + "0x00,0x1E": "TLS_KRB5_WITH_DES_CBC_SHA", + "0x00,0x1F": "TLS_KRB5_WITH_3DES_EDE_CBC_SHA", + "0x00,0x20": "TLS_KRB5_WITH_RC4_128_SHA", + "0x00,0x21": "TLS_KRB5_WITH_IDEA_CBC_SHA", + "0x00,0x22": "TLS_KRB5_WITH_DES_CBC_MD5", + "0x00,0x23": "TLS_KRB5_WITH_3DES_EDE_CBC_MD5", + "0x00,0x24": "TLS_KRB5_WITH_RC4_128_MD5", + "0x00,0x25": "TLS_KRB5_WITH_IDEA_CBC_MD5", + "0x00,0x26": "TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA", + "0x00,0x27": "TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA", + "0x00,0x28": "TLS_KRB5_EXPORT_WITH_RC4_40_SHA", + "0x00,0x29": "TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5", + "0x00,0x2A": "TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5", + "0x00,0x2B": "TLS_KRB5_EXPORT_WITH_RC4_40_MD5", + "0x00,0x2C": "TLS_PSK_WITH_NULL_SHA", + "0x00,0x2D": "TLS_DHE_PSK_WITH_NULL_SHA", + "0x00,0x2E": "TLS_RSA_PSK_WITH_NULL_SHA", + "0x00,0x2F": "TLS_RSA_WITH_AES_128_CBC_SHA", + "0x00,0x30": "TLS_DH_DSS_WITH_AES_128_CBC_SHA", + "0x00,0x31": "TLS_DH_RSA_WITH_AES_128_CBC_SHA", + "0x00,0x32": "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", + "0x00,0x33": "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", + "0x00,0x34": "TLS_DH_anon_WITH_AES_128_CBC_SHA", + "0x00,0x35": "TLS_RSA_WITH_AES_256_CBC_SHA", + "0x00,0x36": "TLS_DH_DSS_WITH_AES_256_CBC_SHA", + "0x00,0x37": "TLS_DH_RSA_WITH_AES_256_CBC_SHA", + "0x00,0x38": "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", + "0x00,0x39": "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", + "0x00,0x3A": "TLS_DH_anon_WITH_AES_256_CBC_SHA", + "0x00,0x3B": "TLS_RSA_WITH_NULL_SHA256", + "0x00,0x3C": "TLS_RSA_WITH_AES_128_CBC_SHA256", + "0x00,0x3D": "TLS_RSA_WITH_AES_256_CBC_SHA256", + "0x00,0x3E": "TLS_DH_DSS_WITH_AES_128_CBC_SHA256", + "0x00,0x3F": "TLS_DH_RSA_WITH_AES_128_CBC_SHA256", + "0x00,0x40": "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", + "0x00,0x41": "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", + "0x00,0x42": "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", + "0x00,0x43": "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", + "0x00,0x44": "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", + "0x00,0x45": "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", + "0x00,0x46": "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", + "0x00,0x67": "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", + "0x00,0x68": "TLS_DH_DSS_WITH_AES_256_CBC_SHA256", + "0x00,0x69": "TLS_DH_RSA_WITH_AES_256_CBC_SHA256", + "0x00,0x6A": "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", + "0x00,0x6B": "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", + "0x00,0x6C": "TLS_DH_anon_WITH_AES_128_CBC_SHA256", + "0x00,0x6D": "TLS_DH_anon_WITH_AES_256_CBC_SHA256", + "0x00,0x84": "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", + "0x00,0x85": "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", + "0x00,0x86": "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", + "0x00,0x87": "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", + "0x00,0x88": "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", + "0x00,0x89": "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", + "0x00,0x8A": "TLS_PSK_WITH_RC4_128_SHA", + "0x00,0x8B": "TLS_PSK_WITH_3DES_EDE_CBC_SHA", + "0x00,0x8C": "TLS_PSK_WITH_AES_128_CBC_SHA", + "0x00,0x8D": "TLS_PSK_WITH_AES_256_CBC_SHA", + "0x00,0x8E": "TLS_DHE_PSK_WITH_RC4_128_SHA", + "0x00,0x8F": "TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA", + "0x00,0x90": "TLS_DHE_PSK_WITH_AES_128_CBC_SHA", + "0x00,0x91": "TLS_DHE_PSK_WITH_AES_256_CBC_SHA", + "0x00,0x92": "TLS_RSA_PSK_WITH_RC4_128_SHA", + "0x00,0x93": "TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA", + "0x00,0x94": "TLS_RSA_PSK_WITH_AES_128_CBC_SHA", + "0x00,0x95": "TLS_RSA_PSK_WITH_AES_256_CBC_SHA", + "0x00,0x96": "TLS_RSA_WITH_SEED_CBC_SHA", + "0x00,0x97": "TLS_DH_DSS_WITH_SEED_CBC_SHA", + "0x00,0x98": "TLS_DH_RSA_WITH_SEED_CBC_SHA", + "0x00,0x99": "TLS_DHE_DSS_WITH_SEED_CBC_SHA", + "0x00,0x9A": "TLS_DHE_RSA_WITH_SEED_CBC_SHA", + "0x00,0x9B": "TLS_DH_anon_WITH_SEED_CBC_SHA", + "0x00,0x9C": "TLS_RSA_WITH_AES_128_GCM_SHA256", + "0x00,0x9D": "TLS_RSA_WITH_AES_256_GCM_SHA384", + "0x00,0x9E": "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", + "0x00,0x9F": "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", + "0x00,0xA0": "TLS_DH_RSA_WITH_AES_128_GCM_SHA256", + "0x00,0xA1": "TLS_DH_RSA_WITH_AES_256_GCM_SHA384", + "0x00,0xA2": "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", + "0x00,0xA3": "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", + "0x00,0xA4": "TLS_DH_DSS_WITH_AES_128_GCM_SHA256", + "0x00,0xA5": "TLS_DH_DSS_WITH_AES_256_GCM_SHA384", + "0x00,0xA6": "TLS_DH_anon_WITH_AES_128_GCM_SHA256", + "0x00,0xA7": "TLS_DH_anon_WITH_AES_256_GCM_SHA384", + "0x00,0xA8": "TLS_PSK_WITH_AES_128_GCM_SHA256", + "0x00,0xA9": "TLS_PSK_WITH_AES_256_GCM_SHA384", + "0x00,0xAA": "TLS_DHE_PSK_WITH_AES_128_GCM_SHA256", + "0x00,0xAB": "TLS_DHE_PSK_WITH_AES_256_GCM_SHA384", + "0x00,0xAC": "TLS_RSA_PSK_WITH_AES_128_GCM_SHA256", + "0x00,0xAD": "TLS_RSA_PSK_WITH_AES_256_GCM_SHA384", + "0x00,0xAE": "TLS_PSK_WITH_AES_128_CBC_SHA256", + "0x00,0xAF": "TLS_PSK_WITH_AES_256_CBC_SHA384", + "0x00,0xB0": "TLS_PSK_WITH_NULL_SHA256", + "0x00,0xB1": "TLS_PSK_WITH_NULL_SHA384", + "0x00,0xB2": "TLS_DHE_PSK_WITH_AES_128_CBC_SHA256", + "0x00,0xB3": "TLS_DHE_PSK_WITH_AES_256_CBC_SHA384", + "0x00,0xB4": "TLS_DHE_PSK_WITH_NULL_SHA256", + "0x00,0xB5": "TLS_DHE_PSK_WITH_NULL_SHA384", + "0x00,0xB6": "TLS_RSA_PSK_WITH_AES_128_CBC_SHA256", + "0x00,0xB7": "TLS_RSA_PSK_WITH_AES_256_CBC_SHA384", + "0x00,0xB8": "TLS_RSA_PSK_WITH_NULL_SHA256", + "0x00,0xB9": "TLS_RSA_PSK_WITH_NULL_SHA384", + "0x00,0xBA": "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256", + "0x00,0xBB": "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256", + "0x00,0xBC": "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256", + "0x00,0xBD": "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256", + "0x00,0xBE": "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", + "0x00,0xBF": "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256", + "0x00,0xC0": "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256", + "0x00,0xC1": "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256", + "0x00,0xC2": "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256", + "0x00,0xC3": "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256", + "0x00,0xC4": "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256", + "0x00,0xC5": "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256", + "0x00,0xC6": "TLS_SM4_GCM_SM3", + "0x00,0xC7": "TLS_SM4_CCM_SM3", + "0x00,0xFF": "TLS_EMPTY_RENEGOTIATION_INFO_SCSV", + "0x13,0x01": "TLS_AES_128_GCM_SHA256", + "0x13,0x02": "TLS_AES_256_GCM_SHA384", + "0x13,0x03": "TLS_CHACHA20_POLY1305_SHA256", + "0x13,0x04": "TLS_AES_128_CCM_SHA256", + "0x13,0x05": "TLS_AES_128_CCM_8_SHA256", + "0x56,0x00": "TLS_FALLBACK_SCSV", + "0xC0,0x01": "TLS_ECDH_ECDSA_WITH_NULL_SHA", + "0xC0,0x02": "TLS_ECDH_ECDSA_WITH_RC4_128_SHA", + "0xC0,0x03": "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", + "0xC0,0x04": "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", + "0xC0,0x05": "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", + "0xC0,0x06": "TLS_ECDHE_ECDSA_WITH_NULL_SHA", + "0xC0,0x07": "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", + "0xC0,0x08": "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", + "0xC0,0x09": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", + "0xC0,0x0A": "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + "0xC0,0x0B": "TLS_ECDH_RSA_WITH_NULL_SHA", + "0xC0,0x0C": "TLS_ECDH_RSA_WITH_RC4_128_SHA", + "0xC0,0x0D": "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", + "0xC0,0x0E": "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", + "0xC0,0x0F": "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", + "0xC0,0x10": "TLS_ECDHE_RSA_WITH_NULL_SHA", + "0xC0,0x11": "TLS_ECDHE_RSA_WITH_RC4_128_SHA", + "0xC0,0x12": "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", + "0xC0,0x13": "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "0xC0,0x14": "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "0xC0,0x15": "TLS_ECDH_anon_WITH_NULL_SHA", + "0xC0,0x16": "TLS_ECDH_anon_WITH_RC4_128_SHA", + "0xC0,0x17": "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA", + "0xC0,0x18": "TLS_ECDH_anon_WITH_AES_128_CBC_SHA", + "0xC0,0x19": "TLS_ECDH_anon_WITH_AES_256_CBC_SHA", + "0xC0,0x1A": "TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA", + "0xC0,0x1B": "TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA", + "0xC0,0x1C": "TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA", + "0xC0,0x1D": "TLS_SRP_SHA_WITH_AES_128_CBC_SHA", + "0xC0,0x1E": "TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA", + "0xC0,0x1F": "TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA", + "0xC0,0x20": "TLS_SRP_SHA_WITH_AES_256_CBC_SHA", + "0xC0,0x21": "TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA", + "0xC0,0x22": "TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA", + "0xC0,0x23": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", + "0xC0,0x24": "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", + "0xC0,0x25": "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256", + "0xC0,0x26": "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384", + "0xC0,0x27": "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", + "0xC0,0x28": "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", + "0xC0,0x29": "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256", + "0xC0,0x2A": "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384", + "0xC0,0x2B": "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "0xC0,0x2C": "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "0xC0,0x2D": "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256", + "0xC0,0x2E": "TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384", + "0xC0,0x2F": "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "0xC0,0x30": "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "0xC0,0x31": "TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256", + "0xC0,0x32": "TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384", + "0xC0,0x33": "TLS_ECDHE_PSK_WITH_RC4_128_SHA", + "0xC0,0x34": "TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA", + "0xC0,0x35": "TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA", + "0xC0,0x36": "TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA", + "0xC0,0x37": "TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256", + "0xC0,0x38": "TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384", + "0xC0,0x39": "TLS_ECDHE_PSK_WITH_NULL_SHA", + "0xC0,0x3A": "TLS_ECDHE_PSK_WITH_NULL_SHA256", + "0xC0,0x3B": "TLS_ECDHE_PSK_WITH_NULL_SHA384", + "0xC0,0x3C": "TLS_RSA_WITH_ARIA_128_CBC_SHA256", + "0xC0,0x3D": "TLS_RSA_WITH_ARIA_256_CBC_SHA384", + "0xC0,0x3E": "TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256", + "0xC0,0x3F": "TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384", + "0xC0,0x40": "TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256", + "0xC0,0x41": "TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384", + "0xC0,0x42": "TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256", + "0xC0,0x43": "TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384", + "0xC0,0x44": "TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256", + "0xC0,0x45": "TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384", + "0xC0,0x46": "TLS_DH_anon_WITH_ARIA_128_CBC_SHA256", + "0xC0,0x47": "TLS_DH_anon_WITH_ARIA_256_CBC_SHA384", + "0xC0,0x48": "TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256", + "0xC0,0x49": "TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384", + "0xC0,0x4A": "TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256", + "0xC0,0x4B": "TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384", + "0xC0,0x4C": "TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256", + "0xC0,0x4D": "TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384", + "0xC0,0x4E": "TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256", + "0xC0,0x4F": "TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384", + "0xC0,0x50": "TLS_RSA_WITH_ARIA_128_GCM_SHA256", + "0xC0,0x51": "TLS_RSA_WITH_ARIA_256_GCM_SHA384", + "0xC0,0x52": "TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256", + "0xC0,0x53": "TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384", + "0xC0,0x54": "TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256", + "0xC0,0x55": "TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384", + "0xC0,0x56": "TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256", + "0xC0,0x57": "TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384", + "0xC0,0x58": "TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256", + "0xC0,0x59": "TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384", + "0xC0,0x5A": "TLS_DH_anon_WITH_ARIA_128_GCM_SHA256", + "0xC0,0x5B": "TLS_DH_anon_WITH_ARIA_256_GCM_SHA384", + "0xC0,0x5C": "TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256", + "0xC0,0x5D": "TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384", + "0xC0,0x5E": "TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256", + "0xC0,0x5F": "TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384", + "0xC0,0x60": "TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256", + "0xC0,0x61": "TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384", + "0xC0,0x62": "TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256", + "0xC0,0x63": "TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384", + "0xC0,0x64": "TLS_PSK_WITH_ARIA_128_CBC_SHA256", + "0xC0,0x65": "TLS_PSK_WITH_ARIA_256_CBC_SHA384", + "0xC0,0x66": "TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256", + "0xC0,0x67": "TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384", + "0xC0,0x68": "TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256", + "0xC0,0x69": "TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384", + "0xC0,0x6A": "TLS_PSK_WITH_ARIA_128_GCM_SHA256", + "0xC0,0x6B": "TLS_PSK_WITH_ARIA_256_GCM_SHA384", + "0xC0,0x6C": "TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256", + "0xC0,0x6D": "TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384", + "0xC0,0x6E": "TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256", + "0xC0,0x6F": "TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384", + "0xC0,0x70": "TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256", + "0xC0,0x71": "TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384", + "0xC0,0x72": "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256", + "0xC0,0x73": "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384", + "0xC0,0x74": "TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256", + "0xC0,0x75": "TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384", + "0xC0,0x76": "TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", + "0xC0,0x77": "TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384", + "0xC0,0x78": "TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256", + "0xC0,0x79": "TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384", + "0xC0,0x7A": "TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256", + "0xC0,0x7B": "TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384", + "0xC0,0x7C": "TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256", + "0xC0,0x7D": "TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384", + "0xC0,0x7E": "TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256", + "0xC0,0x7F": "TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384", + "0xC0,0x80": "TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256", + "0xC0,0x81": "TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384", + "0xC0,0x82": "TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256", + "0xC0,0x83": "TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384", + "0xC0,0x84": "TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256", + "0xC0,0x85": "TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384", + "0xC0,0x86": "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256", + "0xC0,0x87": "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384", + "0xC0,0x88": "TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256", + "0xC0,0x89": "TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384", + "0xC0,0x8A": "TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256", + "0xC0,0x8B": "TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384", + "0xC0,0x8C": "TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256", + "0xC0,0x8D": "TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384", + "0xC0,0x8E": "TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256", + "0xC0,0x8F": "TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384", + "0xC0,0x90": "TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256", + "0xC0,0x91": "TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384", + "0xC0,0x92": "TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256", + "0xC0,0x93": "TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384", + "0xC0,0x94": "TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256", + "0xC0,0x95": "TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384", + "0xC0,0x96": "TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256", + "0xC0,0x97": "TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384", + "0xC0,0x98": "TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256", + "0xC0,0x99": "TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384", + "0xC0,0x9A": "TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256", + "0xC0,0x9B": "TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384", + "0xC0,0x9C": "TLS_RSA_WITH_AES_128_CCM", + "0xC0,0x9D": "TLS_RSA_WITH_AES_256_CCM", + "0xC0,0x9E": "TLS_DHE_RSA_WITH_AES_128_CCM", + "0xC0,0x9F": "TLS_DHE_RSA_WITH_AES_256_CCM", + "0xC0,0xA0": "TLS_RSA_WITH_AES_128_CCM_8", + "0xC0,0xA1": "TLS_RSA_WITH_AES_256_CCM_8", + "0xC0,0xA2": "TLS_DHE_RSA_WITH_AES_128_CCM_8", + "0xC0,0xA3": "TLS_DHE_RSA_WITH_AES_256_CCM_8", + "0xC0,0xA4": "TLS_PSK_WITH_AES_128_CCM", + "0xC0,0xA5": "TLS_PSK_WITH_AES_256_CCM", + "0xC0,0xA6": "TLS_DHE_PSK_WITH_AES_128_CCM", + "0xC0,0xA7": "TLS_DHE_PSK_WITH_AES_256_CCM", + "0xC0,0xA8": "TLS_PSK_WITH_AES_128_CCM_8", + "0xC0,0xA9": "TLS_PSK_WITH_AES_256_CCM_8", + "0xC0,0xAA": "TLS_PSK_DHE_WITH_AES_128_CCM_8", + "0xC0,0xAB": "TLS_PSK_DHE_WITH_AES_256_CCM_8", + "0xC0,0xAC": "TLS_ECDHE_ECDSA_WITH_AES_128_CCM", + "0xC0,0xAD": "TLS_ECDHE_ECDSA_WITH_AES_256_CCM", + "0xC0,0xAE": "TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8", + "0xC0,0xAF": "TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8", + "0xC0,0xB0": "TLS_ECCPWD_WITH_AES_128_GCM_SHA256", + "0xC0,0xB1": "TLS_ECCPWD_WITH_AES_256_GCM_SHA384", + "0xC0,0xB2": "TLS_ECCPWD_WITH_AES_128_CCM_SHA256", + "0xC0,0xB3": "TLS_ECCPWD_WITH_AES_256_CCM_SHA384", + "0xC0,0xB4": "TLS_SHA256_SHA256", + "0xC0,0xB5": "TLS_SHA384_SHA384", + "0xC1,0x00": "TLS_GOSTR341112_256_WITH_KUZNYECHIK_CTR_OMAC", + "0xC1,0x01": "TLS_GOSTR341112_256_WITH_MAGMA_CTR_OMAC", + "0xC1,0x02": "TLS_GOSTR341112_256_WITH_28147_CNT_IMIT", + "0xC1,0x03": "TLS_GOSTR341112_256_WITH_KUZNYECHIK_MGM_L", + "0xC1,0x04": "TLS_GOSTR341112_256_WITH_MAGMA_MGM_L", + "0xC1,0x05": "TLS_GOSTR341112_256_WITH_KUZNYECHIK_MGM_S", + "0xC1,0x06": "TLS_GOSTR341112_256_WITH_MAGMA_MGM_S", + "0xCC,0xA8": "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + "0xCC,0xA9": "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", + "0xCC,0xAA": "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + "0xCC,0xAB": "TLS_PSK_WITH_CHACHA20_POLY1305_SHA256", + "0xCC,0xAC": "TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256", + "0xCC,0xAD": "TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256", + "0xCC,0xAE": "TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256", + "0xD0,0x01": "TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256", + "0xD0,0x02": "TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384", + "0xD0,0x03": "TLS_ECDHE_PSK_WITH_AES_128_CCM_8_SHA256", + "0xD0,0x05": "TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256" +} \ No newline at end of file diff --git a/scan_parameters/connection/connection_utils.py b/scan_parameters/connection/connection_utils.py index 61ea852..851b493 100644 --- a/scan_parameters/connection/connection_utils.py +++ b/scan_parameters/connection/connection_utils.py @@ -9,6 +9,7 @@ from ..exceptions.UnknownConnectionError import UnknownConnectionError from ..exceptions.ConnectionTimeoutError import ConnectionTimeoutError from ..exceptions.DNSError import DNSError +from ssl_scan.SSLv3 import SSLv3 def get_website_info(url: str, port: int): @@ -25,10 +26,20 @@ def get_website_info(url: str, port: int): protocol -- protocol name and version """ logging.info('Creating session...') - ssl_socket, cert_verified = create_session(url, port) - cipher_suite, protocol = get_cipher_suite_and_protocol(ssl_socket) - certificate = get_certificate(ssl_socket) - ssl_socket.close() + try: + ssl_socket, cert_verified = create_session(url, port) + cipher_suite, protocol = get_cipher_suite_and_protocol(ssl_socket) + certificate = get_certificate(ssl_socket) + ssl_socket.close() + except ssl.SSLError: + sslv3 = SSLv3(url, port) + sslv3.parse_cipher_suite() + sslv3.parse_certificate() + cipher_suite = sslv3.cipher_suite + certificate = sslv3.certificate + cert_verified = sslv3.cert_verified + protocol = sslv3.protocol + return certificate, cert_verified, cipher_suite, protocol diff --git a/scan_parameters/non_ratable/ProtocolSupport.py b/scan_parameters/non_ratable/ProtocolSupport.py index 0a8581d..48ab9a6 100644 --- a/scan_parameters/non_ratable/ProtocolSupport.py +++ b/scan_parameters/non_ratable/ProtocolSupport.py @@ -4,7 +4,7 @@ from ..utils import rate_parameter from ..ratable.PType import PType from ..connection.connection_utils import create_session_pyopenssl -from scan_vulnerabilities.tests import sslv3 +from ssl_scan.SSLv3 import SSLv3 class ProtocolSupport: @@ -23,10 +23,10 @@ def scan_protocols(self): """ logging.info('Scanning TLS versions...') ssl_versions = { - SSL.TLSv1_METHOD: "TLSv1.0", - SSL.TLSv1_1_METHOD: "TLSv1.1", - SSL.TLSv1_2_METHOD: "TLSv1.2", - SSL.SSLv23_METHOD: "" + SSL.TLSv1_METHOD: 'TLSv1.0', + SSL.TLSv1_1_METHOD: 'TLSv1.1', + SSL.TLSv1_2_METHOD: 'TLSv1.2', + SSL.SSLv23_METHOD: 'unknown' } supported_protocols = [] unsupported_protocols = [] @@ -35,8 +35,10 @@ def scan_protocols(self): version = ssl_versions[num_version] try: ssl_socket = create_session_pyopenssl(self.url, self.port, context) - if version == "": + if version == 'unknown': version = ssl_socket.get_protocol_version_name() + if version == 'TLSv1': + version += '.0' ssl_socket.close() if version not in supported_protocols: supported_protocols.append(version) @@ -46,7 +48,8 @@ def scan_protocols(self): if 'TLSv1.3' not in supported_protocols: unsupported_protocols.append('TLSv1.3') # SSLv3 scanning - result = sslv3.scan((self.url, self.port)) + sslv3 = SSLv3(self.url, self.port) + result = sslv3.scan_sslv3_version() if result: supported_protocols.append("SSLv3") else: diff --git a/scan_vulnerabilities/tests/sslv3.py b/scan_vulnerabilities/tests/sslv3.py deleted file mode 100644 index 46b579a..0000000 --- a/scan_vulnerabilities/tests/sslv3.py +++ /dev/null @@ -1,49 +0,0 @@ -from ..utils import * - - -def construct_client_hello(): - client_hello = bytes([ - # Record protocol - 0x16, # Content type (Handshake) - 0x03, 0x00, # Version (SSLv3) - 0x00, 0x8f, # Length - # Handshake protocol - 0x01, # Handshake type - 0x00, 0x00, 0x8b, # Length - 0x03, 0x00, # Version - # Random bytes - 0xa9, 0x09, 0x3f, 0x70, 0xad, 0xdc, 0xde, 0x4f, - 0xb1, 0x78, 0x47, 0xe5, 0xf3, 0x35, 0xea, 0xc9, - 0x1b, 0x3b, 0x34, 0x37, 0x23, 0xd8, 0xd4, 0x5d, - 0x92, 0x40, 0x4b, 0x01, 0x9e, 0x55, 0xf7, 0x2f, - 0x00, # Session id length - 0x00, 0x64, # Cipher suites length - # Cipher suites - 0xc0, 0x14, 0xc0, 0x0a, 0x00, 0x39, 0x00, 0x38, - 0x00, 0x37, 0x00, 0x36, 0x00, 0x88, 0x00, 0x87, - 0x00, 0x86, 0x00, 0x85, 0xc0, 0x0f, 0xc0, 0x05, - 0x00, 0x35, 0x00, 0x84, 0xc0, 0x13, 0xc0, 0x09, - 0x00, 0x33, 0x00, 0x32, 0x00, 0x31, 0x00, 0x30, - 0x00, 0x9a, 0x00, 0x99, 0x00, 0x98, 0x00, 0x97, - 0x00, 0x45, 0x00, 0x44, 0x00, 0x43, 0x00, 0x42, - 0xc0, 0x0e, 0xc0, 0x04, 0x00, 0x2f, 0x00, 0x96, - 0x00, 0x41, 0x00, 0x07, 0xc0, 0x11, 0xc0, 0x07, - 0xc0, 0x0c, 0xc0, 0x02, 0x00, 0x05, 0x00, 0x04, - 0xc0, 0x12, 0xc0, 0x08, 0x00, 0x16, 0x00, 0x13, - 0x00, 0x10, 0x00, 0x0d, 0xc0, 0x0d, 0xc0, 0x03, - 0x00, 0x0a, 0x00, 0xff, - 0x01, # Compression methods length - 0x00 # Compression methods - ]) - return client_hello - - -def scan(address): - client_hello = construct_client_hello() - timeout = 2 - server_hello, sock = send_client_hello(address, client_hello, timeout) - # Test if the response is Content type Alert (0x15) - # and test if the alert message is handshake failure (0x28) - if server_hello[0] == 0x15 and server_hello[6] == 0x28: - return False - return True diff --git a/ssl_scan/SSLv3.py b/ssl_scan/SSLv3.py new file mode 100644 index 0000000..d14f69d --- /dev/null +++ b/ssl_scan/SSLv3.py @@ -0,0 +1,84 @@ +from .utils import read_json, hex_to_int + +from scan_vulnerabilities.utils import * +from cryptography.x509 import load_pem_x509_certificate, load_der_x509_certificate + + +class SSLv3: + def __init__(self, url, port): + self.address = (url, port) + self.protocol = 'SSLv3' + self.cipher_suite = None + self.certificate = None + self.cert_verified = None + self.timeout = 2 + self.client_hello = bytes([ + # Record protocol + 0x16, # Content type (Handshake) + 0x03, 0x00, # Version (SSLv3) + 0x00, 0x8f, # Length + # Handshake protocol + 0x01, # Handshake type + 0x00, 0x00, 0x8b, # Length + 0x03, 0x00, # Version + # Random bytes + 0xa9, 0x09, 0x3f, 0x70, 0xad, 0xdc, 0xde, 0x4f, + 0xb1, 0x78, 0x47, 0xe5, 0xf3, 0x35, 0xea, 0xc9, + 0x1b, 0x3b, 0x34, 0x37, 0x23, 0xd8, 0xd4, 0x5d, + 0x92, 0x40, 0x4b, 0x01, 0x9e, 0x55, 0xf7, 0x2f, + 0x00, # Session id length + 0x00, 0x64, # Cipher suites length + # Cipher suites + 0xc0, 0x14, 0xc0, 0x0a, 0x00, 0x39, 0x00, 0x38, + 0x00, 0x37, 0x00, 0x36, 0x00, 0x88, 0x00, 0x87, + 0x00, 0x86, 0x00, 0x85, 0xc0, 0x0f, 0xc0, 0x05, + 0x00, 0x35, 0x00, 0x84, 0xc0, 0x13, 0xc0, 0x09, + 0x00, 0x33, 0x00, 0x32, 0x00, 0x31, 0x00, 0x30, + 0x00, 0x9a, 0x00, 0x99, 0x00, 0x98, 0x00, 0x97, + 0x00, 0x45, 0x00, 0x44, 0x00, 0x43, 0x00, 0x42, + 0xc0, 0x0e, 0xc0, 0x04, 0x00, 0x2f, 0x00, 0x96, + 0x00, 0x41, 0x00, 0x07, 0xc0, 0x11, 0xc0, 0x07, + 0xc0, 0x0c, 0xc0, 0x02, 0x00, 0x05, 0x00, 0x04, + 0xc0, 0x12, 0xc0, 0x08, 0x00, 0x16, 0x00, 0x13, + 0x00, 0x10, 0x00, 0x0d, 0xc0, 0x0d, 0xc0, 0x03, + 0x00, 0x0a, 0x00, 0xff, + 0x01, # Compression methods length + 0x00 # Compression methods + ]) + self.response, _ = send_client_hello(self.address, self.client_hello, self.timeout) + + def scan_sslv3_version(self): + # Test if the response is Content type Alert (0x15) + # and test if the alert message is handshake failure (0x28) + if self.response[0] == 0x15 and self.response[6] == 0x28: + return False + return True + + def parse_cipher_suite(self): + cipher_suites = read_json('iana_cipher_suites.json') + sess_id_len_idx = 43 # Always fixed + cipher_suite_idx = self.response[sess_id_len_idx] + sess_id_len_idx + 1 + cipher_suites_bytes = f'0x{self.response[cipher_suite_idx]:X},' \ + f'0x{self.response[cipher_suite_idx + 1]:X}' + self.cipher_suite = cipher_suites[cipher_suites_bytes] + + def parse_certificate(self): + # Length is always at the same place in server_hello (idx 3, 4) + server_hello_len = hex_to_int([self.response[3], self.response[4]]) + # +4 -- Length index in server_hello + record_protocol_certificate_begin_idx = server_hello_len + 4 + 1 + # +5 -- Certificate index in record layer + handshake_certificate_idx = record_protocol_certificate_begin_idx + 5 + # +7 -- Certificate length index in handshake protocol: certificate + certificate_len_idx = handshake_certificate_idx + 7 + certificate_len = hex_to_int([ + self.response[certificate_len_idx], + self.response[certificate_len_idx + 1], + self.response[certificate_len_idx + 2] + ]) + certificate_idx = certificate_len_idx + 3 + certificate_in_bytes = self.response[certificate_idx:certificate_len + certificate_idx] + self.certificate = load_der_x509_certificate(certificate_in_bytes) + + def verify_cert(self): + self.cert_verified = False diff --git a/ssl_scan/__init__.py b/ssl_scan/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ssl_scan/utils.py b/ssl_scan/utils.py new file mode 100644 index 0000000..a0db21f --- /dev/null +++ b/ssl_scan/utils.py @@ -0,0 +1,27 @@ +import os, json + + +def read_json(file_name: str): + """ + Read a json file and return its content. + + :param file_name: json file name + :return: json data in python objects + """ + root_dir = os.path.dirname(os.path.abspath(__file__)) + file = open(f'{root_dir}/../resources/{file_name}', 'r') + json_data = json.loads(file.read()) + file.close() + return json_data + + +def hex_to_int(hex_num: list): + result = '0x' + # {}:02x: + # {}: -- value + # 0 -- padding with zeros + # 2 -- number digits + # x -- hex format + for num in hex_num: + result += f'{num:02x}' + return int(result, 16) diff --git a/ssltest.py b/ssltest.py index 8bc4d1d..112b436 100755 --- a/ssltest.py +++ b/ssltest.py @@ -9,6 +9,7 @@ from scan_vulnerabilities.tests import session_ticket from scan_vulnerabilities.tests import crime from scan_vulnerabilities.tests import rc4_support +from ssl_scan.SSLv3 import SSLv3 from scan_parameters.ratable.CipherSuite import CipherSuite from scan_parameters.ratable.Certificate import Certificate from scan_parameters.non_ratable.ProtocolSupport import ProtocolSupport @@ -72,7 +73,7 @@ def vulnerability_scan(address, tests, version): # if no -t argument is present if not tests: scans = [value for value in tests_switcher.values()] - elif tests[0] == 0: + elif 0 in tests: return {} else: scans = [tests_switcher.get(test) for test in tests] @@ -207,7 +208,7 @@ def check_test_numbers(tests, usage): :param usage: usage string :return: """ - if not tests or tests[0] == 0: + if not tests or 0 in tests: return test_numbers = [test for test in tests_switcher.keys()] unknown_tests = list(filter(lambda test: test not in test_numbers, tests)) From bae9341ab0a94c89e9573286f405fc35793d2617 Mon Sep 17 00:00:00 2001 From: SamoKopecky Date: Tue, 3 Aug 2021 13:28:24 +0200 Subject: [PATCH 7/8] Simplify SSL version scanning --- scan_parameters/non_ratable/ProtocolSupport.py | 11 +++++++---- ssltest.py | 4 ++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/scan_parameters/non_ratable/ProtocolSupport.py b/scan_parameters/non_ratable/ProtocolSupport.py index 48ab9a6..128d424 100644 --- a/scan_parameters/non_ratable/ProtocolSupport.py +++ b/scan_parameters/non_ratable/ProtocolSupport.py @@ -54,15 +54,18 @@ def scan_protocols(self): supported_protocols.append("SSLv3") else: unsupported_protocols.append("SSLv3") - return supported_protocols, unsupported_protocols + for protocol in supported_protocols: + self.versions[PType.protocols][protocol] = 'N/A' + for no_protocol in unsupported_protocols: + self.versions[PType.no_protocol][no_protocol] = 'N/A' - def rate_protocols(self, supported_protocols, unsupported_protocols): + def rate_protocols(self): """ Rate the scanned protocols. """ - for protocol in supported_protocols: + for protocol in list(self.versions[PType.protocols].keys()): self.versions[PType.protocols][protocol] = rate_parameter(PType.protocol, protocol) - for no_protocol in unsupported_protocols: + for no_protocol in list(self.versions[PType.no_protocol].keys()): self.versions[PType.no_protocol][no_protocol] = rate_parameter(PType.no_protocol, no_protocol) if not self.versions: return diff --git a/ssltest.py b/ssltest.py index 112b436..2be4847 100755 --- a/ssltest.py +++ b/ssltest.py @@ -243,8 +243,8 @@ def scan(args, port: int): certificate.rate_certificate() protocol_support = ProtocolSupport(args.url, port) - supported_protocols, unsupported_protocols = protocol_support.scan_protocols() - protocol_support.rate_protocols(supported_protocols, unsupported_protocols) + protocol_support.scan_protocols() + protocol_support.rate_protocols() versions = WebServerSoft(args.url, port, args.nmap_scan) versions.scan_server_software() From 2cf862f0d18148eddaadd00584adce145e63b5bd Mon Sep 17 00:00:00 2001 From: SamoKopecky Date: Tue, 3 Aug 2021 14:19:52 +0200 Subject: [PATCH 8/8] Improve SSL/TLS version scanning --- resources/{type_names.json => english_strings.json} | 0 scan_parameters/connection/connection_utils.py | 1 + scan_parameters/non_ratable/ProtocolSupport.py | 2 ++ scan_parameters/non_ratable/WebServerSoft.py | 5 +---- ssl_scan/SSLv3.py | 4 +++- ssl_scan/__init__.py | 1 + text_output/TextOutput.py | 4 ++-- 7 files changed, 10 insertions(+), 7 deletions(-) rename resources/{type_names.json => english_strings.json} (100%) diff --git a/resources/type_names.json b/resources/english_strings.json similarity index 100% rename from resources/type_names.json rename to resources/english_strings.json diff --git a/scan_parameters/connection/connection_utils.py b/scan_parameters/connection/connection_utils.py index 851b493..3f3f586 100644 --- a/scan_parameters/connection/connection_utils.py +++ b/scan_parameters/connection/connection_utils.py @@ -35,6 +35,7 @@ def get_website_info(url: str, port: int): sslv3 = SSLv3(url, port) sslv3.parse_cipher_suite() sslv3.parse_certificate() + sslv3.verify_cert() cipher_suite = sslv3.cipher_suite certificate = sslv3.certificate cert_verified = sslv3.cert_verified diff --git a/scan_parameters/non_ratable/ProtocolSupport.py b/scan_parameters/non_ratable/ProtocolSupport.py index 128d424..5935fff 100644 --- a/scan_parameters/non_ratable/ProtocolSupport.py +++ b/scan_parameters/non_ratable/ProtocolSupport.py @@ -43,6 +43,8 @@ def scan_protocols(self): if version not in supported_protocols: supported_protocols.append(version) except SSL.Error as e: + if version == 'unknown': + continue unsupported_protocols.append(version) # Need to do this since there is no explicit option for TLSv1.3 if 'TLSv1.3' not in supported_protocols: diff --git a/scan_parameters/non_ratable/WebServerSoft.py b/scan_parameters/non_ratable/WebServerSoft.py index 59aebd2..1b8c1cb 100644 --- a/scan_parameters/non_ratable/WebServerSoft.py +++ b/scan_parameters/non_ratable/WebServerSoft.py @@ -59,10 +59,7 @@ def scan_server_software(self): """ Call the required functions to scan the webserver software. """ - scans = [] - # for testing purposes TODO: delete later - if self.url != '192.168.1.220': - scans.append(self.scan_software_http) + scans = [self.scan_software_http] if self.scan_nmap: scans.append(self.scan_software_nmap) for scan in scans: diff --git a/ssl_scan/SSLv3.py b/ssl_scan/SSLv3.py index d14f69d..6fb9c92 100644 --- a/ssl_scan/SSLv3.py +++ b/ssl_scan/SSLv3.py @@ -1,7 +1,9 @@ from .utils import read_json, hex_to_int +from OpenSSL.crypto import X509 +from OpenSSL.crypto import verify from scan_vulnerabilities.utils import * -from cryptography.x509 import load_pem_x509_certificate, load_der_x509_certificate +from cryptography.x509 import load_der_x509_certificate class SSLv3: diff --git a/ssl_scan/__init__.py b/ssl_scan/__init__.py index e69de29..21db5e1 100644 --- a/ssl_scan/__init__.py +++ b/ssl_scan/__init__.py @@ -0,0 +1 @@ +# http://ssllib.sourceforge.net/SSLv2.spec.html diff --git a/text_output/TextOutput.py b/text_output/TextOutput.py index 299d15c..0260362 100644 --- a/text_output/TextOutput.py +++ b/text_output/TextOutput.py @@ -7,7 +7,7 @@ class TextOutput: def __init__(self, data: str): self.output = '' self.ratings = read_json('security_levels_names.json') - self.type_names = read_json('type_names.json') + self.type_names = read_json('english_strings.json') self.data = data self.current_data = {} @@ -83,7 +83,7 @@ def output_supported_versions(self, data: dict): if len(values) > 0: versions = [] for k, v in list(values.items()): - versions.append(f'\t\t{k}->{v}') + versions.append(f'\t\t{k}->{self.ratings[v]}') values = '\n'.join(versions) self.output += f'\t{self.type_names[key]}:\n{values}\n'